merge autoland to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Fri, 20 Oct 2017 11:37:54 +0200
changeset 683863 d1e995c8640a191cd127e87273ec96cb2fabffa9
parent 683834 be030533c5afe73f222918809d03b1b5f08f7ef3 (current diff)
parent 683862 58190ad9f36d303669e45fd91b6793ae1dea9a01 (diff)
child 683864 53242956c7854b8b0933557dcb812b79390549f4
child 683878 d1c40a18aeef20ca29fcbe02b2685b2d9aa80e26
child 683885 4ae63a30f821faafe70cf9b2910a881c527639e9
child 683887 59544574f3eee6faf92e2a69d38f41696c4d78d8
child 683889 df44bf1034bbfe5ed581e02bc9f0a5b46f46618d
child 683890 4d15496882d6fc1dbca1742eb800df88ea196151
child 683891 444c126e274e002120317bfce0ef85f0f8858535
child 683893 b2f05b2625b93c7a5c047474ddd32429e42f7692
child 683895 28aded3b44be5514f8bba24d72cdf51ea0b44936
child 683897 87dde64770a3cbe8a2261843ebb9718f2e150523
child 683899 e3d652ab3dbded3da6847b3e61c58c04324d5e4b
child 683900 90cad9590114e4fedcebb994345c14d938c8366c
child 683901 a31a49588984c6a4eddccd116375587596e45463
child 683902 f48d2a5f614d356ff6f4b99a8f4b25d49f13c22a
child 683906 c2d0f621aab3fcff4ea1796aa486177474d6c5ba
child 683908 d0f97efa041c3ee98836c812b2651e1f08e156a1
child 683909 d6854a5cd0a2bd0a120f94648c1fcb4a0d929e3e
child 683914 458f044ba397f5857e8f6bcd378676d43d6d5729
child 683920 6cc77d8fa9deae4d1da3819b2749bd8ffabd6e72
child 683922 816bd8b20efbbcbb0f8e69cf8be925b011dec5dc
child 683945 fd1e74dcfa1d854e785098fa3acc119fefc5419c
child 683949 52326296ffd6e2536b634f3951f0e5b5fd042ad2
child 683951 6dbccfcbe4ec61bac53db5551b14ff79738770c1
child 683952 703df890d8bc0a38c6168e860edf33281ae783a1
child 683953 974ab62c9fd5b56fbe4bb364129c08a93842abe5
child 683957 be1ba0ed52fb653699f8e92c3c528cec9a930022
child 683958 d44785821e4a9abfc1f77e59dc5d4a7633ab48af
child 683998 cc3c9647f61b87e4e08677b2ba2ed428b65125a1
child 684042 aba6835199694a86b484bb60466bb4d900f8359e
child 684043 acc8626021c82fd0fa6c2dd0d94a55d43d74e1ae
child 684053 c830455937925950649575d84a08298ac1d5c341
child 684063 46595d72327eaabbac4f7cc89eff5dfe755fac99
child 684064 01259aa4d1f2419ff9caa1c9a4667b68626dd023
child 684066 b8a043bb9eba6bde40ae5286d95a700ddbe481a9
child 684069 1ea3e47321a60b6fb1acd7bc8c84d96034660400
child 684071 8843a008b48f0561164d91457bfa57d331561ac7
child 684074 7db518b669ce69f4967ffdb03ee41b8322342e1b
child 684102 6c92780eac23ac8702b5b704a728e15ea0e5c38f
child 684103 d558af2d2c8023d20cc59c06e1fc7012ec5f7ddf
child 684104 c6d51a1c01bdcc9b9f9c2cf05ac0bde79063f167
child 684113 822271402656c2dae01dd051431cdb49981ff4aa
child 684114 0c78cac688c5e8f55a5c9cb6f468df1d7e8365f3
child 684117 21d3942fc7367ce6287aaf8cdd9a3af325771c4d
child 684118 b49b1c3662de12b982f6edde264a94b18ab76d56
child 684120 140ef2d806c86cccccfb3f372ff2b1e0f5b75d65
child 684121 359dcda9be96d8d076abb203982094b1f6b58ce3
child 684122 c3d6b43926f2e9f9f9467b1e9611e96e28f0106c
child 684287 ab845a62dc8ec19ebd21dfa013cd851f7b077c76
child 684288 a7e5502cc9c960253e2db33ec2d38955d4b7b3e7
child 684289 f97f73e1886c13c4a128a69275d6372d909dc1ed
child 684369 e1095874ef1522ee235cc9df45459d3b81a544da
child 684371 0eec3d00052750176e74b8a0cd15bb798803c5c3
child 684372 e52708795bb972b8d9f877d2590ad184e3d21fca
child 684387 c3626cf3e9ced826a74a67f2ab59714029f84a23
child 684405 7e833b70e837ca0cb56c3fd2213f9a3bd3b33403
child 684579 d4580c831f968bcb7bce9a4f699f0e2ea92bb21d
child 684616 5dde7e898ccaf1279bc9169fd71aebbcec8f652b
child 684716 436eb0d027854df531224c76118fada7ae82d9e0
child 684754 9f1e10fb3fe24b6ae0e03c514371e49d4ee52805
child 684893 bcda938ed48d28b8a57e2a822ed2c07aba1e5d5d
child 684897 d464145bb532fc9c8243518e54b848bcf651f52c
child 684900 eb072b4a23ab4fe14116ae7fecec607fc3c1e08e
child 685000 fc6b37df2a0dfffd73d7cbbbe0c00a98eec91af0
child 685409 ebd7a404d96e677b6a068346e7ed24fb8a955941
child 685610 c4acc917b9b4d0f6e19ea9edf7e196351fa1222c
child 686819 b71e73e7a9e3e4468154a3a073b62fd5e7fe3f69
child 688869 5884e75998ca8945ee1e8508cb757fb8ad638dc8
child 692071 2505e4a7270da68f9548e5d774e94ffdefb808ec
child 695313 e6eed98452831afedbe26fda5921db51d74fdff1
push id85474
push userbmo:emilio@crisal.io
push dateFri, 20 Oct 2017 10:02:12 +0000
reviewersmerge, merge
milestone58.0a1
merge autoland to mozilla-central. r=merge a=merge MozReview-Commit-ID: BY4c5BIOF81
browser/components/resistfingerprinting/test/browser/browser_roundedWindow_open_min.js
browser/locales/searchplugins/diribg.xml
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_add_edited_input_to_history.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete-properties-with-non-alphanumeric-names.js
dom/media/test/seek.yuv
security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/README.md
security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/create_test_files.sh
security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/nss_ctypes.py
security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/sign_b2g_app.py
security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_1/icon-128.png
security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_1/index.html
security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_1/manifest.webapp
security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin/icon-128.png
security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin/index.html
security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin/manifest.webapp
security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin_toolkit_webapps/index.html
security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin_toolkit_webapps/manifest.webapp
security/manager/ssl/tests/unit/test_signed_apps/privileged-app-test-1.0.zip
security/manager/ssl/tests/unit/test_signed_apps/test-privileged-app-test-1.0.zip
security/manager/ssl/tests/unit/test_signed_apps/trusted_ca1.der
security/manager/ssl/tests/unit/test_signed_apps/unknown_issuer_app_1.zip
security/manager/ssl/tests/unit/test_signed_apps/unsigned_app_1.zip
security/manager/ssl/tests/unit/test_signed_apps/valid_app_1.zip
toolkit/components/printingui/win/nsPrintDialogUtil.cpp
toolkit/components/printingui/win/nsPrintDialogUtil.h
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,56 +1,61 @@
 # Always ignore node_modules.
 **/node_modules/**/*.*
 
+# Always ignore crashtests - specially crafted files that originally caused a
+# crash.
+**/crashtests/**
+
 # Exclude expected objdirs.
 obj*/**
 
 # We ignore all these directories by default, until we get them enabled.
 # If you are enabling a directory, please add directory specific exclusions
 # below.
 chrome/**
 docshell/**
 editor/**
-embedding/**
 extensions/cookie/**
 extensions/spellcheck/**
 extensions/universalchardet/**
 gfx/**
 image/**
 intl/**
 layout/**
 media/**
 memory/**
 modules/**
 netwerk/**
 parser/**
-python/**
 rdf/**
-servo/**
 tools/update-packaging/**
 uriloader/**
-view/**
 widget/**
 
 # We currently have no js files in these directories, so we ignore them by
 # default to aid ESLint's performance.
 build/**
 config/**
 db/**
+embedding/**
 gradle/**
 hal/**
 mfbt/**
 mozglue/**
 nsprpub/**
 other-licenses/**
 probes/**
 startupcache/**
 xpfe/**
 
+# These directories only contain crashtests, but we still skip the whole
+# directory to aid performance.
+view/**
+
 # browser/ exclusions
 browser/app/**
 browser/branding/**/firefox-branding.js
 # Gzipped test file.
 browser/base/content/test/general/gZipOfflineChild.html
 browser/base/content/test/urlbar/file_blank_but_not_blank.html
 # New tab is likely to be replaced soon.
 browser/base/content/newtab/**
@@ -277,28 +282,34 @@ mobile/android/chrome/content/about.js
 # Not much JS to lint and non-standard at that
 mobile/android/installer/
 mobile/android/locales/
 
 # Non-standard `(catch ex if ...)`
 mobile/android/chrome/content/browser.js
 mobile/android/components/Snippets.js
 
+# Only contains non-standard test files.
+python/**
+
 # security/ exclusions (pref files).
 security/manager/ssl/security-prefs.js
 
 # NSS / taskcluster only.
 security/nss/**
 
 # services/ exclusions
 
 # Uses `#filter substitution`
 services/sync/modules/constants.js
 services/sync/services-sync.js
 
+# Servo is imported.
+servo/**
+
 # Remote protocol exclusions
 testing/marionette/test_*.js
 testing/marionette/atom.js
 testing/marionette/legacyaction.js
 testing/marionette/client
 testing/marionette/doc
 testing/marionette/harness
 
--- a/accessible/tests/mochitest/hittest/a11y.ini
+++ b/accessible/tests/mochitest/hittest/a11y.ini
@@ -4,12 +4,11 @@ support-files = zoom_tree.xul
   !/accessible/tests/mochitest/letters.gif
 
 [test_browser.html]
 [test_canvas_hitregion.html]
 skip-if = (os == "android" || appname == "b2g")
 [test_general.html]
 [test_menu.xul]
 [test_shadowroot.html]
-skip-if = stylo # Stylo doesn't support shadow DOM yet, bug 1293844
 [test_zoom.html]
 [test_zoom_text.html]
 [test_zoom_tree.xul]
--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -268,18 +268,22 @@ var BrowserPageActions = {
    * Returns the node in the urlbar to which popups for the given action should
    * be anchored.  If the action is null, a sensible anchor is returned.
    *
    * @param  action (PageActions.Action, optional)
    *         The action you want to anchor.
    * @return (DOM node, nonnull) The node to which the action should be
    *         anchored.
    */
-  panelAnchorNodeForAction(action) {
+  panelAnchorNodeForAction(action, event) {
     // Try each of the following nodes in order, using the first that's visible.
+    if (event && event.target.closest("panel")) {
+      return this.mainButtonNode;
+    }
+
     let potentialAnchorNodeIDs = [
       action && action.anchorIDOverride,
       action && this._urlbarButtonNodeIDForActionID(action.id),
       this.mainButtonNode.id,
       "identity-icon",
     ];
     let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
                     .getInterface(Ci.nsIDOMWindowUtils);
@@ -748,17 +752,17 @@ var BrowserPageActionFeedback = {
     delete this.feedbackLabel;
     return this.feedbackLabel = document.getElementById("pageActionFeedbackMessage");
   },
 
   show(action, event) {
     this.feedbackLabel.textContent = this.panelNode.getAttribute(action.id + "Feedback");
     this.panelNode.hidden = false;
 
-    let anchor = BrowserPageActions.panelAnchorNodeForAction(action);
+    let anchor = BrowserPageActions.panelAnchorNodeForAction(action, event);
     this.panelNode.openPopup(anchor, {
       position: "bottomcenter topright",
       triggerEvent: event,
     });
 
     this.panelNode.addEventListener("popupshown", () => {
       this.feedbackAnimationBox.setAttribute("animate", "true");
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -134,16 +134,19 @@ if (AppConstants.MOZ_CRASHREPORTER) {
   XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter",
                                      "@mozilla.org/xre/app-info;1",
                                      "nsICrashReporter");
 }
 
 XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
   return Services.strings.createBundle("chrome://browser/locale/browser.properties");
 });
+XPCOMUtils.defineLazyGetter(this, "gTabBrowserBundle", function() {
+  return Services.strings.createBundle("chrome://browser/locale/tabbrowser.properties");
+});
 
 XPCOMUtils.defineLazyGetter(this, "gCustomizeMode", function() {
   let scope = {};
   Cu.import("resource:///modules/CustomizeMode.jsm", scope);
   return new scope.CustomizeMode(window);
 });
 
 XPCOMUtils.defineLazyGetter(this, "gPrefService", function() {
@@ -4071,18 +4074,17 @@ function FillHistoryMenu(aParent) {
       item.setAttribute("index", j);
 
       // Cache this so that gotoHistoryIndex doesn't need the original index
       item.setAttribute("historyindex", j - index);
 
       if (j != index) {
         // Use list-style-image rather than the image attribute in order to
         // allow CSS to override this.
-        item.style.listStyleImage =
-          "url(" + PlacesUtils.urlWithSizeRef(window, "page-icon:" + uri, 16) + ")";
+        item.style.listStyleImage = `url(page-icon:${uri})`;
       }
 
       if (j < index) {
         item.className = "unified-nav-back menuitem-iconic menuitem-with-favicon";
         item.setAttribute("tooltiptext", tooltipBack);
       } else if (j == index) {
         item.setAttribute("type", "radio");
         item.setAttribute("checked", "true");
@@ -8948,18 +8950,18 @@ TabModalPromptBox.prototype = {
     try {
       hostForAllowFocusCheckbox = principalToAllowFocusFor.URI.host;
     } catch (ex) { /* Ignore exceptions for host-less URIs */ }
     if (hostForAllowFocusCheckbox) {
       let allowFocusRow = document.createElementNS(XUL_NS, "row");
       allowFocusCheckbox = document.createElementNS(XUL_NS, "checkbox");
       let spacer = document.createElementNS(XUL_NS, "spacer");
       allowFocusRow.appendChild(spacer);
-      let label = gBrowser.mStringBundle.getFormattedString("tabs.allowTabFocusByPromptForSite",
-                                                            [hostForAllowFocusCheckbox]);
+      let label = gTabBrowserBundle.formatStringFromName("tabs.allowTabFocusByPromptForSite",
+                                                      [hostForAllowFocusCheckbox], 1);
       allowFocusCheckbox.setAttribute("label", label);
       allowFocusRow.appendChild(allowFocusCheckbox);
       newPrompt.appendChild(allowFocusRow);
     }
 
     let tab = gBrowser.getTabForBrowser(browser);
     let closeCB = this._promptCloseCallback.bind(null, onCloseCallback, principalToAllowFocusFor,
                                                  allowFocusCheckbox);
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -12,17 +12,16 @@
           xmlns:xbl="http://www.mozilla.org/xbl">
 
   <binding id="tabbrowser">
     <resources>
       <stylesheet src="chrome://browser/content/tabbrowser.css"/>
     </resources>
 
     <content>
-      <xul:stringbundle anonid="tbstringbundle" src="chrome://browser/locale/tabbrowser.properties"/>
       <xul:tabbox anonid="tabbox" class="tabbrowser-tabbox"
                   flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown,tabcontainer"
                   onselect="if (event.target.localName == 'tabpanels') this.parentNode.updateCurrentBrowser();">
         <xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
           <xul:notificationbox flex="1" notificationside="top">
             <xul:hbox flex="1" class="browserSidebarContainer">
               <xul:vbox flex="1" class="browserContainer">
                 <xul:stack flex="1" class="browserStack" anonid="browserStack">
@@ -71,19 +70,16 @@
                    .getService(Components.interfaces.mozIPlacesAutoComplete);
       </field>
       <field name="mTabBox" readonly="true">
         document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
       </field>
       <field name="mPanelContainer" readonly="true">
         document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
       </field>
-      <field name="mStringBundle">
-        document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
-      </field>
       <field name="mCurrentTab">
         null
       </field>
       <field name="_lastRelatedTabMap">
         new WeakMap();
       </field>
       <field name="mCurrentBrowser">
         null
@@ -1627,17 +1623,17 @@
                 try {
                   var characterSet = browser.characterSet;
                   const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
                                                  .getService(Components.interfaces.nsITextToSubURI);
                   title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
                 } catch (ex) { /* Do nothing. */ }
               } else {
                 // Still no title? Fall back to our untitled string.
-                title = this.mStringBundle.getString("tabs.emptyTabTitle");
+                title = gTabBrowserBundle.GetStringFromName("tabs.emptyTabTitle");
               }
             }
 
             return this._setTabLabel(aTab, title, { isContentTitle });
           ]]>
         </body>
       </method>
 
@@ -2640,17 +2636,17 @@
               lazyBrowserURI = aURIObject;
               aURI = "about:blank";
             }
 
             var uriIsAboutBlank = aURI == "about:blank";
 
             if (!aNoInitialLabel) {
               if (isBlankPageURL(aURI)) {
-                t.setAttribute("label", this.mStringBundle.getString("tabs.emptyTabTitle"));
+                t.setAttribute("label", gTabBrowserBundle.GetStringFromName("tabs.emptyTabTitle"));
               } else {
                 // Set URL as label so that the tab isn't empty initially.
                 this.setInitialTabTitle(t, aURI, { beforeTabOpen: true });
               }
             }
 
             if (aIsPrerendered) {
               t.setAttribute("hidden", "true");
@@ -2908,37 +2904,36 @@
           var shouldPrompt = Services.prefs.getBoolPref(pref);
           if (!shouldPrompt)
             return true;
 
           var ps = Services.prompt;
 
           // default to true: if it were false, we wouldn't get this far
           var warnOnClose = { value: true };
-          var bundle = this.mStringBundle;
 
           // focus the window before prompting.
           // this will raise any minimized window, which will
           // make it obvious which window the prompt is for and will
           // solve the problem of windows "obscuring" the prompt.
           // see bug #350299 for more details
           window.focus();
           var warningMessage =
-            PluralForm.get(tabsToClose, bundle.getString("tabs.closeWarningMultiple"))
+            PluralForm.get(tabsToClose, gTabBrowserBundle.GetStringFromName("tabs.closeWarningMultiple"))
                       .replace("#1", tabsToClose);
           var buttonPressed =
             ps.confirmEx(window,
-                         bundle.getString("tabs.closeWarningTitle"),
+                         gTabBrowserBundle.GetStringFromName("tabs.closeWarningTitle"),
                          warningMessage,
                          (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0)
                          + (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
-                         bundle.getString("tabs.closeButtonMultiple"),
+                         gTabBrowserBundle.GetStringFromName("tabs.closeButtonMultiple"),
                          null, null,
                          aCloseTabs == this.closingTabsEnum.ALL ?
-                           bundle.getString("tabs.closeWarningPromptMe") : null,
+                           gTabBrowserBundle.GetStringFromName("tabs.closeWarningPromptMe") : null,
                          warnOnClose);
           var reallyClose = (buttonPressed == 0);
 
           // don't set the pref unless they press OK and it's false
           if (aCloseTabs == this.closingTabsEnum.ALL && reallyClose && !warnOnClose.value)
             Services.prefs.setBoolPref(pref, false);
 
           return reallyClose;
@@ -5511,55 +5506,55 @@
           if (tab.localName != "tab") {
             event.preventDefault();
             return;
           }
 
           let stringWithShortcut = (stringId, keyElemId) => {
             let keyElem = document.getElementById(keyElemId);
             let shortcut = ShortcutUtils.prettifyShortcut(keyElem);
-            return this.mStringBundle.getFormattedString(stringId, [shortcut]);
+            return gTabBrowserBundle.formatStringFromName(stringId, [shortcut], 1);
           };
 
           var label;
           if (tab.mOverCloseButton) {
             label = tab.selected ?
                     stringWithShortcut("tabs.closeSelectedTab.tooltip", "key_close") :
-                    this.mStringBundle.getString("tabs.closeTab.tooltip");
+                    gTabBrowserBundle.GetStringFromName("tabs.closeTab.tooltip");
           } else if (tab._overPlayingIcon) {
             let stringID;
             if (tab.selected) {
               stringID = tab.linkedBrowser.audioMuted ?
                 "tabs.unmuteAudio.tooltip" :
                 "tabs.muteAudio.tooltip";
               label = stringWithShortcut(stringID, "key_toggleMute");
             } else {
               if (tab.hasAttribute("activemedia-blocked")) {
                 stringID = "tabs.unblockAudio.tooltip";
               } else {
                 stringID = tab.linkedBrowser.audioMuted ?
                   "tabs.unmuteAudio.background.tooltip" :
                   "tabs.muteAudio.background.tooltip";
               }
 
-              label = this.mStringBundle.getString(stringID);
+              label = gTabBrowserBundle.GetStringFromName(stringID);
             }
           } else {
             label = tab._fullLabel || tab.getAttribute("label");
             if (AppConstants.E10S_TESTING_ONLY &&
                 tab.linkedBrowser &&
                 tab.linkedBrowser.isRemoteBrowser) {
               label += " - e10s";
               if (tab.linkedBrowser.frameLoader &&
                   Services.appinfo.maxWebProcessCount > 1) {
                 label += " (" + tab.linkedBrowser.frameLoader.tabParent.osPid + ")";
               }
             }
             if (tab.userContextId) {
-              label = this.mStringBundle.getFormattedString("tabs.containers.tooltip", [label, ContextualIdentityService.getUserContextLabel(tab.userContextId)]);
+              label = gTabBrowserBundle.formatStringFromName("tabs.containers.tooltip", [label, ContextualIdentityService.getUserContextLabel(tab.userContextId)], 2);
             }
           }
 
           event.target.setAttribute("label", label);
         ]]></body>
       </method>
 
       <method name="handleEvent">
@@ -6367,20 +6362,20 @@
     </content>
 
     <implementation implements="nsIDOMEventListener, nsIObserver">
       <constructor>
         <![CDATA[
           this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
 
           let { restoreTabsButton } = this;
-          restoreTabsButton.setAttribute("label", this.tabbrowser.mStringBundle.getString("tabs.restoreLastTabs"));
+          restoreTabsButton.setAttribute("label", gTabBrowserBundle.GetStringFromName("tabs.restoreLastTabs"));
 
           var tab = this.firstChild;
-          tab.label = this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle");
+          tab.label = gTabBrowserBundle.GetStringFromName("tabs.emptyTabTitle");
           tab.setAttribute("onerror", "this.removeAttribute('image');");
 
           window.addEventListener("resize", this);
           window.addEventListener("load", this);
 
           Services.prefs.addObserver("privacy.userContext", this);
           this.observe(null, "nsPref:changed", "privacy.userContext.enabled");
 
@@ -6611,17 +6606,17 @@
       </property>
 
       <method name="_propagateVisibility">
         <body><![CDATA[
           let visible = this.visible;
 
           document.getElementById("menu_closeWindow").hidden = !visible;
           document.getElementById("menu_close").setAttribute("label",
-            this.tabbrowser.mStringBundle.getString(visible ? "tabs.closeTab" : "tabs.close"));
+            gTabBrowserBundle.GetStringFromName(visible ? "tabs.closeTab" : "tabs.close"));
 
           TabsInTitlebar.allowedBy("tabs-visible", visible);
         ]]></body>
       </method>
 
       <method name="updateVisibility">
         <body><![CDATA[
           if (this.childNodes.length - this.tabbrowser._removingTabs.length == 1)
--- a/browser/base/content/test/performance/browser_startup.js
+++ b/browser/base/content/test/performance/browser_startup.js
@@ -100,17 +100,16 @@ const startupPhases = {
       "resource://gre/modules/CrashSubmit.jsm",
       "resource://gre/modules/FxAccounts.jsm",
       "resource://gre/modules/FxAccountsStorage.jsm",
       "resource://gre/modules/PlacesSyncUtils.jsm",
       "resource://gre/modules/Sqlite.jsm",
     ]),
     services: new Set([
       "@mozilla.org/browser/annotation-service;1",
-      "@mozilla.org/browser/favicon-service;1",
       "@mozilla.org/browser/nav-bookmarks-service;1",
     ])
   }},
 
   // Things that are expected to be completely out of the startup path
   // and loaded lazily when used for the first time by the user should
   // be blacklisted here.
   "before becoming idle": {blacklist: {
--- a/browser/base/content/test/urlbar/browser_page_action_menu.js
+++ b/browser/base/content/test/urlbar/browser_page_action_menu.js
@@ -121,16 +121,63 @@ add_task(async function emailLink() {
     let hiddenPromise = promisePageActionPanelHidden();
     EventUtils.synthesizeMouseAtCenter(emailLinkButton, {});
     await hiddenPromise;
 
     Assert.ok(fnCalled);
   });
 });
 
+add_task(async function copyURLFromPanel() {
+  // Open an actionable page so that the main page action button appears.  (It
+  // does not appear on about:blank for example.)
+  let url = "http://example.com/";
+  await BrowserTestUtils.withNewTab(url, async () => {
+    // Open the panel and click Copy URL.
+    await promisePageActionPanelOpen();
+    Assert.ok(true, "page action panel opened");
+
+    let copyURLButton =
+      document.getElementById("pageAction-panel-copyURL");
+    let hiddenPromise = promisePageActionPanelHidden();
+    EventUtils.synthesizeMouseAtCenter(copyURLButton, {});
+    await hiddenPromise;
+
+    let feedbackPanel = document.getElementById("pageActionFeedback");
+    let feedbackShownPromise = BrowserTestUtils.waitForEvent(feedbackPanel, "popupshown");
+    await feedbackShownPromise;
+    Assert.equal(feedbackPanel.anchorNode.id, "pageActionButton", "Feedback menu should be anchored on the main Page Action button");
+    let feedbackHiddenPromise = promisePanelHidden("pageActionFeedback");
+    await feedbackHiddenPromise;
+  });
+});
+
+add_task(async function copyURLFromURLBar() {
+  // Open an actionable page so that the main page action button appears.  (It
+  // does not appear on about:blank for example.)
+  let url = "http://example.com/";
+  await BrowserTestUtils.withNewTab(url, async () => {
+    // Add action to URL bar.
+    let action = PageActions._builtInActions.find(a => a.id == "copyURL");
+    action.shownInUrlbar = true;
+    registerCleanupFunction(() => action.shownInUrlbar = false);
+
+    let copyURLButton =
+      document.getElementById("pageAction-urlbar-copyURL");
+    let feedbackShownPromise = promisePanelShown("pageActionFeedback");
+    EventUtils.synthesizeMouseAtCenter(copyURLButton, {});
+
+    await feedbackShownPromise;
+    let panel = document.getElementById("pageActionFeedback");
+    Assert.equal(panel.anchorNode.id, "pageAction-urlbar-copyURL", "Feedback menu should be anchored on the main URL bar button");
+    let feedbackHiddenPromise = promisePanelHidden("pageActionFeedback");
+    await feedbackHiddenPromise;
+  });
+});
+
 add_task(async function sendToDevice_nonSendable() {
   // Open a tab that's not sendable.  An about: page like about:home is
   // convenient.
   await BrowserTestUtils.withNewTab("about:home", async () => {
     // ... but the page actions should be hidden on about:home, including the
     // main button.  (It's not easy to load a page that's both actionable and
     // not sendable.)  So first check that that's the case, and then unhide the
     // main button so that this test can continue.
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -1,13 +1,14 @@
 [DEFAULT]
 support-files =
   head.js
   head_pageAction.js
   head_sessions.js
+  head_webNavigation.js
   profilerSymbols.sjs
   context.html
   context_frame.html
   ctxmenu-image.png
   context_tabs_onUpdated_page.html
   context_tabs_onUpdated_iframe.html
   file_clearplugindata.html
   file_find_frames.html
@@ -171,16 +172,18 @@ skip-if = os == "linux" && debug && bits
 [browser_ext_themes_validation.js]
 [browser_ext_url_overrides_newtab.js]
 [browser_ext_user_events.js]
 [browser_ext_webRequest.js]
 [browser_ext_webNavigation_frameId0.js]
 [browser_ext_webNavigation_getFrames.js]
 [browser_ext_webNavigation_onCreatedNavigationTarget.js]
 [browser_ext_webNavigation_onCreatedNavigationTarget_contextmenu.js]
+[browser_ext_webNavigation_onCreatedNavigationTarget_named_window.js]
+[browser_ext_webNavigation_onCreatedNavigationTarget_subframe_window_open.js]
 [browser_ext_webNavigation_onCreatedNavigationTarget_window_open.js]
 [browser_ext_webNavigation_urlbar_transitions.js]
 [browser_ext_windows.js]
 [browser_ext_windows_create.js]
 tags = fullscreen
 [browser_ext_windows_create_params.js]
 [browser_ext_windows_create_tabId.js]
 [browser_ext_windows_create_url.js]
--- a/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget.js
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget.js
@@ -1,15 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-const BASE_URL = "http://mochi.test:8888/browser/browser/components/extensions/test/browser";
-const SOURCE_PAGE = `${BASE_URL}/webNav_createdTargetSource.html`;
-const OPENED_PAGE = `${BASE_URL}/webNav_createdTarget.html`;
+Services.scriptloader.loadSubScript(new URL("head_webNavigation.js", gTestPath).href,
+                                    this);
 
 async function background() {
   const tabs = await browser.tabs.query({active: true, currentWindow: true});
   const sourceTabId = tabs[0].id;
 
   const sourceTabFrames = await browser.webNavigation.getAllFrames({tabId: sourceTabId});
 
   browser.webNavigation.onCreatedNavigationTarget.addListener((msg) => {
@@ -29,83 +28,65 @@ async function background() {
     browser.test.sendMessage("tabsOnCreated", tab.id);
   });
 
   browser.test.sendMessage("expectedSourceTab", {
     sourceTabId, sourceTabFrames,
   });
 }
 
-async function runTestCase({extension, openNavTarget, expectedWebNavProps}) {
-  await openNavTarget();
-
-  const webNavMsg = await extension.awaitMessage("webNavOnCreated");
-  const createdTabId = await extension.awaitMessage("tabsOnCreated");
-  const completedNavMsg = await extension.awaitMessage("webNavOnCompleted");
-
-  let {sourceTabId, sourceFrameId, url} = expectedWebNavProps;
-
-  is(webNavMsg.tabId, createdTabId, "Got the expected tabId property");
-  is(webNavMsg.sourceTabId, sourceTabId, "Got the expected sourceTabId property");
-  is(webNavMsg.sourceFrameId, sourceFrameId, "Got the expected sourceFrameId property");
-  is(webNavMsg.url, url, "Got the expected url property");
-
-  is(completedNavMsg.tabId, createdTabId, "Got the expected webNavigation.onCompleted tabId property");
-  is(completedNavMsg.url, url, "Got the expected webNavigation.onCompleted url property");
-}
-
 add_task(async function test_on_created_navigation_target_from_mouse_click() {
   const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, SOURCE_PAGE);
 
   const extension = ExtensionTestUtils.loadExtension({
     background,
     manifest: {
       permissions: ["webNavigation"],
     },
   });
 
   await extension.startup();
 
   const expectedSourceTab = await extension.awaitMessage("expectedSourceTab");
 
   info("Open link in a new tab using Ctrl-click");
 
-  await runTestCase({
+  await runCreatedNavigationTargetTest({
     extension,
     openNavTarget() {
       BrowserTestUtils.synthesizeMouseAtCenter("#test-create-new-tab-from-mouse-click",
                                                {ctrlKey: true, metaKey: true},
                                                tab.linkedBrowser);
     },
     expectedWebNavProps: {
       sourceTabId: expectedSourceTab.sourceTabId,
       sourceFrameId: 0,
       url: `${OPENED_PAGE}#new-tab-from-mouse-click`,
     },
   });
 
   info("Open link in a new window using Shift-click");
 
-  await runTestCase({
+  await runCreatedNavigationTargetTest({
     extension,
     openNavTarget() {
       BrowserTestUtils.synthesizeMouseAtCenter("#test-create-new-window-from-mouse-click",
                                                {shiftKey: true},
                                                tab.linkedBrowser);
     },
     expectedWebNavProps: {
       sourceTabId: expectedSourceTab.sourceTabId,
       sourceFrameId: 0,
       url: `${OPENED_PAGE}#new-window-from-mouse-click`,
     },
   });
 
   info("Open link with target=\"_blank\" in a new tab using click");
 
-  await runTestCase({
+  await runCreatedNavigationTargetTest({
     extension,
     openNavTarget() {
       BrowserTestUtils.synthesizeMouseAtCenter("#test-create-new-tab-from-targetblank-click",
                                                {},
                                                tab.linkedBrowser);
     },
     expectedWebNavProps: {
       sourceTabId: expectedSourceTab.sourceTabId,
@@ -130,17 +111,17 @@ add_task(async function test_on_created_
   });
 
   await extension.startup();
 
   const expectedSourceTab = await extension.awaitMessage("expectedSourceTab");
 
   info("Open a subframe link in a new tab using Ctrl-click");
 
-  await runTestCase({
+  await runCreatedNavigationTargetTest({
     extension,
     openNavTarget() {
       BrowserTestUtils.synthesizeMouseAtCenter(function() {
         // This code runs as a framescript in the child process and it returns the
         // target link in the subframe.
         return this.content.frames[0].document // eslint-disable-line mozilla/no-cpows-in-tests
           .querySelector("#test-create-new-tab-from-mouse-click-subframe");
       }, {ctrlKey: true, metaKey: true}, tab.linkedBrowser);
@@ -149,17 +130,17 @@ add_task(async function test_on_created_
       sourceTabId: expectedSourceTab.sourceTabId,
       sourceFrameId: expectedSourceTab.sourceTabFrames[1].frameId,
       url: `${OPENED_PAGE}#new-tab-from-mouse-click-subframe`,
     },
   });
 
   info("Open a subframe link in a new window using Shift-click");
 
-  await runTestCase({
+  await runCreatedNavigationTargetTest({
     extension,
     openNavTarget() {
       BrowserTestUtils.synthesizeMouseAtCenter(function() {
         // This code runs as a framescript in the child process and it returns the
         // target link in the subframe.
         return this.content.frames[0].document // eslint-disable-line mozilla/no-cpows-in-tests
                       .querySelector("#test-create-new-window-from-mouse-click-subframe");
       }, {shiftKey: true}, tab.linkedBrowser);
@@ -168,17 +149,17 @@ add_task(async function test_on_created_
       sourceTabId: expectedSourceTab.sourceTabId,
       sourceFrameId: expectedSourceTab.sourceTabFrames[1].frameId,
       url: `${OPENED_PAGE}#new-window-from-mouse-click-subframe`,
     },
   });
 
   info("Open a subframe link with target=\"_blank\" in a new tab using click");
 
-  await runTestCase({
+  await runCreatedNavigationTargetTest({
     extension,
     openNavTarget() {
       BrowserTestUtils.synthesizeMouseAtCenter(function() {
         // This code runs as a framescript in the child process and it returns the
         // target link in the subframe.
         return this.content.frames[0].document // eslint-disable-line mozilla/no-cpows-in-tests
           .querySelector("#test-create-new-tab-from-targetblank-click-subframe");
       }, {}, tab.linkedBrowser);
@@ -189,9 +170,8 @@ add_task(async function test_on_created_
       url: `${OPENED_PAGE}#new-tab-from-targetblank-click-subframe`,
     },
   });
 
   await BrowserTestUtils.removeTab(tab);
 
   await extension.unload();
 });
-
--- a/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_contextmenu.js
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_contextmenu.js
@@ -1,15 +1,22 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-const BASE_URL = "http://mochi.test:8888/browser/browser/components/extensions/test/browser";
-const SOURCE_PAGE = `${BASE_URL}/webNav_createdTargetSource.html`;
-const OPENED_PAGE = `${BASE_URL}/webNav_createdTarget.html`;
+Services.scriptloader.loadSubScript(new URL("head_webNavigation.js", gTestPath).href,
+                                    this);
+
+async function clickContextMenuItem({pageElementSelector, contextMenuItemLabel}) {
+  const contentAreaContextMenu = await openContextMenu(pageElementSelector);
+  const item = contentAreaContextMenu.getElementsByAttribute("label", contextMenuItemLabel);
+  is(item.length, 1, `found contextMenu item for "${contextMenuItemLabel}"`);
+  item[0].click();
+  await closeContextMenu();
+}
 
 async function background() {
   const tabs = await browser.tabs.query({active: true, currentWindow: true});
   const sourceTabId = tabs[0].id;
 
   const sourceTabFrames = await browser.webNavigation.getAllFrames({tabId: sourceTabId});
 
   browser.webNavigation.onCreatedNavigationTarget.addListener((msg) => {
@@ -29,76 +36,50 @@ async function background() {
     browser.test.sendMessage("tabsOnCreated", tab.id);
   });
 
   browser.test.sendMessage("expectedSourceTab", {
     sourceTabId, sourceTabFrames,
   });
 }
 
-async function runTestCase({extension, openNavTarget, expectedWebNavProps}) {
-  await openNavTarget();
-
-  const webNavMsg = await extension.awaitMessage("webNavOnCreated");
-  const createdTabId = await extension.awaitMessage("tabsOnCreated");
-  const completedNavMsg = await extension.awaitMessage("webNavOnCompleted");
-
-  let {sourceTabId, sourceFrameId, url} = expectedWebNavProps;
-
-  is(webNavMsg.tabId, createdTabId, "Got the expected tabId property");
-  is(webNavMsg.sourceTabId, sourceTabId, "Got the expected sourceTabId property");
-  is(webNavMsg.sourceFrameId, sourceFrameId, "Got the expected sourceFrameId property");
-  is(webNavMsg.url, url, "Got the expected url property");
-
-  is(completedNavMsg.tabId, createdTabId, "Got the expected webNavigation.onCompleted tabId property");
-  is(completedNavMsg.url, url, "Got the expected webNavigation.onCompleted url property");
-}
-
-async function clickContextMenuItem({pageElementSelector, contextMenuItemLabel}) {
-  const contentAreaContextMenu = await openContextMenu(pageElementSelector);
-  const item = contentAreaContextMenu.getElementsByAttribute("label", contextMenuItemLabel);
-  is(item.length, 1, `found contextMenu item for "${contextMenuItemLabel}"`);
-  item[0].click();
-  await closeContextMenu();
-}
-
 add_task(async function test_on_created_navigation_target_from_context_menu() {
   const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, SOURCE_PAGE);
 
   const extension = ExtensionTestUtils.loadExtension({
     background,
     manifest: {
       permissions: ["webNavigation"],
     },
   });
 
   await extension.startup();
 
   const expectedSourceTab = await extension.awaitMessage("expectedSourceTab");
 
   info("Open link in a new tab from the context menu");
 
-  await runTestCase({
+  await runCreatedNavigationTargetTest({
     extension,
     async openNavTarget() {
       await clickContextMenuItem({
         pageElementSelector: "#test-create-new-tab-from-context-menu",
         contextMenuItemLabel: "Open Link in New Tab",
       });
     },
     expectedWebNavProps: {
       sourceTabId: expectedSourceTab.sourceTabId,
       sourceFrameId: 0,
       url: `${OPENED_PAGE}#new-tab-from-context-menu`,
     },
   });
 
   info("Open link in a new window from the context menu");
 
-  await runTestCase({
+  await runCreatedNavigationTargetTest({
     extension,
     async openNavTarget() {
       await clickContextMenuItem({
         pageElementSelector: "#test-create-new-window-from-context-menu",
         contextMenuItemLabel: "Open Link in New Window",
       });
     },
     expectedWebNavProps: {
@@ -124,17 +105,17 @@ add_task(async function test_on_created_
   });
 
   await extension.startup();
 
   const expectedSourceTab = await extension.awaitMessage("expectedSourceTab");
 
   info("Open a subframe link in a new tab from the context menu");
 
-  await runTestCase({
+  await runCreatedNavigationTargetTest({
     extension,
     async openNavTarget() {
       await clickContextMenuItem({
         pageElementSelector: function() {
           // This code runs as a framescript in the child process and it returns the
           // target link in the subframe.
           return this.content.frames[0] // eslint-disable-line mozilla/no-cpows-in-tests
             .document.querySelector("#test-create-new-tab-from-context-menu-subframe");
@@ -146,17 +127,17 @@ add_task(async function test_on_created_
       sourceTabId: expectedSourceTab.sourceTabId,
       sourceFrameId: expectedSourceTab.sourceTabFrames[1].frameId,
       url: `${OPENED_PAGE}#new-tab-from-context-menu-subframe`,
     },
   });
 
   info("Open a subframe link in a new window from the context menu");
 
-  await runTestCase({
+  await runCreatedNavigationTargetTest({
     extension,
     async openNavTarget() {
       await clickContextMenuItem({
         pageElementSelector: function() {
           // This code runs as a framescript in the child process and it returns the
           // target link in the subframe.
           return this.content.frames[0] // eslint-disable-line mozilla/no-cpows-in-tests
             .document.querySelector("#test-create-new-window-from-context-menu-subframe");
copy from browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_window_open.js
copy to browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_named_window.js
--- a/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_window_open.js
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_named_window.js
@@ -1,15 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-const BASE_URL = "http://mochi.test:8888/browser/browser/components/extensions/test/browser";
-const SOURCE_PAGE = `${BASE_URL}/webNav_createdTargetSource.html`;
-const OPENED_PAGE = `${BASE_URL}/webNav_createdTarget.html`;
+Services.scriptloader.loadSubScript(new URL("head_webNavigation.js", gTestPath).href,
+                                    this);
 
 async function background() {
   const tabs = await browser.tabs.query({active: true, currentWindow: true});
   const sourceTabId = tabs[0].id;
 
   const sourceTabFrames = await browser.webNavigation.getAllFrames({tabId: sourceTabId});
 
   browser.webNavigation.onCreatedNavigationTarget.addListener((msg) => {
@@ -35,135 +34,64 @@ async function background() {
     }
   });
 
   browser.test.sendMessage("expectedSourceTab", {
     sourceTabId, sourceTabFrames,
   });
 }
 
-async function runTestCase({extension, openNavTarget, expectedWebNavProps}) {
-  await openNavTarget();
-
-  const webNavMsg = await extension.awaitMessage("webNavOnCreated");
-  const createdTabId = await extension.awaitMessage("tabsOnCreated");
-  const completedNavMsg = await extension.awaitMessage("webNavOnCompleted");
-
-  let {sourceTabId, sourceFrameId, url} = expectedWebNavProps;
-
-  is(webNavMsg.tabId, createdTabId, "Got the expected tabId property");
-  is(webNavMsg.sourceTabId, sourceTabId, "Got the expected sourceTabId property");
-  is(webNavMsg.sourceFrameId, sourceFrameId, "Got the expected sourceFrameId property");
-  is(webNavMsg.url, url, "Got the expected url property");
-
-  is(completedNavMsg.tabId, createdTabId, "Got the expected webNavigation.onCompleted tabId property");
-  is(completedNavMsg.url, url, "Got the expected webNavigation.onCompleted url property");
-}
-
-add_task(async function test_on_created_navigation_target_from_window_open() {
+add_task(async function test_window_open_in_named_win() {
   const tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, SOURCE_PAGE);
 
   gBrowser.selectedTab = tab1;
 
   const extension = ExtensionTestUtils.loadExtension({
     background,
     manifest: {
       permissions: ["webNavigation", "tabs", "<all_urls>"],
     },
   });
 
   await extension.startup();
 
   const expectedSourceTab = await extension.awaitMessage("expectedSourceTab");
 
-  info("open an url in a new tab from a window.open call");
+  info("open a url in a new named window from a window.open call");
 
-  await runTestCase({
+  await runCreatedNavigationTargetTest({
     extension,
     openNavTarget() {
       extension.sendMessage({
         type: "execute-contentscript",
-        code: `window.open("${OPENED_PAGE}#new-tab-from-window-open"); true;`,
+        code: `window.open("${OPENED_PAGE}#new-named-window-open", "TestWinName"); true;`,
       });
     },
     expectedWebNavProps: {
       sourceTabId: expectedSourceTab.sourceTabId,
       sourceFrameId: 0,
-      url: `${OPENED_PAGE}#new-tab-from-window-open`,
+      url: `${OPENED_PAGE}#new-named-window-open`,
     },
   });
 
-  info("open an url in a new window from a window.open call");
+  info("open a url in an existent named window from a window.open call");
 
-  await runTestCase({
+  await runCreatedNavigationTargetTest({
     extension,
     openNavTarget() {
       extension.sendMessage({
         type: "execute-contentscript",
-        code: `window.open("${OPENED_PAGE}#new-win-from-window-open", "_blank", "toolbar=0"); true;`,
+        code: `window.open("${OPENED_PAGE}#existent-named-window-open", "TestWinName"); true;`,
       });
     },
     expectedWebNavProps: {
       sourceTabId: expectedSourceTab.sourceTabId,
       sourceFrameId: 0,
-      url: `${OPENED_PAGE}#new-win-from-window-open`,
+      url: `${OPENED_PAGE}#existent-named-window-open`,
     },
   });
 
+  assertNoPendingCreatedNavigationTargetData();
+
   await BrowserTestUtils.removeTab(tab1);
 
   await extension.unload();
 });
-
-add_task(async function test_on_created_navigation_target_from_window_open_subframe() {
-  const tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, SOURCE_PAGE);
-
-  gBrowser.selectedTab = tab1;
-
-  const extension = ExtensionTestUtils.loadExtension({
-    background,
-    manifest: {
-      permissions: ["webNavigation", "tabs", "<all_urls>"],
-    },
-  });
-
-  await extension.startup();
-
-  const expectedSourceTab = await extension.awaitMessage("expectedSourceTab");
-
-  info("open an url in a new tab from subframe window.open call");
-
-  await runTestCase({
-    extension,
-    openNavTarget() {
-      extension.sendMessage({
-        type: "execute-contentscript",
-        code: `document.querySelector('iframe').contentWindow.open("${OPENED_PAGE}#new-tab-from-window-open-subframe"); true;`,
-      });
-    },
-    expectedWebNavProps: {
-      sourceTabId: expectedSourceTab.sourceTabId,
-      sourceFrameId: expectedSourceTab.sourceTabFrames[1].frameId,
-      url: `${OPENED_PAGE}#new-tab-from-window-open-subframe`,
-    },
-  });
-
-  info("open an url in a new window from subframe window.open call");
-
-  await runTestCase({
-    extension,
-    openNavTarget() {
-      extension.sendMessage({
-        type: "execute-contentscript",
-        code: `document.querySelector('iframe').contentWindow.open("${OPENED_PAGE}#new-win-from-window-open-subframe", "_blank", "toolbar=0"); true;`,
-      });
-    },
-    expectedWebNavProps: {
-      sourceTabId: expectedSourceTab.sourceTabId,
-      sourceFrameId: expectedSourceTab.sourceTabFrames[1].frameId,
-      url: `${OPENED_PAGE}#new-win-from-window-open-subframe`,
-    },
-  });
-
-  await BrowserTestUtils.removeTab(tab1);
-
-  await extension.unload();
-});
copy from browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_window_open.js
copy to browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_subframe_window_open.js
--- a/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_window_open.js
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_subframe_window_open.js
@@ -1,15 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-const BASE_URL = "http://mochi.test:8888/browser/browser/components/extensions/test/browser";
-const SOURCE_PAGE = `${BASE_URL}/webNav_createdTargetSource.html`;
-const OPENED_PAGE = `${BASE_URL}/webNav_createdTarget.html`;
+Services.scriptloader.loadSubScript(new URL("head_webNavigation.js", gTestPath).href,
+                                    this);
 
 async function background() {
   const tabs = await browser.tabs.query({active: true, currentWindow: true});
   const sourceTabId = tabs[0].id;
 
   const sourceTabFrames = await browser.webNavigation.getAllFrames({tabId: sourceTabId});
 
   browser.webNavigation.onCreatedNavigationTarget.addListener((msg) => {
@@ -35,135 +34,130 @@ async function background() {
     }
   });
 
   browser.test.sendMessage("expectedSourceTab", {
     sourceTabId, sourceTabFrames,
   });
 }
 
-async function runTestCase({extension, openNavTarget, expectedWebNavProps}) {
-  await openNavTarget();
-
-  const webNavMsg = await extension.awaitMessage("webNavOnCreated");
-  const createdTabId = await extension.awaitMessage("tabsOnCreated");
-  const completedNavMsg = await extension.awaitMessage("webNavOnCompleted");
-
-  let {sourceTabId, sourceFrameId, url} = expectedWebNavProps;
-
-  is(webNavMsg.tabId, createdTabId, "Got the expected tabId property");
-  is(webNavMsg.sourceTabId, sourceTabId, "Got the expected sourceTabId property");
-  is(webNavMsg.sourceFrameId, sourceFrameId, "Got the expected sourceFrameId property");
-  is(webNavMsg.url, url, "Got the expected url property");
-
-  is(completedNavMsg.tabId, createdTabId, "Got the expected webNavigation.onCompleted tabId property");
-  is(completedNavMsg.url, url, "Got the expected webNavigation.onCompleted url property");
-}
-
-add_task(async function test_on_created_navigation_target_from_window_open() {
+add_task(async function test_window_open_from_subframe() {
   const tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, SOURCE_PAGE);
 
   gBrowser.selectedTab = tab1;
 
   const extension = ExtensionTestUtils.loadExtension({
     background,
     manifest: {
       permissions: ["webNavigation", "tabs", "<all_urls>"],
     },
   });
 
   await extension.startup();
 
   const expectedSourceTab = await extension.awaitMessage("expectedSourceTab");
 
-  info("open an url in a new tab from a window.open call");
+  info("open a url in a new tab from subframe window.open call");
+
+  await runCreatedNavigationTargetTest({
+    extension,
+    openNavTarget() {
+      extension.sendMessage({
+        type: "execute-contentscript",
+        code: `document.querySelector('iframe').contentWindow.open("${OPENED_PAGE}#new-tab-from-window-open-subframe"); true;`,
+      });
+    },
+    expectedWebNavProps: {
+      sourceTabId: expectedSourceTab.sourceTabId,
+      sourceFrameId: expectedSourceTab.sourceTabFrames[1].frameId,
+      url: `${OPENED_PAGE}#new-tab-from-window-open-subframe`,
+    },
+  });
+
+  info("open a url in a new window from subframe window.open call");
+
+  await runCreatedNavigationTargetTest({
+    extension,
+    openNavTarget() {
+      extension.sendMessage({
+        type: "execute-contentscript",
+        code: `document.querySelector('iframe').contentWindow.open("${OPENED_PAGE}#new-win-from-window-open-subframe", "_blank", "toolbar=0"); true;`,
+      });
+    },
+    expectedWebNavProps: {
+      sourceTabId: expectedSourceTab.sourceTabId,
+      sourceFrameId: expectedSourceTab.sourceTabFrames[1].frameId,
+      url: `${OPENED_PAGE}#new-win-from-window-open-subframe`,
+    },
+  });
+
+  assertNoPendingCreatedNavigationTargetData();
+
+  await BrowserTestUtils.removeTab(tab1);
+
+  await extension.unload();
+});
 
-  await runTestCase({
+add_task(async function test_window_open_close_from_browserAction_popup() {
+  const tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, SOURCE_PAGE);
+
+  gBrowser.selectedTab = tab1;
+
+  function popup() {
+    window.open("", "_self").close();
+
+    browser.test.sendMessage("browserAction_popup_executed");
+  }
+
+  const extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: {
+      browser_action: {
+        default_popup: "popup.html",
+      },
+      permissions: ["webNavigation", "tabs", "<all_urls>"],
+    },
+    files: {
+      "popup.html": `<!DOCTYPE html>
+        <html>
+          <head>
+            <meta charset="utf-8">
+          </head>
+          <body>
+            <script src="popup.js"></script>
+          </body>
+        </html>
+      `,
+      "popup.js": popup,
+    },
+  });
+
+  await extension.startup();
+
+  const expectedSourceTab = await extension.awaitMessage("expectedSourceTab");
+
+  clickBrowserAction(extension);
+
+  await extension.awaitMessage("browserAction_popup_executed");
+
+  info("open a url in a new tab from a window.open call");
+
+  await runCreatedNavigationTargetTest({
     extension,
     openNavTarget() {
       extension.sendMessage({
         type: "execute-contentscript",
         code: `window.open("${OPENED_PAGE}#new-tab-from-window-open"); true;`,
       });
     },
     expectedWebNavProps: {
       sourceTabId: expectedSourceTab.sourceTabId,
       sourceFrameId: 0,
       url: `${OPENED_PAGE}#new-tab-from-window-open`,
     },
   });
 
-  info("open an url in a new window from a window.open call");
-
-  await runTestCase({
-    extension,
-    openNavTarget() {
-      extension.sendMessage({
-        type: "execute-contentscript",
-        code: `window.open("${OPENED_PAGE}#new-win-from-window-open", "_blank", "toolbar=0"); true;`,
-      });
-    },
-    expectedWebNavProps: {
-      sourceTabId: expectedSourceTab.sourceTabId,
-      sourceFrameId: 0,
-      url: `${OPENED_PAGE}#new-win-from-window-open`,
-    },
-  });
+  assertNoPendingCreatedNavigationTargetData();
 
   await BrowserTestUtils.removeTab(tab1);
 
   await extension.unload();
 });
-
-add_task(async function test_on_created_navigation_target_from_window_open_subframe() {
-  const tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, SOURCE_PAGE);
-
-  gBrowser.selectedTab = tab1;
-
-  const extension = ExtensionTestUtils.loadExtension({
-    background,
-    manifest: {
-      permissions: ["webNavigation", "tabs", "<all_urls>"],
-    },
-  });
-
-  await extension.startup();
-
-  const expectedSourceTab = await extension.awaitMessage("expectedSourceTab");
-
-  info("open an url in a new tab from subframe window.open call");
-
-  await runTestCase({
-    extension,
-    openNavTarget() {
-      extension.sendMessage({
-        type: "execute-contentscript",
-        code: `document.querySelector('iframe').contentWindow.open("${OPENED_PAGE}#new-tab-from-window-open-subframe"); true;`,
-      });
-    },
-    expectedWebNavProps: {
-      sourceTabId: expectedSourceTab.sourceTabId,
-      sourceFrameId: expectedSourceTab.sourceTabFrames[1].frameId,
-      url: `${OPENED_PAGE}#new-tab-from-window-open-subframe`,
-    },
-  });
-
-  info("open an url in a new window from subframe window.open call");
-
-  await runTestCase({
-    extension,
-    openNavTarget() {
-      extension.sendMessage({
-        type: "execute-contentscript",
-        code: `document.querySelector('iframe').contentWindow.open("${OPENED_PAGE}#new-win-from-window-open-subframe", "_blank", "toolbar=0"); true;`,
-      });
-    },
-    expectedWebNavProps: {
-      sourceTabId: expectedSourceTab.sourceTabId,
-      sourceFrameId: expectedSourceTab.sourceTabFrames[1].frameId,
-      url: `${OPENED_PAGE}#new-win-from-window-open-subframe`,
-    },
-  });
-
-  await BrowserTestUtils.removeTab(tab1);
-
-  await extension.unload();
-});
--- a/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_window_open.js
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_window_open.js
@@ -1,15 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-const BASE_URL = "http://mochi.test:8888/browser/browser/components/extensions/test/browser";
-const SOURCE_PAGE = `${BASE_URL}/webNav_createdTargetSource.html`;
-const OPENED_PAGE = `${BASE_URL}/webNav_createdTarget.html`;
+Services.scriptloader.loadSubScript(new URL("head_webNavigation.js", gTestPath).href,
+                                    this);
 
 async function background() {
   const tabs = await browser.tabs.query({active: true, currentWindow: true});
   const sourceTabId = tabs[0].id;
 
   const sourceTabFrames = await browser.webNavigation.getAllFrames({tabId: sourceTabId});
 
   browser.webNavigation.onCreatedNavigationTarget.addListener((msg) => {
@@ -35,135 +34,130 @@ async function background() {
     }
   });
 
   browser.test.sendMessage("expectedSourceTab", {
     sourceTabId, sourceTabFrames,
   });
 }
 
-async function runTestCase({extension, openNavTarget, expectedWebNavProps}) {
-  await openNavTarget();
-
-  const webNavMsg = await extension.awaitMessage("webNavOnCreated");
-  const createdTabId = await extension.awaitMessage("tabsOnCreated");
-  const completedNavMsg = await extension.awaitMessage("webNavOnCompleted");
-
-  let {sourceTabId, sourceFrameId, url} = expectedWebNavProps;
-
-  is(webNavMsg.tabId, createdTabId, "Got the expected tabId property");
-  is(webNavMsg.sourceTabId, sourceTabId, "Got the expected sourceTabId property");
-  is(webNavMsg.sourceFrameId, sourceFrameId, "Got the expected sourceFrameId property");
-  is(webNavMsg.url, url, "Got the expected url property");
-
-  is(completedNavMsg.tabId, createdTabId, "Got the expected webNavigation.onCompleted tabId property");
-  is(completedNavMsg.url, url, "Got the expected webNavigation.onCompleted url property");
-}
-
-add_task(async function test_on_created_navigation_target_from_window_open() {
+add_task(async function test_window_open() {
   const tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, SOURCE_PAGE);
 
   gBrowser.selectedTab = tab1;
 
   const extension = ExtensionTestUtils.loadExtension({
     background,
     manifest: {
       permissions: ["webNavigation", "tabs", "<all_urls>"],
     },
   });
 
   await extension.startup();
 
   const expectedSourceTab = await extension.awaitMessage("expectedSourceTab");
 
-  info("open an url in a new tab from a window.open call");
+  info("open a url in a new tab from a window.open call");
 
-  await runTestCase({
+  await runCreatedNavigationTargetTest({
     extension,
     openNavTarget() {
       extension.sendMessage({
         type: "execute-contentscript",
         code: `window.open("${OPENED_PAGE}#new-tab-from-window-open"); true;`,
       });
     },
     expectedWebNavProps: {
       sourceTabId: expectedSourceTab.sourceTabId,
       sourceFrameId: 0,
       url: `${OPENED_PAGE}#new-tab-from-window-open`,
     },
   });
 
-  info("open an url in a new window from a window.open call");
+  info("open a url in a new window from a window.open call");
 
-  await runTestCase({
+  await runCreatedNavigationTargetTest({
     extension,
     openNavTarget() {
       extension.sendMessage({
         type: "execute-contentscript",
         code: `window.open("${OPENED_PAGE}#new-win-from-window-open", "_blank", "toolbar=0"); true;`,
       });
     },
     expectedWebNavProps: {
       sourceTabId: expectedSourceTab.sourceTabId,
       sourceFrameId: 0,
       url: `${OPENED_PAGE}#new-win-from-window-open`,
     },
   });
 
+  assertNoPendingCreatedNavigationTargetData();
+
   await BrowserTestUtils.removeTab(tab1);
 
   await extension.unload();
 });
 
-add_task(async function test_on_created_navigation_target_from_window_open_subframe() {
+add_task(async function test_window_open_close_from_browserAction_popup() {
   const tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, SOURCE_PAGE);
 
   gBrowser.selectedTab = tab1;
 
+  function popup() {
+    window.open("", "_self").close();
+
+    browser.test.sendMessage("browserAction_popup_executed");
+  }
+
   const extension = ExtensionTestUtils.loadExtension({
     background,
     manifest: {
+      browser_action: {
+        default_popup: "popup.html",
+      },
       permissions: ["webNavigation", "tabs", "<all_urls>"],
     },
+    files: {
+      "popup.html": `<!DOCTYPE html>
+        <html>
+          <head>
+            <meta charset="utf-8">
+          </head>
+          <body>
+            <script src="popup.js"></script>
+          </body>
+        </html>
+      `,
+      "popup.js": popup,
+    },
   });
 
   await extension.startup();
 
   const expectedSourceTab = await extension.awaitMessage("expectedSourceTab");
 
-  info("open an url in a new tab from subframe window.open call");
+  clickBrowserAction(extension);
+
+  await extension.awaitMessage("browserAction_popup_executed");
 
-  await runTestCase({
+  info("open a url in a new tab from a window.open call");
+
+  await runCreatedNavigationTargetTest({
     extension,
     openNavTarget() {
       extension.sendMessage({
         type: "execute-contentscript",
-        code: `document.querySelector('iframe').contentWindow.open("${OPENED_PAGE}#new-tab-from-window-open-subframe"); true;`,
+        code: `window.open("${OPENED_PAGE}#new-tab-from-window-open"); true;`,
       });
     },
     expectedWebNavProps: {
       sourceTabId: expectedSourceTab.sourceTabId,
-      sourceFrameId: expectedSourceTab.sourceTabFrames[1].frameId,
-      url: `${OPENED_PAGE}#new-tab-from-window-open-subframe`,
+      sourceFrameId: 0,
+      url: `${OPENED_PAGE}#new-tab-from-window-open`,
     },
   });
 
-  info("open an url in a new window from subframe window.open call");
-
-  await runTestCase({
-    extension,
-    openNavTarget() {
-      extension.sendMessage({
-        type: "execute-contentscript",
-        code: `document.querySelector('iframe').contentWindow.open("${OPENED_PAGE}#new-win-from-window-open-subframe", "_blank", "toolbar=0"); true;`,
-      });
-    },
-    expectedWebNavProps: {
-      sourceTabId: expectedSourceTab.sourceTabId,
-      sourceFrameId: expectedSourceTab.sourceTabFrames[1].frameId,
-      url: `${OPENED_PAGE}#new-win-from-window-open-subframe`,
-    },
-  });
+  assertNoPendingCreatedNavigationTargetData();
 
   await BrowserTestUtils.removeTab(tab1);
 
   await extension.unload();
 });
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/head_webNavigation.js
@@ -0,0 +1,39 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+/* exported BASE_URL, SOURCE_PAGE, OPENED_PAGE,
+   runCreatedNavigationTargetTest, assertNoPendingCreatedNavigationTargetData */
+
+const BASE_URL = "http://mochi.test:8888/browser/browser/components/extensions/test/browser";
+const SOURCE_PAGE = `${BASE_URL}/webNav_createdTargetSource.html`;
+const OPENED_PAGE = `${BASE_URL}/webNav_createdTarget.html`;
+
+async function runCreatedNavigationTargetTest({extension, openNavTarget, expectedWebNavProps}) {
+  await openNavTarget();
+
+  const webNavMsg = await extension.awaitMessage("webNavOnCreated");
+  const createdTabId = await extension.awaitMessage("tabsOnCreated");
+  const completedNavMsg = await extension.awaitMessage("webNavOnCompleted");
+
+  let {sourceTabId, sourceFrameId, url} = expectedWebNavProps;
+
+  is(webNavMsg.tabId, createdTabId, "Got the expected tabId property");
+  is(webNavMsg.sourceTabId, sourceTabId, "Got the expected sourceTabId property");
+  is(webNavMsg.sourceFrameId, sourceFrameId, "Got the expected sourceFrameId property");
+  is(webNavMsg.url, url, "Got the expected url property");
+
+  is(completedNavMsg.tabId, createdTabId, "Got the expected webNavigation.onCompleted tabId property");
+  is(completedNavMsg.url, url, "Got the expected webNavigation.onCompleted url property");
+}
+
+
+// Test that there are no pending createdNavigationTarget messages still tracked
+// in WebNavigation.jsm (to be called before the extension is unloaded, because
+// once the last extension which have subscribed a webNavigation event is unloaded
+// all the pending created navigation target data is completely cleared).
+function assertNoPendingCreatedNavigationTargetData() {
+  const {Manager} = Cu.import("resource://gre/modules/WebNavigation.jsm", {});
+  Assert.equal(Manager.createdNavigationTargetByOuterWindowId.size, 0,
+               "There should be no pending createdNavigationTarget messages in WebNavigation");
+}
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -957,16 +957,19 @@ BrowserGlue.prototype = {
     DirectoryLinksProvider.init();
     NewTabUtils.init();
     NewTabUtils.links.addProvider(DirectoryLinksProvider);
 
     PageActions.init();
 
     this._firstWindowTelemetry(aWindow);
     this._firstWindowLoaded();
+
+    // Set the default favicon size for UI views that use the page-icon protocol.
+    PlacesUtils.favicons.setDefaultIconURIPreferredSize(16 * aWindow.devicePixelRatio);
   },
 
   _sendMediaTelemetry() {
     let win = Services.appShell.hiddenDOMWindow;
     let v = win.document.createElementNS("http://www.w3.org/1999/xhtml", "video");
     v.reportCanPlayTelemetry();
   },
 
--- a/browser/components/resistfingerprinting/test/browser/browser.ini
+++ b/browser/components/resistfingerprinting/test/browser/browser.ini
@@ -13,17 +13,18 @@ support-files =
 [browser_netInfo.js]
 [browser_performanceAPI.js]
 [browser_roundedWindow_dialogWindow.js]
 [browser_roundedWindow_newWindow.js]
 [browser_roundedWindow_open_max_inner.js]
 [browser_roundedWindow_open_max_outer.js]
 [browser_roundedWindow_open_mid_inner.js]
 [browser_roundedWindow_open_mid_outer.js]
-[browser_roundedWindow_open_min.js]
+[browser_roundedWindow_open_min_inner.js]
+[browser_roundedWindow_open_min_outer.js]
 [browser_roundedWindow_windowSetting_max_inner.js]
 [browser_roundedWindow_windowSetting_max_outer.js]
 [browser_roundedWindow_windowSetting_mid_inner.js]
 [browser_roundedWindow_windowSetting_mid_outer.js]
 [browser_roundedWindow_windowSetting_min_inner.js]
 [browser_roundedWindow_windowSetting_min_outer.js]
 [browser_timezone.js]
 [browser_bug1369357_site_specific_zoom_level.js]
deleted file mode 100644
--- a/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_open_min.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
- * Bug 1330882 - A test case for opening new windows through window.open() as
- *   rounded size when fingerprinting resistance is enabled. This test is for
- *   minimum values.
- */
-
-OpenTest.run([
-  {settingWidth: 199, settingHeight: 99, targetWidth: 200, targetHeight: 100},
-  {settingWidth: 10, settingHeight: 10, targetWidth: 200, targetHeight: 100}
-]);
new file mode 100644
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_open_min_inner.js
@@ -0,0 +1,10 @@
+/*
+ * Bug 1330882 - A test case for opening new windows through window.open() as
+ *   rounded size when fingerprinting resistance is enabled. This test is for
+ *   minimum values.
+ */
+
+OpenTest.run([
+  {settingWidth: 199, settingHeight: 99, targetWidth: 200, targetHeight: 100},
+  {settingWidth: 10, settingHeight: 10, targetWidth: 200, targetHeight: 100}
+], false);
new file mode 100644
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_open_min_outer.js
@@ -0,0 +1,10 @@
+/*
+ * Bug 1330882 - A test case for opening new windows through window.open() as
+ *   rounded size when fingerprinting resistance is enabled. This test is for
+ *   minimum values.
+ */
+
+OpenTest.run([
+  {settingWidth: 199, settingHeight: 99, targetWidth: 200, targetHeight: 100},
+  {settingWidth: 10, settingHeight: 10, targetWidth: 200, targetHeight: 100}
+], true);
--- a/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
+++ b/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
@@ -2758,18 +2758,18 @@ class PreferencesPane extends React.Pure
                 key: nestedPref.name,
                 prefName: nestedPref.name,
                 disabled: !enabled,
                 value: prefs[nestedPref.name],
                 onChange: this.handlePrefChange,
                 titleString: nestedPref.titleString,
                 labelClassName: `icon ${nestedPref.icon}` }))
             )),
-            React.createElement("hr", null),
-            React.createElement(PreferencesInput, { className: "showSnippets", prefName: "feeds.snippets",
+            !prefs.disableSnippets && React.createElement("hr", null),
+            !prefs.disableSnippets && React.createElement(PreferencesInput, { className: "showSnippets", prefName: "feeds.snippets",
               value: prefs["feeds.snippets"], onChange: this.handlePrefChange,
               titleString: { id: "settings_pane_snippets_header" },
               descString: { id: "settings_pane_snippets_body" } })
           ),
           React.createElement(
             "section",
             { className: "actions" },
             React.createElement(
@@ -3883,23 +3883,23 @@ function addSnippetsSubscriber(store) {
 
   let initializing = false;
 
   store.subscribe(async () => {
     const state = store.getState();
     // state.Prefs.values["feeds.snippets"]:  Should snippets be shown?
     // state.Snippets.initialized             Is the snippets data initialized?
     // snippets.initialized:                  Is SnippetsProvider currently initialised?
-    if (state.Prefs.values["feeds.snippets"] && state.Snippets.initialized && !snippets.initialized &&
+    if (state.Prefs.values["feeds.snippets"] && !state.Prefs.values.disableSnippets && state.Snippets.initialized && !snippets.initialized &&
     // Don't call init multiple times
     !initializing) {
       initializing = true;
       await snippets.init({ appData: state.Snippets });
       initializing = false;
-    } else if (state.Prefs.values["feeds.snippets"] === false && snippets.initialized) {
+    } else if ((state.Prefs.values["feeds.snippets"] === false || state.Prefs.values.disableSnippets === true) && snippets.initialized) {
       snippets.uninit();
     }
   });
 
   // These values are returned for testing purposes
   return snippets;
 }
 
@@ -3907,9 +3907,9 @@ module.exports = {
   addSnippetsSubscriber,
   SnippetsMap,
   SnippetsProvider,
   SNIPPETS_UPDATE_INTERVAL_MS
 };
 /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
 
 /***/ })
-/******/ ]);
\ No newline at end of file
+/******/ ]);
--- a/browser/extensions/activity-stream/lib/ActivityStream.jsm
+++ b/browser/extensions/activity-stream/lib/ActivityStream.jsm
@@ -92,16 +92,20 @@ const PREFS_CONFIG = new Map([
   ["prerender", {
     title: "Use the prerendered version of activity-stream.html. This is set automatically by PrefsFeed.jsm.",
     value: true
   }],
   ["showSearch", {
     title: "Show the Search bar",
     value: true
   }],
+  ["disableSnippets", {
+    title: "Disable snippets on activity stream",
+    value: false
+  }],
   ["showTopSites", {
     title: "Show the Top Sites section",
     value: true
   }],
   ["collapseTopSites", {
     title: "Collapse the Top Sites section",
     value: false
   }],
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -346,21 +346,58 @@ FormAutofillHandler.prototype = {
         }
         // Delete the field so the phishing hint won't treat it as a "also fill"
         // field.
         delete profile[fieldName];
       }
     }
   },
 
+  _creditCardExpDateTransformer(profile) {
+    if (!profile["cc-exp"]) {
+      return;
+    }
+
+    let detail = this.getFieldDetailByName("cc-exp");
+    if (!detail) {
+      return;
+    }
+
+    let element = detail.elementWeakRef.get();
+    if (element.tagName != "INPUT" || !element.placeholder) {
+      return;
+    }
+
+    let result,
+      ccExpMonth = profile["cc-exp-month"],
+      ccExpYear = profile["cc-exp-year"],
+      placeholder = element.placeholder;
+
+    result = /(?:[^m]|\b)(m{1,2})\s*([-/\\]*)\s*(y{2,4})(?!y)/i.exec(placeholder);
+    if (result) {
+      profile["cc-exp"] = String(ccExpMonth).padStart(result[1].length, "0") +
+                          result[2] +
+                          String(ccExpYear).substr(-1 * result[3].length);
+      return;
+    }
+
+    result = /(?:[^y]|\b)(y{2,4})\s*([-/\\]*)\s*(m{1,2})(?!m)/i.exec(placeholder);
+    if (result) {
+      profile["cc-exp"] = String(ccExpYear).substr(-1 * result[1].length) +
+                          result[2] +
+                          String(ccExpMonth).padStart(result[3].length, "0");
+    }
+  },
+
   getAdaptedProfiles(originalProfiles) {
     for (let profile of originalProfiles) {
       this._addressTransformer(profile);
       this._telTransformer(profile);
       this._matchSelectOptions(profile);
+      this._creditCardExpDateTransformer(profile);
     }
     return originalProfiles;
   },
 
   /**
    * Processes form fields that can be autofilled, and populates them with the
    * profile provided by backend.
    *
--- a/browser/extensions/formautofill/content/FormAutofillFrameScript.js
+++ b/browser/extensions/formautofill/content/FormAutofillFrameScript.js
@@ -31,16 +31,19 @@ var FormAutofillFrameScript = {
       return;
     }
     this._hasPendingTask = true;
 
     setTimeout(() => {
       FormAutofillContent.identifyAutofillFields(this._nextHandleElement);
       this._hasPendingTask = false;
       this._nextHandleElement = null;
+      // This is for testing purpose only which sends a message to indicate that the
+      // form has been identified, and ready to open popup.
+      sendAsyncMessage("FormAutofill:FieldsIdentified");
     });
   },
 
   init() {
     addEventListener("focusin", this);
     addMessageListener("FormAutofill:PreviewProfile", this);
     addMessageListener("FormAutoComplete:PopupClosed", this);
     addMessageListener("FormAutoComplete:PopupOpened", this);
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -93,16 +93,54 @@ function getDisplayedPopupItems(browser,
 
   return [...listItemElems].filter(item => item.getAttribute("collapsed") != "true");
 }
 
 async function sleep(ms = 500) {
   await new Promise(resolve => setTimeout(resolve, ms));
 }
 
+async function focusAndWaitForFieldsIdentified(browser, selector) {
+  /* eslint no-shadow: ["error", { "allow": ["selector", "previouslyFocused", "previouslyIdentified"] }] */
+  const {previouslyFocused, previouslyIdentified} = await ContentTask.spawn(browser, {selector}, async function({selector}) {
+    Components.utils.import("resource://gre/modules/FormLikeFactory.jsm");
+    const input = content.document.querySelector(selector);
+    const rootElement = FormLikeFactory.findRootForField(input);
+    const previouslyFocused = content.document.activeElement == input;
+    const previouslyIdentified = rootElement.hasAttribute("test-formautofill-identified");
+
+    input.focus();
+
+    return {previouslyFocused, previouslyIdentified};
+  });
+
+  if (previouslyIdentified) {
+    return;
+  }
+
+  // Once the input is previously focused, no more FormAutofill:FieldsIdentified will be
+  // sent as the message goes along with focus event.
+  if (!previouslyFocused) {
+    await new Promise(resolve => {
+      Services.mm.addMessageListener("FormAutofill:FieldsIdentified", function onIdentified() {
+        Services.mm.removeMessageListener("FormAutofill:FieldsIdentified", onIdentified);
+        resolve();
+      });
+    });
+  }
+  // Wait 500ms to ensure that "markAsAutofillField" is completely finished.
+  await sleep();
+  await ContentTask.spawn(browser, {}, async function() {
+    Components.utils.import("resource://gre/modules/FormLikeFactory.jsm");
+    FormLikeFactory
+      .findRootForField(content.document.activeElement)
+      .setAttribute("test-formautofill-identified", "true");
+  });
+}
+
 async function expectPopupOpen(browser) {
   const {autoCompletePopup} = browser;
   const listItemElems = getDisplayedPopupItems(browser);
 
   await BrowserTestUtils.waitForCondition(() => autoCompletePopup.popupOpen,
                                          "popup should be open");
   await BrowserTestUtils.waitForCondition(() => {
     return [...listItemElems].every(item => {
@@ -111,33 +149,17 @@ async function expectPopupOpen(browser) 
              item.getAttribute("originaltype") == "autofill-footer") &&
              item.hasAttribute("formautofillattached");
     });
   }, "The popup should be a form autofill one");
 }
 
 async function openPopupOn(browser, selector) {
   await SimpleTest.promiseFocus(browser);
-  /* eslint no-shadow: ["error", { "allow": ["selector"] }] */
-  const identified = await ContentTask.spawn(browser, {selector}, async function({selector}) {
-    const input = content.document.querySelector(selector);
-    const forms = content.document.getElementsByTagName("form");
-    const rootElement = [...forms].find(form => form.contains(input)) || content.document.body;
-
-    input.focus();
-    if (rootElement.hasAttribute("test-formautofill-identified")) {
-      return true;
-    }
-    rootElement.setAttribute("test-formautofill-identified", "true");
-    return false;
-  });
-  // Wait 2 seconds for identifyAutofillFields if the form hasn't been identified yet.
-  if (!identified) {
-    await sleep(2000);
-  }
+  await focusAndWaitForFieldsIdentified(browser, selector);
   await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
   await expectPopupOpen(browser);
 }
 
 async function expectPopupClose(browser) {
   await BrowserTestUtils.waitForCondition(() => !browser.autoCompletePopup.popupOpen,
     "popup should have closed");
 }
--- a/browser/extensions/formautofill/test/mochitest/formautofill_common.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_common.js
@@ -9,28 +9,48 @@ let expectingPopup = null;
 
 const {FormAutofillUtils} = SpecialPowers.Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
 async function sleep(ms = 500, reason = "Intentionally wait for UI ready") {
   SimpleTest.requestFlakyTimeout(reason);
   await new Promise(resolve => setTimeout(resolve, ms));
 }
 
-async function setInput(selector, value) {
-  let input = document.querySelector("input" + selector);
-  input.value = value;
+async function focusAndWaitForFieldsIdentified(input) {
+  const rootElement = input.form || input.ownerDocument.documentElement;
+  const previouslyFocused = input != document.activeElement;
+
   input.focus();
 
-  // "identifyAutofillFields" is invoked asynchronously in "focusin" event. We
-  // should make sure fields are ready for popup before doing tests.
-  //
-  // TODO: "sleep" is used here temporarily because there's no event to
-  //       notify us of the state of "identifyAutofillFields" for now. We should
-  //       figure out a better way after the heuristics land.
-  await sleep(500, "Guarantee asynchronous identifyAutofillFields is invoked");
+  if (rootElement.hasAttribute("test-formautofill-identified")) {
+    return;
+  }
+  if (!previouslyFocused) {
+    await new Promise(resolve => {
+      formFillChromeScript.addMessageListener("FormAutofillTest:FieldsIdentified", function onIdentified() {
+        formFillChromeScript.removeMessageListener("FormAutofillTest:FieldsIdentified", onIdentified);
+        resolve();
+      });
+    });
+  }
+  // In order to ensure that "markAsAutofillField" is fully executed, a short period
+  // of timeout is still required.
+  await sleep(300, "Guarantee asynchronous identifyAutofillFields is invoked");
+  rootElement.setAttribute("test-formautofill-identified", "true");
+}
+
+async function setInput(selector, value, userInput = false) {
+  const input = document.querySelector("input" + selector);
+  if (userInput) {
+    SpecialPowers.wrap(input).setUserInput(value);
+  } else {
+    input.value = value;
+  }
+  await focusAndWaitForFieldsIdentified(input);
+
   return input;
 }
 
 function clickOnElement(selector) {
   let element = document.querySelector(selector);
 
   if (!element) {
     throw new Error("Can not find the element");
--- a/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
@@ -164,16 +164,20 @@ var ParentUtils = {
   observe(subject, topic, data) {
     assert.ok(topic === "formautofill-storage-changed");
     sendAsyncMessage("formautofill-storage-changed", {subject: null, topic, data});
   },
 };
 
 Services.obs.addObserver(ParentUtils, "formautofill-storage-changed");
 
+Services.mm.addMessageListener("FormAutofill:FieldsIdentified", () => {
+  sendAsyncMessage("FormAutofillTest:FieldsIdentified");
+});
+
 addMessageListener("FormAutofillTest:AddAddress", (msg) => {
   ParentUtils.operateAddress("add", msg, "FormAutofillTest:AddressAdded");
 });
 
 addMessageListener("FormAutofillTest:RemoveAddress", (msg) => {
   ParentUtils.operateAddress("remove", msg, "FormAutofillTest:AddressRemoved");
 });
 
--- a/browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
+++ b/browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
@@ -729,16 +729,90 @@ const TESTCASES = [
       "cc-exp-month": 1,
     })],
     expectedResult: [{
       "guid": "123",
       "cc-exp-month": 1,
     }],
     expectedOptionElements: [],
   },
+  {
+    description: "Use placeholder to adjust cc-exp format [mm/yy].",
+    document: `<form><input placeholder="mm/yy" autocomplete="cc-exp"></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
+      "cc-exp": "01/25",
+    })],
+  },
+  {
+    description: "Use placeholder to adjust cc-exp format [mm / yy].",
+    document: `<form><input placeholder="mm / yy" autocomplete="cc-exp"></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
+      "cc-exp": "01/25",
+    })],
+  },
+  {
+    description: "Use placeholder to adjust cc-exp format [MM / YY].",
+    document: `<form><input placeholder="MM / YY" autocomplete="cc-exp"></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
+      "cc-exp": "01/25",
+    })],
+  },
+  {
+    description: "Use placeholder to adjust cc-exp format [mm / yyyy].",
+    document: `<form><input placeholder="mm / yyyy" autocomplete="cc-exp"></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
+      "cc-exp": "01/2025",
+    })],
+  },
+  {
+    description: "Use placeholder to adjust cc-exp format [mm - yyyy].",
+    document: `<form><input placeholder="mm - yyyy" autocomplete="cc-exp"></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
+      "cc-exp": "01-2025",
+    })],
+  },
+  {
+    description: "Use placeholder to adjust cc-exp format [yyyy-mm].",
+    document: `<form><input placeholder="yyyy-mm" autocomplete="cc-exp"></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
+      "cc-exp": "2025-01",
+    })],
+  },
+  {
+    description: "Use placeholder to adjust cc-exp format [yyy-mm].",
+    document: `<form><input placeholder="yyy-mm" autocomplete="cc-exp"></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
+      "cc-exp": "025-01",
+    })],
+  },
+  {
+    description: "Use placeholder to adjust cc-exp format [mmm yyyy].",
+    document: `<form><input placeholder="mmm yyyy" autocomplete="cc-exp"></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+  },
+  {
+    description: "Use placeholder to adjust cc-exp format [mm foo yyyy].",
+    document: `<form><input placeholder="mm foo yyyy" autocomplete="cc-exp"></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+  },
+  {
+    description: "Use placeholder to adjust cc-exp format [mm - - yyyy].",
+    document: `<form><input placeholder="mm - - yyyy" autocomplete="cc-exp"></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+  },
 ];
 
 for (let testcase of TESTCASES) {
   add_task(async function() {
     do_print("Starting testcase: " + testcase.description);
 
     let doc = MockDocument.createTestDocument("http://localhost:8080/test/",
                                               testcase.document);
--- a/browser/locales/search/list.json
+++ b/browser/locales/search/list.json
@@ -1,12 +1,12 @@
 {
   "default": {
     "visibleDefaultEngines": [
-      "google", "yahoo", "amazondotcom", "bing", "ddg", "twitter", "wikipedia"
+      "google", "yahoo", "amazondotcom", "bing", "ddg", "ebay", "twitter", "wikipedia"
     ]
   },
   "regionOverrides": {
     "US": {
       "google": "google-nocodes"
     },
     "CA": {
       "google": "google-nocodes",
@@ -129,17 +129,17 @@
         "visibleDefaultEngines": [
           "yandex-by", "google", "ddg", "wikipedia-be", "wikipedia-be-tarask"
         ]
       }
     },
     "bg": {
       "default": {
         "visibleDefaultEngines": [
-          "google", "diribg", "amazondotcom", "ddg", "portalbgdict", "wikipedia-bg"
+          "google", "amazondotcom", "ddg", "portalbgdict", "wikipedia-bg"
         ]
       }
     },
     "bn-BD": {
       "default": {
         "visibleDefaultEngines": [
           "google", "yahoo", "bing", "ddg", "wikipedia-bn"
         ]
deleted file mode 100644
--- a/browser/locales/searchplugins/diribg.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<!-- 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/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Дири.бг</ShortName>
-<Description>Търсачка Дири на dir.bg</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAQAUAAAAAAAAAAAAAAAEAAAABAAD///8A7NLCAM+OZgDFd0cAyoNXAOK8pACySgoA+/TxANCPZwC0TAsAwm45AOzTwwDPiFkAuVENAMJmKgDpyLMAynxJAO7UwwDnwKYAvlYPAMp1OwD79PAA0opZAO/VxADXjlwAxFsRAPDWwwD46uEA8NbEAMlfEwDWh00A8tfFAM5kFgDRbiUA2opQAPPZxQDZfDUA02kYAPns4gDWciYA9NrGAOy3jQDZbhoA56VwAO6/mgD228YA/fbxAOB8KgDecxwA7LCAAPHCnAD33McA+ubVAOWALADjeB4A6ppWAOeJOgD43scA/O7jAO+scgDleh8A7aNkAOybVwD53sgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIDAwQFAAMGAQAAAAAHCAkJCQkJCQoKCQsAAAAADA0ODwAADxANDQ0RAAAAEhMUFQAAAAAAFhMTFwAAABgZGgAAAAAAABsZGRwAAAAdHQAAAAAAAAAAHh0fAAAAICEAAAAAAAAAACIgIwAAACQlJgAAAAAAAAAnJSgAAAApKisAAAAAAAAsKiotAAAALi8wMS4AAAAyMDAwMwAAAAA0NTY1Nzc4NjU1NjkAAAAAADo7PDw8PD00Pjw/AAAAAAAAAAA5OQAAAD48PwAAAAAAAAAAAAAAAAA+PD8AAAAAAAAAAAAAAAAAPjw/AAAAAAAAAAAAAAAAAD48PwAAAAAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8=</Image>
-<Url type="text/html" method="GET" template="http://www.diri.bg/search.php" resultdomain="diri.bg">
-  <Param name="came" value="s" />
-  <Param name="u" value="1" />
-  <Param name="browser" value="ff" />
-  <Param name="textfield" value="{searchTerms}" />
-</Url>
-<SearchForm>http://diri.bg/</SearchForm> 
-</SearchPlugin>
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -538,23 +538,31 @@ tabbrowser {
 .tabbrowser-tab[image] > .tab-stack > .tab-content[attention]:not([pinned]):not([selected="true"]):-moz-locale-dir(rtl) {
   background-position-x: right 11px;
 }
 
 .tab-label[attention]:not([selected="true"]) {
   font-weight: bold;
 }
 
-/* Tab separators */
+/* Drag space */
 
 .titlebar-placeholder[type="pre-tabs"],
 .titlebar-placeholder[type="post-tabs"] {
   width: 40px;
 }
 
+@media (max-width: 500px) {
+  .titlebar-placeholder[type="post-tabs"] {
+    display: none;
+  }
+}
+
+/* Tab separators */
+
 .titlebar-placeholder[type="pre-tabs"] {
   border-inline-end: 1px solid;
   opacity: 0.2;
 }
 
 .tabbrowser-tab::after,
 .tabbrowser-tab::before {
   border-left: 1px solid;
--- a/browser/themes/windows/compacttheme.css
+++ b/browser/themes/windows/compacttheme.css
@@ -41,16 +41,26 @@
     #TabsToolbar {
       color: hsl(240,9%,98%);
     }
 
     /* Keep showing the correct color inside the tabs. */
     .tabbrowser-tab {
       color: var(--chrome-color) !important;
     }
+
+    /* Because we're forcing the tabs toolbar to be [brighttext] to
+     * get white toolbar button icons, we need to manually set the
+     * correct color for the tab hover state for the light theme. */
+    .tabbrowser-tab:hover > .tab-stack > .tab-background:not([selected=true]):-moz-lwtheme-darktext {
+      background-color: rgba(0,0,0,.1) !important;
+    }
+    .tabbrowser-tab:hover > .tab-stack > .tab-background > .tab-line:not([selected=true]):-moz-lwtheme-darktext {
+      background-color: rgba(0,0,0,.2) !important;
+    }
   }
 }
 
 @media (-moz-windows-glass) {
   /* Set to full fill-opacity to improve visibility of toolbar buttons on aero glass. */
   #TabsToolbar {
     --toolbarbutton-icon-fill-opacity: 1;
   }
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -914,17 +914,19 @@ rust_unlock_unstable =
 ifdef MOZ_RUST_SIMD
 rust_unlock_unstable += RUSTC_BOOTSTRAP=1
 endif
 
 ifdef MOZ_USING_SCCACHE
 sccache_wrap := RUSTC_WRAPPER='$(CCACHE)'
 endif
 
+ifdef MOZ_DEBUG_SYMBOLS
 default_rustflags += -C debuginfo=2
+endif
 
 # We use the + prefix to pass down the jobserver fds to cargo, but we
 # don't use the prefix when make -n is used, so that cargo doesn't run
 # in that case)
 define RUN_CARGO
 $(if $(findstring n,$(filter-out --%, $(MAKEFLAGS))),,+)env $(environment_cleaner) $(rust_unlock_unstable) $(rustflags_override) $(sccache_wrap) \
 	CARGO_TARGET_DIR=$(CARGO_TARGET_DIR) \
 	RUSTC=$(RUSTC) \
--- a/devtools/client/inspector/rules/test/browser_rules_original-source-link.js
+++ b/devtools/client/inspector/rules/test/browser_rules_original-source-link.js
@@ -73,11 +73,11 @@ function editorSelected(editor) {
   is(line, 3, "cursor is at correct line number in original source");
 }
 
 function verifyLinkText(text, view) {
   info("Verifying that the rule-view stylesheet link is " + text);
   let label = getRuleViewLinkByIndex(view, 1)
     .querySelector(".ruleview-rule-source-label");
   return waitForSuccess(function* () {
-    return label.textContent == text;
+    return label.textContent == text && label.getAttribute("title") === URL_ROOT + text;
   }, "Link text changed to display correct location: " + text);
 }
--- a/devtools/client/inspector/rules/views/rule-editor.js
+++ b/devtools/client/inspector/rules/views/rule-editor.js
@@ -304,27 +304,30 @@ RuleEditor.prototype = {
     }
 
     this._currentLocation = {
       url,
       line,
       column
     };
 
-    let title = CssLogic.shortSource({href: displayURL});
+    let sourceTextContent = CssLogic.shortSource({href: displayURL});
+    let title = displayURL ? displayURL : sourceTextContent;
     if (line > 0) {
+      sourceTextContent += ":" + line;
       title += ":" + line;
     }
     if (this.rule.mediaText) {
+      sourceTextContent += " @" + this.rule.mediaText;
       title += " @" + this.rule.mediaText;
     }
 
     let sourceLabel = this.element.querySelector(".ruleview-rule-source-label");
     sourceLabel.setAttribute("title", title);
-    sourceLabel.textContent = title;
+    sourceLabel.textContent = sourceTextContent;
   },
 
   updateSourceLink: function () {
     if (this.rule.isSystem) {
       let sourceLabel = this.element.querySelector(".ruleview-rule-source-label");
       let title = this.rule.title;
       let sourceHref = (this.rule.sheet && this.rule.sheet.href) ?
           this.rule.sheet.href : title;
--- a/devtools/client/netmonitor/src/components/MonitorPanel.js
+++ b/devtools/client/netmonitor/src/components/MonitorPanel.js
@@ -9,17 +9,17 @@ const {
   createClass,
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
 const Actions = require("../actions/index");
-const { getFormDataSections } = require("../utils/request-utils");
+const { updateFormDataSections } = require("../utils/request-utils");
 const { getSelectedRequest } = require("../selectors/index");
 
 // Components
 const SplitBox = createFactory(require("devtools/client/shared/components/splitter/SplitBox"));
 const NetworkDetailsPanel = createFactory(require("./NetworkDetailsPanel"));
 const RequestList = createFactory(require("./RequestList"));
 const Toolbar = createFactory(require("./Toolbar"));
 const { div } = DOM;
@@ -49,42 +49,17 @@ const MonitorPanel = createClass({
     };
   },
 
   componentDidMount() {
     MediaQueryList.addListener(this.onLayoutChange);
   },
 
   componentWillReceiveProps(nextProps) {
-    let {
-      request = {},
-      updateRequest,
-    } = nextProps;
-    let {
-      formDataSections,
-      requestHeaders,
-      requestHeadersFromUploadStream,
-      requestPostData,
-    } = request;
-
-    if (!formDataSections && requestHeaders &&
-        requestHeadersFromUploadStream && requestPostData) {
-      getFormDataSections(
-        requestHeaders,
-        requestHeadersFromUploadStream,
-        requestPostData,
-        this.props.connector.getLongString,
-      ).then((newFormDataSections) => {
-        updateRequest(
-          request.id,
-          { formDataSections: newFormDataSections },
-          true,
-        );
-      });
-    }
+    updateFormDataSections(nextProps);
   },
 
   componentWillUnmount() {
     MediaQueryList.removeListener(this.onLayoutChange);
 
     let { clientWidth, clientHeight } = findDOMNode(this.refs.endPanel) || {};
 
     if (this.state.isVerticalSpliter && clientWidth) {
@@ -103,40 +78,41 @@ const MonitorPanel = createClass({
     });
   },
 
   render() {
     let {
       connector,
       isEmpty,
       networkDetailsOpen,
+      openLink,
       sourceMapService,
-      openLink
     } = this.props;
 
     let initialWidth = Services.prefs.getIntPref(
         "devtools.netmonitor.panes-network-details-width");
     let initialHeight = Services.prefs.getIntPref(
         "devtools.netmonitor.panes-network-details-height");
+
     return (
       div({ className: "monitor-panel" },
         Toolbar(),
         SplitBox({
           className: "devtools-responsive-container",
           initialWidth: `${initialWidth}px`,
           initialHeight: `${initialHeight}px`,
           minSize: "50px",
           maxSize: "80%",
           splitterSize: "1px",
           startPanel: RequestList({ isEmpty, connector }),
           endPanel: networkDetailsOpen && NetworkDetailsPanel({
             ref: "endPanel",
             connector,
+            openLink,
             sourceMapService,
-            openLink,
           }),
           endPanelCollapsed: !networkDetailsOpen,
           endPanelControl: true,
           vert: this.state.isVerticalSpliter,
         }),
       )
     );
   }
--- a/devtools/client/netmonitor/src/components/ParamsPanel.js
+++ b/devtools/client/netmonitor/src/components/ParamsPanel.js
@@ -1,22 +1,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/. */
 
 "use strict";
 
 const {
+  createClass,
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { L10N } = require("../utils/l10n");
 const { getUrlQuery, parseQueryString, parseFormData } = require("../utils/request-utils");
 const { sortObjectKeys } = require("../utils/sort-utils");
+const { updateFormDataSections } = require("../utils/request-utils");
+const Actions = require("../actions/index");
 
 // Components
 const PropertiesView = createFactory(require("./PropertiesView"));
 
 const { div } = DOM;
 
 const JSON_SCOPE_NAME = L10N.getStr("jsonScopeName");
 const PARAMS_EMPTY_TEXT = L10N.getStr("paramsEmptyText");
@@ -26,93 +30,106 @@ const PARAMS_POST_PAYLOAD = L10N.getStr(
 const PARAMS_QUERY_STRING = L10N.getStr("paramsQueryString");
 const SECTION_NAMES = [
   JSON_SCOPE_NAME,
   PARAMS_FORM_DATA,
   PARAMS_POST_PAYLOAD,
   PARAMS_QUERY_STRING,
 ];
 
-/*
+/**
  * Params panel component
  * Displays the GET parameters and POST data of a request
  */
-function ParamsPanel({
-  openLink,
-  request,
-}) {
-  let {
-    formDataSections,
-    mimeType,
-    requestPostData,
-    url,
-  } = request;
-  let postData = requestPostData ? requestPostData.postData.text : null;
-  let query = getUrlQuery(url);
+const ParamsPanel = createClass({
+  displayName: "ParamsPanel",
+
+  propTypes: {
+    connector: PropTypes.object.isRequired,
+    openLink: PropTypes.func,
+    request: PropTypes.object.isRequired,
+    updateRequest: PropTypes.func.isRequired,
+  },
+
+  componentDidMount() {
+    updateFormDataSections(this.props);
+  },
+
+  componentWillReceiveProps(nextProps) {
+    updateFormDataSections(nextProps);
+  },
+
+  render() {
+    let {
+      openLink,
+      request
+    } = this.props;
+    let {
+      formDataSections,
+      mimeType,
+      requestPostData,
+      url,
+    } = request;
+    let postData = requestPostData ? requestPostData.postData.text : null;
+    let query = getUrlQuery(url);
+
+    if (!formDataSections && !postData && !query) {
+      return div({ className: "empty-notice" },
+        PARAMS_EMPTY_TEXT
+      );
+    }
+
+    let object = {};
+    let json;
 
-  if (!formDataSections && !postData && !query) {
-    return div({ className: "empty-notice" },
-      PARAMS_EMPTY_TEXT
+    // Query String section
+    if (query) {
+      object[PARAMS_QUERY_STRING] = getProperties(parseQueryString(query));
+    }
+
+    // Form Data section
+    if (formDataSections && formDataSections.length > 0) {
+      let sections = formDataSections.filter((str) => /\S/.test(str)).join("&");
+      object[PARAMS_FORM_DATA] = getProperties(parseFormData(sections));
+    }
+
+    // Request payload section
+    if (formDataSections && formDataSections.length === 0 && postData) {
+      try {
+        json = JSON.parse(postData);
+      } catch (error) {
+        // Continue regardless of parsing error
+      }
+
+      if (json) {
+        object[JSON_SCOPE_NAME] = sortObjectKeys(json);
+      } else {
+        object[PARAMS_POST_PAYLOAD] = {
+          EDITOR_CONFIG: {
+            text: postData,
+            mode: mimeType.replace(/;.+/, ""),
+          },
+        };
+      }
+    } else {
+      postData = "";
+    }
+
+    return (
+      div({ className: "panel-container" },
+        PropertiesView({
+          object,
+          filterPlaceHolder: PARAMS_FILTER_TEXT,
+          sectionNames: SECTION_NAMES,
+          openLink,
+        })
+      )
     );
   }
-
-  let object = {};
-  let json;
-
-  // Query String section
-  if (query) {
-    object[PARAMS_QUERY_STRING] = getProperties(parseQueryString(query));
-  }
-
-  // Form Data section
-  if (formDataSections && formDataSections.length > 0) {
-    let sections = formDataSections.filter((str) => /\S/.test(str)).join("&");
-    object[PARAMS_FORM_DATA] = getProperties(parseFormData(sections));
-  }
-
-  // Request payload section
-  if (formDataSections && formDataSections.length === 0 && postData) {
-    try {
-      json = JSON.parse(postData);
-    } catch (error) {
-      // Continue regardless of parsing error
-    }
-
-    if (json) {
-      object[JSON_SCOPE_NAME] = sortObjectKeys(json);
-    } else {
-      object[PARAMS_POST_PAYLOAD] = {
-        EDITOR_CONFIG: {
-          text: postData,
-          mode: mimeType.replace(/;.+/, ""),
-        },
-      };
-    }
-  } else {
-    postData = "";
-  }
-
-  return (
-    div({ className: "panel-container" },
-      PropertiesView({
-        object,
-        filterPlaceHolder: PARAMS_FILTER_TEXT,
-        sectionNames: SECTION_NAMES,
-        openLink,
-      })
-    )
-  );
-}
-
-ParamsPanel.displayName = "ParamsPanel";
-
-ParamsPanel.propTypes = {
-  request: PropTypes.object.isRequired,
-  openLink: PropTypes.func,
-};
+});
 
 /**
  * Mapping array to dict for TreeView usage.
  * Since TreeView only support Object(dict) format.
  * This function also deal with duplicate key case
  * (for multiple selection and query params with same keys)
  *
  * @param {Object[]} arr - key-value pair array like query or form params
@@ -128,9 +145,13 @@ function getProperties(arr) {
       map[obj.name].push(obj.value);
     } else {
       map[obj.name] = obj.value;
     }
     return map;
   }, {}));
 }
 
-module.exports = ParamsPanel;
+module.exports = connect(null,
+  (dispatch) => ({
+    updateRequest: (id, data, batch) => dispatch(Actions.updateRequest(id, data, batch)),
+  }),
+)(ParamsPanel);
--- a/devtools/client/netmonitor/src/components/TabboxPanel.js
+++ b/devtools/client/netmonitor/src/components/TabboxPanel.js
@@ -67,17 +67,17 @@ function TabboxPanel({
         title: COOKIES_TITLE,
       },
         CookiesPanel({ request, openLink }),
       ),
       TabPanel({
         id: PANELS.PARAMS,
         title: PARAMS_TITLE,
       },
-        ParamsPanel({ request, openLink }),
+        ParamsPanel({ connector, openLink, request }),
       ),
       TabPanel({
         id: PANELS.RESPONSE,
         title: RESPONSE_TITLE,
       },
         ResponsePanel({ request, openLink }),
       ),
       TabPanel({
@@ -86,17 +86,17 @@ function TabboxPanel({
       },
         TimingsPanel({ request }),
       ),
       request.cause && request.cause.stacktrace && request.cause.stacktrace.length > 0 &&
       TabPanel({
         id: PANELS.STACK_TRACE,
         title: STACK_TRACE_TITLE,
       },
-        StackTracePanel({ request, sourceMapService, openLink, connector }),
+        StackTracePanel({ connector, openLink, request, sourceMapService }),
       ),
       request.securityState && request.securityState !== "insecure" &&
       TabPanel({
         id: PANELS.SECURITY,
         title: SECURITY_TITLE,
       },
         SecurityPanel({ request, openLink }),
       ),
--- a/devtools/client/netmonitor/src/reducers/requests.js
+++ b/devtools/client/netmonitor/src/reducers/requests.js
@@ -1,27 +1,29 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const I = require("devtools/client/shared/vendor/immutable");
-const { getUrlDetails } = require("../utils/request-utils");
+const {
+  getUrlDetails,
+  processNetworkUpdates,
+} = require("../utils/request-utils");
 const {
   ADD_REQUEST,
   CLEAR_REQUESTS,
   CLONE_SELECTED_REQUEST,
   OPEN_NETWORK_DETAILS,
   REMOVE_SELECTED_CUSTOM_REQUEST,
   SELECT_REQUEST,
   SEND_CUSTOM_REQUEST,
   TOGGLE_RECORDING,
   UPDATE_REQUEST,
-  UPDATE_PROPS,
 } = require("../constants");
 
 const Request = I.Record({
   id: null,
   // Set to true in case of a request that's being edited as part of "edit and resend"
   isCustom: false,
   // Request properties - at the beginning, they are unknown and are gradually filled in
   startedMillis: undefined,
@@ -185,40 +187,18 @@ function requestsReducer(state = new Req
       let { requests, lastEndedMillis } = state;
 
       let updatedRequest = requests.get(action.id);
       if (!updatedRequest) {
         return state;
       }
 
       updatedRequest = updatedRequest.withMutations(request => {
-        for (let [key, value] of Object.entries(action.data)) {
-          if (!UPDATE_PROPS.includes(key)) {
-            continue;
-          }
-
-          request[key] = value;
-
-          switch (key) {
-            case "url":
-              // Compute the additional URL details
-              request.urlDetails = getUrlDetails(value);
-              break;
-            case "totalTime":
-              request.endedMillis = request.startedMillis + value;
-              lastEndedMillis = Math.max(lastEndedMillis, request.endedMillis);
-              break;
-            case "requestPostData":
-              request.requestHeadersFromUploadStream = {
-                headers: [],
-                headersSize: 0,
-              };
-              break;
-          }
-        }
+        let values = processNetworkUpdates(action.data);
+        request = Object.assign(request, values);
       });
 
       return state.withMutations(st => {
         st.requests = requests.set(updatedRequest.id, updatedRequest);
         st.lastEndedMillis = lastEndedMillis;
       });
     }
 
--- a/devtools/client/netmonitor/src/utils/request-utils.js
+++ b/devtools/client/netmonitor/src/utils/request-utils.js
@@ -1,16 +1,20 @@
 /* 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/. */
 
 /* eslint-disable mozilla/reject-some-requires */
 
 "use strict";
 
+const {
+  UPDATE_PROPS,
+} = require("devtools/client/netmonitor/src/constants");
+
 const CONTENT_MIME_TYPE_ABBREVIATIONS = {
   "ecmascript": "js",
   "javascript": "js",
   "x-javascript": "js"
 };
 
 /**
  * Extracts any urlencoded form data sections (e.g. "?foo=bar&baz=42") from a
@@ -384,16 +388,79 @@ function getResponseHeader(item, header)
   for (let responseHeader of responseHeaders.headers) {
     if (responseHeader.name.toLowerCase() == header) {
       return responseHeader.value;
     }
   }
   return null;
 }
 
+/**
+ * Extracts any urlencoded form data sections from a POST request.
+ */
+function updateFormDataSections(props) {
+  let {
+    connector,
+    request = {},
+    updateRequest,
+  } = props;
+  let {
+    formDataSections,
+    requestHeaders,
+    requestHeadersFromUploadStream,
+    requestPostData,
+  } = request;
+
+  if (!formDataSections && requestHeaders &&
+      requestHeadersFromUploadStream && requestPostData) {
+    getFormDataSections(
+      requestHeaders,
+      requestHeadersFromUploadStream,
+      requestPostData,
+      connector.getLongString,
+    ).then((newFormDataSections) => {
+      updateRequest(
+        request.id,
+        { formDataSections: newFormDataSections },
+        true,
+      );
+    });
+  }
+}
+
+/**
+ * This helper function is used for additional processing of
+ * incoming network update packets. It's used by Network and
+ * Console panel reducers.
+ */
+function processNetworkUpdates(request) {
+  let result = {};
+  for (let [key, value] of Object.entries(request)) {
+    if (UPDATE_PROPS.includes(key)) {
+      result[key] = value;
+
+      switch (key) {
+        case "securityInfo":
+          result.securityState = value.state;
+          break;
+        case "totalTime":
+          result.totalTime = request.totalTime;
+          break;
+        case "requestPostData":
+          result.requestHeadersFromUploadStream = {
+            headers: [],
+            headersSize: 0,
+          };
+          break;
+      }
+    }
+  }
+  return result;
+}
+
 module.exports = {
   decodeUnicodeBase64,
   getFormDataSections,
   fetchHeaders,
   formDataURI,
   writeHeaderText,
   decodeUnicodeUrl,
   getAbbreviatedMimeType,
@@ -406,11 +473,13 @@ module.exports = {
   getUrlBaseNameWithQuery,
   getUrlDetails,
   getUrlHost,
   getUrlHostName,
   getUrlQuery,
   getUrlScheme,
   parseQueryString,
   parseFormData,
+  updateFormDataSections,
+  processNetworkUpdates,
   propertiesEqual,
   ipToLong,
 };
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -441,17 +441,17 @@ html .jsterm-stack-node {
 
 textarea.jsterm-input-node,
 textarea.jsterm-complete-node {
   width: 100%;
   border: none;
   margin: 0;
   background-color: transparent;
   resize: none;
-  font-size: var(--theme-toolbar-font-size);
+  font-size: inherit;
   line-height: 16px;
   overflow-x: hidden;
   /* Set padding for console input on textarea to make sure it is included in
      scrollHeight that is used when resizing JSTerminal's input. */
   padding: 4px 0;
   padding-inline-start: 20px;
 }
 
@@ -1128,18 +1128,18 @@ a.learn-more-link.webconsole-learn-more-
   flex: 1;
   direction: ltr;
   overflow: auto;
   -moz-user-select: text;
   position: relative;
 }
 
 html,
-body,
-#app-wrapper {
+body {
+  display: block !important; /* Until Bug 1409413 removes the accidental display: flex */
   height: 100%;
   margin: 0;
   padding: 0;
 }
 
 body {
   overflow: hidden;
 }
--- a/devtools/client/webconsole/new-console-output/components/message-types/NetworkEventMessage.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/NetworkEventMessage.js
@@ -103,17 +103,19 @@ function NetworkEventMessage({
   // API consumed by Net monitor UI components. Most of the method
   // are not needed in context of the Console panel (atm) and thus
   // let's just provide empty implementation.
   // Individual methods might be implemented step by step as needed.
   let connector = {
     viewSourceInDebugger: (url, line) => {
       serviceContainer.onViewSourceInDebugger({url, line});
     },
-    getLongString: () => {},
+    getLongString: (grip) => {
+      return serviceContainer.getLongString(grip);
+    },
     getTabTarget: () => {},
     getNetworkRequest: () => {},
     sendHTTPRequest: () => {},
     setPreferences: () => {},
     triggerActivity: () => {},
   };
 
   // Only render the attachment if the network-event is
--- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -69,32 +69,37 @@ NewConsoleOutputWrapper.prototype = {
       let selection = this.document.defaultView.getSelection();
       if (selection && !selection.isCollapsed) {
         return;
       }
 
       this.jsterm.focus();
     });
 
+    let { hud } = this.jsterm;
+
     const serviceContainer = {
       attachRefToHud,
       emitNewMessage: (node, messageId, timeStamp) => {
         this.jsterm.hud.emit("new-messages", new Set([{
           node,
           messageId,
           timeStamp,
         }]));
       },
-      hudProxy: this.jsterm.hud.proxy,
+      hudProxy: hud.proxy,
       openLink: url => {
-        this.jsterm.hud.owner.openLink(url);
+        hud.owner.openLink(url);
       },
       createElement: nodename => {
         return this.document.createElement(nodename);
       },
+      getLongString: (grip) => {
+        return hud.proxy.webConsoleClient.getString(grip);
+      },
     };
 
     // Set `openContextMenu` this way so, `serviceContainer` variable
     // is available in the current scope and we can pass it into
     // `createContextMenu` method.
     serviceContainer.openContextMenu = (e, message) => {
       let { screenX, screenY, target } = e;
 
@@ -228,18 +233,25 @@ NewConsoleOutputWrapper.prototype = {
   dispatchTimestampsToggle: function (enabled) {
     store.dispatch(actions.timestampsToggle(enabled));
   },
 
   dispatchMessageUpdate: function (message, res) {
     // network-message-updated will emit when all the update message arrives.
     // Since we can't ensure the order of the network update, we check
     // that networkInfo.updates has all we need.
+    // Note that 'requestPostData' is sent only for POST requests, so we need
+    // to count with that.
     const NUMBER_OF_NETWORK_UPDATE = 8;
-    if (res.networkInfo.updates.length === NUMBER_OF_NETWORK_UPDATE) {
+    let expectedLength = NUMBER_OF_NETWORK_UPDATE;
+    if (res.networkInfo.updates.indexOf("requestPostData") != -1) {
+      expectedLength++;
+    }
+
+    if (res.networkInfo.updates.length === expectedLength) {
       this.batchedMessageUpdates({ res, message });
     }
   },
 
   dispatchRequestUpdate: function (id, data) {
     this.batchedRequestUpdates({ id, data });
   },
 
--- a/devtools/client/webconsole/new-console-output/reducers/messages.js
+++ b/devtools/client/webconsole/new-console-output/reducers/messages.js
@@ -18,19 +18,23 @@ const {
   FILTERS,
   MESSAGE_TYPE,
   MESSAGE_SOURCE,
 } = constants;
 const { getGripPreviewItems } = require("devtools/client/shared/components/reps/reps");
 const { getSourceNames } = require("devtools/client/shared/source-utils");
 
 const {
-  UPDATE_PROPS
+  UPDATE_REQUEST,
 } = require("devtools/client/netmonitor/src/constants");
 
+const {
+  processNetworkUpdates,
+} = require("devtools/client/netmonitor/src/utils/request-utils");
+
 const MessageState = Immutable.Record({
   // List of all the messages added to the console.
   messagesById: Immutable.OrderedMap(),
   // Array of the visible messages.
   visibleMessages: [],
   // Object for the filtered messages.
   filteredMessagesCount: getDefaultFiltersCounter(),
   // List of the message ids which are opened.
@@ -269,44 +273,24 @@ function messages(state = new MessageSta
     case constants.NETWORK_MESSAGE_UPDATE:
       return state.set(
         "networkMessagesUpdateById",
         Object.assign({}, networkMessagesUpdateById, {
           [action.message.id]: action.message
         })
       );
 
+    case UPDATE_REQUEST:
     case constants.NETWORK_UPDATE_REQUEST: {
       let request = networkMessagesUpdateById[action.id];
       if (!request) {
         return state;
       }
 
-      let values = {};
-      for (let [key, value] of Object.entries(action.data)) {
-        if (UPDATE_PROPS.includes(key)) {
-          values[key] = value;
-
-          switch (key) {
-            case "securityInfo":
-              values.securityState = value.state;
-              break;
-            case "totalTime":
-              values.totalTime = request.totalTime;
-              break;
-            case "requestPostData":
-              values.requestHeadersFromUploadStream = {
-                headers: [],
-                headersSize: 0,
-              };
-              break;
-          }
-        }
-      }
-
+      let values = processNetworkUpdates(action.data);
       newState = state.set(
         "networkMessagesUpdateById",
         Object.assign({}, networkMessagesUpdateById, {
           [action.id]: Object.assign({}, request, values)
         })
       );
 
       return newState;
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -190,39 +190,35 @@ skip-if = true # Bug 1406060
 skip-if = true # Bug 1406060
 [browser_console_webconsole_ctrlw_close_tab.js]
 skip-if = true # Bug 1406060
 [browser_console_webconsole_iframe_messages.js]
 skip-if = true # Bug 1406060
 [browser_console_webconsole_private_browsing.js]
 skip-if = true #	Bug 1403188
 # old console skip-if = e10s # Bug 1042253 - webconsole e10s tests
+[browser_jsterm_add_edited_input_to_history.js]
+[browser_jsterm_autocomplete-properties-with-non-alphanumeric-names.js]
 [browser_jsterm_copy_command.js]
 skip-if = true
 subsuite = clipboard
 # old console skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_jsterm_dollar.js]
 [browser_jsterm_history_persist.js]
 [browser_jsterm_inspect.js]
 [browser_jsterm_no_autocompletion_on_defined_variables.js]
 [browser_jsterm_no_input_and_tab_key_pressed.js]
 [browser_jsterm_no_input_change_and_tab_key_pressed.js]
 [browser_netmonitor_shows_reqs_in_webconsole.js]
-[browser_webconsole.js]
-skip-if = true #	Bug 1404829
-[browser_webconsole_add_edited_input_to_history.js]
-skip-if = true # Bug 1408915
 [browser_webconsole_allow_mixedcontent_securityerrors.js]
 tags = mcb
 skip-if = true #	Bug 1403452
 # old console skip-if = (os == 'win' && bits == 64) # Bug 1390001
 [browser_webconsole_assert.js]
 skip-if = true #	Bug 1403458
-[browser_webconsole_autocomplete-properties-with-non-alphanumeric-names.js]
-skip-if = true # Bug 1408916
 [browser_webconsole_autocomplete_JSTerm_helpers.js]
 skip-if = true # Bug 1408917
 [browser_webconsole_autocomplete_accessibility.js]
 skip-if = true # Bug 1408918
 [browser_webconsole_autocomplete_and_selfxss.js]
 subsuite = clipboard
 skip-if = true #	Bug 1404850
 # old console skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
rename from devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_add_edited_input_to_history.js
rename to devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_add_edited_input_to_history.js
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_add_edited_input_to_history.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_add_edited_input_to_history.js
@@ -7,26 +7,27 @@
 // lost after navigating in history.
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=817834
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 817834";
 
 add_task(function* () {
-  yield loadTab(TEST_URI);
-
-  let hud = yield openConsole();
-
+  let hud = yield openNewTabAndConsole(TEST_URI);
+  // Clearing history that might have been set in previous tests.
+  yield hud.jsterm.clearHistory();
   testEditedInputHistory(hud);
+  yield hud.jsterm.clearHistory();
 });
 
-function testEditedInputHistory(HUD) {
-  let jsterm = HUD.jsterm;
+function testEditedInputHistory(hud) {
+  let jsterm = hud.jsterm;
   let inputNode = jsterm.inputNode;
+
   ok(!jsterm.getInputValue(), "jsterm.getInputValue() is empty");
   is(inputNode.selectionStart, 0);
   is(inputNode.selectionEnd, 0);
 
   jsterm.setInputValue('"first item"');
   EventUtils.synthesizeKey("VK_UP", {});
   is(jsterm.getInputValue(), '"first item"', "null test history up");
   EventUtils.synthesizeKey("VK_DOWN", {});
rename from devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete-properties-with-non-alphanumeric-names.js
rename to devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete-properties-with-non-alphanumeric-names.js
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete-properties-with-non-alphanumeric-names.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete-properties-with-non-alphanumeric-names.js
@@ -2,46 +2,38 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test that properties starting with underscores or dollars can be
 // autocompleted (bug 967468).
-
-add_task(function* () {
-  const TEST_URI = "data:text/html;charset=utf8,test autocompletion with " +
-                   "$ or _";
-  yield loadTab(TEST_URI);
-
-  function* autocomplete(term) {
-    let deferred = defer();
+const TEST_URI = "data:text/html;charset=utf8,test autocompletion with $ or _";
 
-    jsterm.setInputValue(term);
-    jsterm.complete(jsterm.COMPLETE_HINT_ONLY, deferred.resolve);
-
-    yield deferred.promise;
+add_task(async function () {
+  let { jsterm } = await openNewTabAndConsole(TEST_URI);
 
-    ok(popup.itemCount > 0,
-       "There's " + popup.itemCount + " suggestions for '" + term + "'");
-  }
-
-  let { jsterm } = yield openConsole();
-  let popup = jsterm.autocompletePopup;
-
-  yield jsterm.execute("var testObject = {$$aaab: '', $$aaac: ''}");
+  await jsterm.execute("var testObject = {$$aaab: '', $$aaac: ''}");
 
   // Should work with bug 967468.
-  yield autocomplete("Object.__d");
-  yield autocomplete("testObject.$$a");
+  await testAutocomplete(jsterm, "Object.__d");
+  await testAutocomplete(jsterm, "testObject.$$a");
 
   // Here's when things go wrong in bug 967468.
-  yield autocomplete("Object.__de");
-  yield autocomplete("testObject.$$aa");
+  await testAutocomplete(jsterm, "Object.__de");
+  await testAutocomplete(jsterm, "testObject.$$aa");
 
   // Should work with bug 1207868.
-  yield jsterm.execute("let foobar = {a: ''}; const blargh = {a: 1};");
-  yield autocomplete("foobar");
-  yield autocomplete("blargh");
-  yield autocomplete("foobar.a");
-  yield autocomplete("blargh.a");
+  await jsterm.execute("let foobar = {a: ''}; const blargh = {a: 1};");
+  await testAutocomplete(jsterm, "foobar");
+  await testAutocomplete(jsterm, "blargh");
+  await testAutocomplete(jsterm, "foobar.a");
+  await testAutocomplete(jsterm, "blargh.a");
 });
+
+async function testAutocomplete(jsterm, inputString) {
+  jsterm.setInputValue(inputString);
+  await new Promise(resolve => jsterm.complete(jsterm.COMPLETE_HINT_ONLY, resolve));
+
+  let popup = jsterm.autocompletePopup;
+  ok(popup.itemCount > 0, `There's ${popup.itemCount} suggestions for '${inputString}'`);
+}
deleted file mode 100644
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole.js
+++ /dev/null
@@ -1,69 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// See Bug 611795.
-
-const TEST_URI = 'data:text/html;charset=utf-8,<div style="-moz-opacity:0;">' +
-                 'test repeated css warnings</div><p style="-moz-opacity:0">' +
-                 "hi</p>";
-var hud;
-
-/**
- * Unit test for bug 611795:
- * Repeated CSS messages get collapsed into one.
- */
-
-add_task(function* () {
-  yield loadTab(TEST_URI);
-
-  hud = yield openConsole();
-  hud.jsterm.clearOutput(true);
-
-  BrowserReload();
-  yield loadBrowser(gBrowser.selectedBrowser);
-
-  yield onContentLoaded();
-  yield testConsoleLogRepeats();
-
-  hud = null;
-});
-
-function onContentLoaded() {
-  let cssWarning = "Unknown property \u2018-moz-opacity\u2019.  Declaration dropped.";
-
-  return waitForMessages({
-    webconsole: hud,
-    messages: [{
-      text: cssWarning,
-      category: CATEGORY_CSS,
-      severity: SEVERITY_WARNING,
-      repeats: 2,
-    }],
-  });
-}
-
-function testConsoleLogRepeats() {
-  let jsterm = hud.jsterm;
-
-  jsterm.clearOutput();
-
-  jsterm.setInputValue("for (let i = 0; i < 10; ++i) console.log('this is a " +
-                       "line of reasonably long text that I will use to " +
-                       "verify that the repeated text node is of an " +
-                       "appropriate size.');");
-  jsterm.execute();
-
-  return waitForMessages({
-    webconsole: hud,
-    messages: [{
-      text: "this is a line of reasonably long text",
-      category: CATEGORY_WEBDEV,
-      severity: SEVERITY_LOG,
-      repeats: 10,
-    }],
-  });
-}
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_messages_expand.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_messages_expand.js
@@ -1,45 +1,49 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const TEST_URI = "data:text/html;charset=utf8,Test that clicking on a network message " +
-                 "in the console toggles the HTTP inspection.";
-
 const TEST_FILE = "test-network-request.html";
 const TEST_PATH = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/mochitest/";
+const TEST_URI = TEST_PATH + TEST_FILE;
 
 const NET_PREF = "devtools.webconsole.filter.net";
 const XHR_PREF = "devtools.webconsole.filter.netxhr";
 
-Services.prefs.setBoolPref(NET_PREF, true);
+Services.prefs.setBoolPref(NET_PREF, false);
 Services.prefs.setBoolPref(XHR_PREF, true);
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref(NET_PREF);
   Services.prefs.clearUserPref(XHR_PREF);
 });
 
 add_task(async function task() {
   const hud = await openNewTabAndConsole(TEST_URI);
 
   const currentTab = gBrowser.selectedTab;
   let target = TargetFactory.forTab(currentTab);
   let toolbox = gDevTools.getToolbox(target);
 
-  const documentUrl = TEST_PATH + TEST_FILE;
-  await loadDocument(documentUrl);
-  info("Document loaded.");
+  // Fire an XHR POST request.
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function () {
+    content.wrappedJSObject.testXhrPost();
+  });
 
-  let messageNode = await waitFor(() => findMessage(hud, documentUrl));
+  info("XHR executed");
+
+  await waitForRequestUpdates(toolbox);
+
+  let xhrUrl = TEST_PATH + "test-data.json";
+  let messageNode = await waitFor(() => findMessage(hud, xhrUrl));
   let urlNode = messageNode.querySelector(".url");
   info("Network message found.");
 
-  let updates = waitForNetworkUpdates(toolbox);
+  let updates = waitForPayloadReady(toolbox);
 
   // Expand network log
   urlNode.click();
 
   await updates;
   await testNetworkMessage(messageNode);
 });
 
@@ -56,44 +60,61 @@ async function testNetworkMessage(messag
   ok(responseTab, "Response tab is available");
   ok(timingsTab, "Timings tab is available");
 
   // Headers tab should be selected by default, so just check its content.
   let headersContent = messageNode.querySelector(
     "#headers-panel .headers-overview");
   ok(headersContent, "Headers content is available");
 
+  // Select Params tab and check the content. CodeMirror initialization
+  // is delayed to prevent UI freeze, so wait for a little while.
+  paramsTab.click();
+  let paramsPanel = messageNode.querySelector("#params-panel");
+  await waitForSourceEditor(paramsPanel);
+  let paramsContent = messageNode.querySelector(
+    "#params-panel .panel-container .CodeMirror");
+  ok(paramsContent, "Params content is available");
+  ok(paramsContent.textContent.includes("Hello world!"), "Post body is correct");
+
   // Select Response tab and check the content. CodeMirror initialization
-  // is delayed  to prevent UI freeze, so wait for a little while.
+  // is delayed, so again wait for a little while.
   responseTab.click();
-  await waitForSourceEditor(messageNode);
+  let responsePanel = messageNode.querySelector("#response-panel");
+  await waitForSourceEditor(responsePanel);
   let responseContent = messageNode.querySelector(
     "#response-panel .editor-row-container .CodeMirror");
   ok(responseContent, "Response content is available");
   ok(responseContent.textContent, "Response text is available");
 
   // Select Timings tab and check the content.
   timingsTab.click();
   let timingsContent = messageNode.querySelector(
     "#timings-panel .timings-container .timings-label");
   ok(timingsContent, "Timings content is available");
   ok(timingsContent.textContent, "Timings text is available");
 }
 
-async function waitForNetworkUpdates(toolbox) {
-  let panel = toolbox.getCurrentPanel();
-  let hud = panel.hud;
-  let ui = hud.ui;
-
+async function waitForPayloadReady(toolbox) {
+  let {ui} = toolbox.getCurrentPanel().hud;
   return new Promise(resolve => {
     ui.jsterm.hud.on("network-request-payload-ready", () => {
       info("network-request-payload-ready received");
       resolve();
     });
   });
 }
 
-async function waitForSourceEditor(messageNode) {
+async function waitForSourceEditor(panel) {
   return waitUntil(() => {
-    return !!messageNode.querySelector(
-      "#response-panel .editor-row-container .CodeMirror");
+    return !!panel.querySelector(".CodeMirror");
   });
 }
+
+async function waitForRequestUpdates(toolbox) {
+  let {ui} = toolbox.getCurrentPanel().hud;
+  return new Promise(resolve => {
+    ui.jsterm.hud.on("network-message-updated", () => {
+      info("network-message-updated received");
+      resolve();
+    });
+  });
+}
--- a/devtools/client/webconsole/new-console-output/test/store/messages.test.js
+++ b/devtools/client/webconsole/new-console-output/test/store/messages.test.js
@@ -40,17 +40,17 @@ describe("Message reducer:", () => {
       const message = stubPreparedMessages.get("console.log('foobar', 'test')");
       dispatch(actions.messageAdd(packet));
 
       const messages = getAllMessagesById(getState());
 
       expect(messages.first()).toEqual(message);
     });
 
-    it("increments repeat on a repeating message", () => {
+    it("increments repeat on a repeating log message", () => {
       const key1 = "console.log('foobar', 'test')";
       const { dispatch, getState } = setupStore([key1, key1]);
 
       const packet = clonePacket(stubPackets.get(key1));
 
       // Repeat ID must be the same even if the timestamp is different.
       packet.message.timeStamp = 1;
       dispatch(actions.messageAdd(packet));
@@ -60,16 +60,56 @@ describe("Message reducer:", () => {
       const messages = getAllMessagesById(getState());
 
       expect(messages.size).toBe(1);
 
       const repeat = getAllRepeatById(getState());
       expect(repeat[messages.first().id]).toBe(4);
     });
 
+    it("increments repeat on a repeating css message", () => {
+      const key1 = "Unknown property ‘such-unknown-property’.  Declaration dropped.";
+      const { dispatch, getState } = setupStore([key1, key1]);
+
+      const packet = clonePacket(stubPackets.get(key1));
+
+      // Repeat ID must be the same even if the timestamp is different.
+      packet.pageError.timeStamp = 1;
+      dispatch(actions.messageAdd(packet));
+      packet.pageError.timeStamp = 2;
+      dispatch(actions.messageAdd(packet));
+
+      const messages = getAllMessagesById(getState());
+
+      expect(messages.size).toBe(1);
+
+      const repeat = getAllRepeatById(getState());
+      expect(repeat[messages.first().id]).toBe(4);
+    });
+
+    it("increments repeat on a repeating error message", () => {
+      const key1 = "ReferenceError: asdf is not defined";
+      const { dispatch, getState } = setupStore([key1, key1]);
+
+      const packet = clonePacket(stubPackets.get(key1));
+
+      // Repeat ID must be the same even if the timestamp is different.
+      packet.pageError.timeStamp = 1;
+      dispatch(actions.messageAdd(packet));
+      packet.pageError.timeStamp = 2;
+      dispatch(actions.messageAdd(packet));
+
+      const messages = getAllMessagesById(getState());
+
+      expect(messages.size).toBe(1);
+
+      const repeat = getAllRepeatById(getState());
+      expect(repeat[messages.first().id]).toBe(4);
+    });
+
     it("does not increment repeat after closing a group", () => {
       const logKey = "console.log('foobar', 'test')";
       const { getState } = setupStore([
         logKey,
         logKey,
         "console.group('bar')",
         logKey,
         logKey,
--- a/devtools/client/webconsole/test/browser_webconsole_view_source.js
+++ b/devtools/client/webconsole/test/browser_webconsole_view_source.js
@@ -9,26 +9,16 @@
 // order to have it opened in the standard View Source window.
 
 "use strict";
 
 const TEST_URI = "https://example.com/browser/devtools/client/webconsole/" +
                  "test/test-mixedcontent-securityerrors.html";
 
 add_task(function* () {
-  yield actuallyTest();
-});
-
-add_task(function* () {
-  Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false);
-  yield actuallyTest();
-  Services.prefs.clearUserPref("devtools.debugger.new-debugger-frontend");
-});
-
-var actuallyTest = Task.async(function*() {
   yield loadTab(TEST_URI);
   let hud = yield openConsole(null);
   info("console opened");
 
   let [result] = yield waitForMessages({
     webconsole: hud,
     messages: [{
       text: "Blocked loading mixed active content",
--- a/dom/base/ShadowRoot.cpp
+++ b/dom/base/ShadowRoot.cpp
@@ -74,20 +74,20 @@ ShadowRoot::ShadowRoot(Element* aElement
   // Add the ShadowRoot as a mutation observer on the host to watch
   // for mutations because the insertion points in this ShadowRoot
   // may need to be updated when the host children are modified.
   GetHost()->AddMutationObserver(this);
 }
 
 ShadowRoot::~ShadowRoot()
 {
-  if (GetHost()) {
-    // mPoolHost may have been unlinked or a new ShadowRoot may have been
-    // creating, making this one obsolete.
-    GetHost()->RemoveMutationObserver(this);
+  if (auto* host = GetHost()) {
+    // mHost may have been unlinked or a new ShadowRoot may have been
+    // created, making this one obsolete.
+    host->RemoveMutationObserver(this);
   }
 
   UnsetFlags(NODE_IS_IN_SHADOW_TREE);
 
   // nsINode destructor expects mSubtreeRoot == this.
   SetSubtreeRootPointer(this);
 }
 
@@ -109,18 +109,17 @@ ShadowRoot::FromNode(nsINode* aNode)
   return nullptr;
 }
 
 void
 ShadowRoot::StyleSheetChanged()
 {
   mProtoBinding->FlushSkinSheets();
 
-  nsIPresShell* shell = OwnerDoc()->GetShell();
-  if (shell) {
+  if (nsIPresShell* shell = OwnerDoc()->GetShell()) {
     OwnerDoc()->BeginUpdate(UPDATE_STYLE);
     shell->RecordShadowStyleChange(this);
     OwnerDoc()->EndUpdate(UPDATE_STYLE);
   }
 }
 
 void
 ShadowRoot::InsertSheet(StyleSheet* aSheet,
@@ -234,162 +233,178 @@ ShadowRoot::RemoveInsertionPoint(HTMLCon
   mInsertionPoints.RemoveElement(aInsertionPoint);
 }
 
 void
 ShadowRoot::RemoveDestInsertionPoint(nsIContent* aInsertionPoint,
                                      nsTArray<nsIContent*>& aDestInsertionPoints)
 {
   // Remove the insertion point from the destination insertion points.
-  // Also remove all succeeding insertion points because it is no longer
-  // possible for the content to be distributed into deeper node trees.
+  //
+  // Note that while it sounds tempting to just remove all the insertion points
+  // after it too, since they're usually after in tree position, it may not be
+  // the case when we're redistributing after new insertion points have been
+  // bound to the tree before aInsertionPoint, see bug 1409088.
   int32_t index = aDestInsertionPoints.IndexOf(aInsertionPoint);
 
   // It's possible that we already removed the insertion point while processing
-  // other insertion point removals.
+  // other insertion point removals / fallback content redistribution (which
+  // does DestInsertionPoints().Clear()).
   if (index >= 0) {
-    aDestInsertionPoints.SetLength(index);
+    aDestInsertionPoints.RemoveElementAt(index);
   }
 }
 
 void
+ShadowRoot::DistributionChanged()
+{
+  // FIXME(emilio): We could be more granular in a bunch of cases.
+  auto* host = GetHost();
+  if (!host || !host->IsInComposedDoc()) {
+    return;
+  }
+
+  auto* shell = OwnerDoc()->GetShell();
+  if (!shell) {
+    return;
+  }
+
+  // FIXME(emilio): Rename this to DestroyFramesForAndRestyle?
+  shell->DestroyFramesFor(host);
+}
+
+const HTMLContentElement*
 ShadowRoot::DistributeSingleNode(nsIContent* aContent)
 {
   // Find the insertion point to which the content belongs.
-  HTMLContentElement* insertionPoint = nullptr;
-  for (uint32_t i = 0; i < mInsertionPoints.Length(); i++) {
-    if (mInsertionPoints[i]->Match(aContent)) {
-      if (mInsertionPoints[i]->MatchedNodes().Contains(aContent)) {
+  HTMLContentElement* foundInsertionPoint = nullptr;
+  for (HTMLContentElement* insertionPoint : mInsertionPoints) {
+    if (insertionPoint->Match(aContent)) {
+      if (insertionPoint->MatchedNodes().Contains(aContent)) {
         // Node is already matched into the insertion point. We are done.
-        return;
+        return insertionPoint;
       }
 
       // Matching may cause the insertion point to drop fallback content.
-      if (mInsertionPoints[i]->MatchedNodes().IsEmpty() &&
-          static_cast<nsINode*>(mInsertionPoints[i])->GetFirstChild()) {
+      if (insertionPoint->MatchedNodes().IsEmpty() &&
+          insertionPoint->HasChildren()) {
         // This match will cause the insertion point to drop all fallback
         // content and used matched nodes instead. Give up on the optimization
         // and just distribute all nodes.
         DistributeAllNodes();
-        return;
+        MOZ_ASSERT(insertionPoint->MatchedNodes().Contains(aContent));
+        return insertionPoint;
       }
-      insertionPoint = mInsertionPoints[i];
+      foundInsertionPoint = insertionPoint;
       break;
     }
   }
 
+  if (!foundInsertionPoint) {
+    return nullptr;
+  }
+
   // Find the index into the insertion point.
-  if (insertionPoint) {
-    nsCOMArray<nsIContent>& matchedNodes = insertionPoint->MatchedNodes();
-    // Find the appropriate position in the matched node list for the
-    // newly distributed content.
-    bool isIndexFound = false;
-    ExplicitChildIterator childIterator(GetHost());
-    for (uint32_t i = 0; i < matchedNodes.Length(); i++) {
-      // Seek through the host's explicit children until the inserted content
-      // is found or when the current matched node is reached.
-      if (childIterator.Seek(aContent, matchedNodes[i])) {
-        // aContent was found before the current matched node.
-        insertionPoint->InsertMatchedNode(i, aContent);
-        isIndexFound = true;
-        break;
-      }
-    }
-
-    if (!isIndexFound) {
-      // We have still not found an index in the insertion point,
-      // thus it must be at the end.
-      MOZ_ASSERT(childIterator.Seek(aContent, nullptr),
-                 "Trying to match a node that is not a candidate to be matched");
-      insertionPoint->AppendMatchedNode(aContent);
-    }
-
-    // Handle the case where the parent of the insertion point has a ShadowRoot.
-    // The node distributed into the insertion point must be reprojected to the
-    // insertion points of the parent's ShadowRoot.
-    ShadowRoot* parentShadow = insertionPoint->GetParent()->GetShadowRoot();
-    if (parentShadow) {
-      parentShadow->DistributeSingleNode(aContent);
+  nsCOMArray<nsIContent>& matchedNodes = foundInsertionPoint->MatchedNodes();
+  // Find the appropriate position in the matched node list for the
+  // newly distributed content.
+  bool isIndexFound = false;
+  ExplicitChildIterator childIterator(GetHost());
+  for (uint32_t i = 0; i < matchedNodes.Length(); i++) {
+    // Seek through the host's explicit children until the inserted content
+    // is found or when the current matched node is reached.
+    if (childIterator.Seek(aContent, matchedNodes[i])) {
+      // aContent was found before the current matched node.
+      foundInsertionPoint->InsertMatchedNode(i, aContent);
+      isIndexFound = true;
+      break;
     }
   }
+
+  if (!isIndexFound) {
+    // We have still not found an index in the insertion point,
+    // thus it must be at the end.
+    MOZ_ASSERT(childIterator.Seek(aContent, nullptr),
+               "Trying to match a node that is not a candidate to be matched");
+    foundInsertionPoint->AppendMatchedNode(aContent);
+  }
+
+  return foundInsertionPoint;
 }
 
-void
+const HTMLContentElement*
 ShadowRoot::RemoveDistributedNode(nsIContent* aContent)
 {
   // Find insertion point containing the content and remove the node.
-  for (uint32_t i = 0; i < mInsertionPoints.Length(); i++) {
-    if (mInsertionPoints[i]->MatchedNodes().Contains(aContent)) {
-      // Removing the matched node may cause the insertion point to use
-      // fallback content.
-      if (mInsertionPoints[i]->MatchedNodes().Length() == 1 &&
-          static_cast<nsINode*>(mInsertionPoints[i])->GetFirstChild()) {
-        // Removing the matched node will cause fallback content to be
-        // used instead. Give up optimization and distribute all nodes.
-        DistributeAllNodes();
-        return;
-      }
+  for (HTMLContentElement* insertionPoint : mInsertionPoints) {
+    if (!insertionPoint->MatchedNodes().Contains(aContent)) {
+      continue;
+    }
 
-      mInsertionPoints[i]->RemoveMatchedNode(aContent);
+    // Removing the matched node may cause the insertion point to use
+    // fallback content.
+    if (insertionPoint->MatchedNodes().Length() == 1 &&
+        insertionPoint->HasChildren()) {
+      // Removing the matched node will cause fallback content to be
+      // used instead. Give up optimization and distribute all nodes.
+      DistributeAllNodes();
+      return insertionPoint;
+    }
 
-      // Handle the case where the parent of the insertion point has a ShadowRoot.
-      // The removed node needs to be removed from the insertion points of the
-      // parent's ShadowRoot.
-      ShadowRoot* parentShadow = mInsertionPoints[i]->GetParent()->GetShadowRoot();
-      if (parentShadow) {
-        parentShadow->RemoveDistributedNode(aContent);
-      }
+    insertionPoint->RemoveMatchedNode(aContent);
+    return insertionPoint;
+  }
 
-      break;
-    }
-  }
+  return nullptr;
 }
 
 void
 ShadowRoot::DistributeAllNodes()
 {
   // Create node pool.
   nsTArray<nsIContent*> nodePool;
   ExplicitChildIterator childIterator(GetHost());
   for (nsIContent* content = childIterator.GetNextChild(); content;
        content = childIterator.GetNextChild()) {
     nodePool.AppendElement(content);
   }
 
   nsTArray<ShadowRoot*> shadowsToUpdate;
 
-  for (uint32_t i = 0; i < mInsertionPoints.Length(); i++) {
-    mInsertionPoints[i]->ClearMatchedNodes();
+  for (HTMLContentElement* insertionPoint : mInsertionPoints) {
+    insertionPoint->ClearMatchedNodes();
     // Assign matching nodes from node pool.
     for (uint32_t j = 0; j < nodePool.Length(); j++) {
-      if (mInsertionPoints[i]->Match(nodePool[j])) {
-        mInsertionPoints[i]->AppendMatchedNode(nodePool[j]);
+      if (insertionPoint->Match(nodePool[j])) {
+        insertionPoint->AppendMatchedNode(nodePool[j]);
         nodePool.RemoveElementAt(j--);
       }
     }
 
     // Keep track of instances where the content insertion point is distributed
     // (parent of insertion point has a ShadowRoot).
-    nsIContent* insertionParent = mInsertionPoints[i]->GetParent();
+    nsIContent* insertionParent = insertionPoint->GetParent();
     MOZ_ASSERT(insertionParent, "The only way for an insertion point to be in the"
                                 "mInsertionPoints array is to be a descendant of a"
                                 "ShadowRoot, in which case, it should have a parent");
 
     // If the parent of the insertion point has a ShadowRoot, the nodes distributed
     // to the insertion point must be reprojected to the insertion points of the
     // parent's ShadowRoot.
     ShadowRoot* parentShadow = insertionParent->GetShadowRoot();
     if (parentShadow && !shadowsToUpdate.Contains(parentShadow)) {
       shadowsToUpdate.AppendElement(parentShadow);
     }
   }
 
-  for (uint32_t i = 0; i < shadowsToUpdate.Length(); i++) {
-    shadowsToUpdate[i]->DistributeAllNodes();
+  for (ShadowRoot* shadow : shadowsToUpdate) {
+    shadow->DistributeAllNodes();
   }
+
+  DistributionChanged();
 }
 
 void
 ShadowRoot::GetInnerHTML(nsAString& aInnerHTML)
 {
   GetMarkup(false, aInnerHTML);
 }
 
@@ -440,115 +455,163 @@ ShadowRoot::StyleSheets()
 
 /**
  * Returns whether the web components pool population algorithm
  * on the host would contain |aContent|. This function ignores
  * insertion points in the pool, thus should only be used to
  * test nodes that have not yet been distributed.
  */
 bool
-ShadowRoot::IsPooledNode(nsIContent* aContent, nsIContent* aContainer,
-                         nsIContent* aHost)
+ShadowRoot::IsPooledNode(nsIContent* aContent) const
 {
   if (nsContentUtils::IsContentInsertionPoint(aContent)) {
     // Insertion points never end up in the pool.
     return false;
   }
 
-  if (aContainer == aHost &&
-      nsContentUtils::IsInSameAnonymousTree(aContainer, aContent)) {
+  auto* host = GetHost();
+  auto* container = aContent->GetParent();
+  if (container == host && !aContent->IsRootOfAnonymousSubtree()) {
     // Children of the host will end up in the pool. We check to ensure
     // that the content is in the same anonymous tree as the container
     // because anonymous content may report its container as the host
     // but it may not be in the host's child list.
     return true;
   }
 
-  if (aContainer) {
+  if (auto* content = HTMLContentElement::FromContentOrNull(container)) {
     // Fallback content will end up in pool if its parent is a child of the host.
-    HTMLContentElement* content = HTMLContentElement::FromContent(aContainer);
-    return content && content->IsInsertionPoint() &&
+    return content->IsInsertionPoint() &&
            content->MatchedNodes().IsEmpty() &&
-           aContainer->GetParentNode() == aHost;
+           container->GetParentNode() == host;
   }
 
   return false;
 }
 
 void
 ShadowRoot::AttributeChanged(nsIDocument* aDocument,
                              Element* aElement,
                              int32_t aNameSpaceID,
                              nsAtom* aAttribute,
                              int32_t aModType,
                              const nsAttrValue* aOldValue)
 {
-  if (!IsPooledNode(aElement, aElement->GetParent(), GetHost())) {
+  if (!IsPooledNode(aElement)) {
     return;
   }
 
   // Attributes may change insertion point matching, find its new distribution.
-  RemoveDistributedNode(aElement);
-  DistributeSingleNode(aElement);
+  //
+  // FIXME(emilio): What about state changes?
+  if (!RedistributeElement(aElement)) {
+    return;
+  }
+
+  if (!aElement->IsInComposedDoc()) {
+    return;
+  }
+
+  auto* shell = OwnerDoc()->GetShell();
+  if (!shell) {
+    return;
+  }
+
+  shell->DestroyFramesFor(aElement);
+}
+
+bool
+ShadowRoot::RedistributeElement(Element* aElement)
+{
+  auto* oldInsertionPoint = RemoveDistributedNode(aElement);
+  auto* newInsertionPoint = DistributeSingleNode(aElement);
+
+  if (oldInsertionPoint == newInsertionPoint) {
+    if (oldInsertionPoint) {
+      if (auto* shadow = oldInsertionPoint->GetParent()->GetShadowRoot()) {
+        return shadow->RedistributeElement(aElement);
+      }
+    }
+
+    return false;
+  }
+
+  while (oldInsertionPoint) {
+    // Handle the case where the parent of the insertion point has a ShadowRoot.
+    // The node distributed into the insertion point must be reprojected to the
+    // insertion points of the parent's ShadowRoot.
+    auto* shadow = oldInsertionPoint->GetParent()->GetShadowRoot();
+    if (!shadow) {
+      break;
+    }
+
+    oldInsertionPoint = shadow->RemoveDistributedNode(aElement);
+  }
+
+  while (newInsertionPoint) {
+    // Handle the case where the parent of the insertion point has a ShadowRoot.
+    // The node distributed into the insertion point must be reprojected to the
+    // insertion points of the parent's ShadowRoot.
+    auto* shadow = newInsertionPoint->GetParent()->GetShadowRoot();
+    if (!shadow) {
+      break;
+    }
+
+    newInsertionPoint = shadow->DistributeSingleNode(aElement);
+  }
+
+  return true;
 }
 
 void
 ShadowRoot::ContentAppended(nsIDocument* aDocument,
                             nsIContent* aContainer,
                             nsIContent* aFirstNewContent)
 {
-  if (mInsertionPointChanged) {
-    DistributeAllNodes();
-    mInsertionPointChanged = false;
-    return;
-  }
-
-  // Watch for new nodes added to the pool because the node
-  // may need to be added to an insertion point.
-  nsIContent* currentChild = aFirstNewContent;
-  while (currentChild) {
-    // Add insertion point to destination insertion points of fallback content.
-    if (nsContentUtils::IsContentInsertionPoint(aContainer)) {
-      HTMLContentElement* content = HTMLContentElement::FromContent(aContainer);
-      if (content && content->MatchedNodes().IsEmpty()) {
-        currentChild->DestInsertionPoints().AppendElement(aContainer);
-      }
-    }
-
-    if (IsPooledNode(currentChild, aContainer, GetHost())) {
-      DistributeSingleNode(currentChild);
-    }
-
-    currentChild = currentChild->GetNextSibling();
+  for (nsIContent* content = aFirstNewContent;
+       content;
+       content = content->GetNextSibling()) {
+    ContentInserted(aDocument, aContainer, aFirstNewContent);
   }
 }
 
 void
 ShadowRoot::ContentInserted(nsIDocument* aDocument,
                             nsIContent* aContainer,
                             nsIContent* aChild)
 {
   if (mInsertionPointChanged) {
     DistributeAllNodes();
     mInsertionPointChanged = false;
     return;
   }
 
+  // Add insertion point to destination insertion points of fallback content.
+  if (nsContentUtils::IsContentInsertionPoint(aContainer)) {
+    HTMLContentElement* content = HTMLContentElement::FromContent(aContainer);
+    if (content && content->MatchedNodes().IsEmpty()) {
+      aChild->DestInsertionPoints().AppendElement(aContainer);
+    }
+  }
+
   // Watch for new nodes added to the pool because the node
   // may need to be added to an insertion point.
-  if (IsPooledNode(aChild, aContainer, GetHost())) {
-    // Add insertion point to destination insertion points of fallback content.
-    if (nsContentUtils::IsContentInsertionPoint(aContainer)) {
-      HTMLContentElement* content = HTMLContentElement::FromContent(aContainer);
-      if (content && content->MatchedNodes().IsEmpty()) {
-        aChild->DestInsertionPoints().AppendElement(aContainer);
+  if (IsPooledNode(aChild)) {
+    auto* insertionPoint = DistributeSingleNode(aChild);
+    while (insertionPoint) {
+      // Handle the case where the parent of the insertion point has a ShadowRoot.
+      // The node distributed into the insertion point must be reprojected to the
+      // insertion points of the parent's ShadowRoot.
+      auto* parentShadow = insertionPoint->GetParent()->GetShadowRoot();
+      if (!parentShadow) {
+        break;
       }
+
+      insertionPoint = parentShadow->DistributeSingleNode(aChild);
     }
-
-    DistributeSingleNode(aChild);
   }
 }
 
 void
 ShadowRoot::ContentRemoved(nsIDocument* aDocument,
                            nsIContent* aContainer,
                            nsIContent* aChild,
                            nsIContent* aPreviousSibling)
@@ -565,18 +628,31 @@ ShadowRoot::ContentRemoved(nsIDocument* 
     HTMLContentElement* content = HTMLContentElement::FromContent(aContainer);
     if (content && content->MatchedNodes().IsEmpty()) {
       aChild->DestInsertionPoints().Clear();
     }
   }
 
   // Watch for node that is removed from the pool because
   // it may need to be removed from an insertion point.
-  if (IsPooledNode(aChild, aContainer, GetHost())) {
-    RemoveDistributedNode(aChild);
+  if (IsPooledNode(aChild)) {
+    auto* insertionPoint = RemoveDistributedNode(aChild);
+    while (insertionPoint) {
+      // Handle the case where the parent of the insertion point has a
+      // ShadowRoot.
+      //
+      // The removed node needs to be removed from the insertion points of the
+      // parent's ShadowRoot.
+      auto* parentShadow = insertionPoint->GetParent()->GetShadowRoot();
+      if (!parentShadow) {
+        break;
+      }
+
+      insertionPoint = parentShadow->RemoveDistributedNode(aChild);
+    }
   }
 }
 
 nsresult
 ShadowRoot::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
                   bool aPreallocateChildren) const
 {
   *aResult = nullptr;
--- a/dom/base/ShadowRoot.h
+++ b/dom/base/ShadowRoot.h
@@ -49,44 +49,67 @@ public:
   void RemoveFromIdTable(Element* aElement, nsAtom* aId);
   void InsertSheet(StyleSheet* aSheet, nsIContent* aLinkingContent);
   void RemoveSheet(StyleSheet* aSheet);
   bool ApplyAuthorStyles();
   void SetApplyAuthorStyles(bool aApplyAuthorStyles);
   StyleSheetList* StyleSheets();
 
   /**
-   * Distributes a single explicit child of the pool host to the content
-   * insertion points in this ShadowRoot.
-   */
-  void DistributeSingleNode(nsIContent* aContent);
-
-  /**
-   * Removes a single explicit child of the pool host from the content
-   * insertion points in this ShadowRoot.
-   */
-  void RemoveDistributedNode(nsIContent* aContent);
-
-  /**
    * Distributes all the explicit children of the pool host to the content
    * insertion points in this ShadowRoot.
    */
   void DistributeAllNodes();
 
+private:
+  /**
+   * Distributes a single explicit child of the pool host to the content
+   * insertion points in this ShadowRoot.
+   *
+   * Returns the insertion point the element is distributed to after this call.
+   *
+   * Note that this doesn't handle distributing the node in the insertion point
+   * parent's shadow root.
+   */
+  const HTMLContentElement* DistributeSingleNode(nsIContent* aContent);
+
+  /**
+   * Removes a single explicit child of the pool host from the content
+   * insertion points in this ShadowRoot.
+   *
+   * Returns the old insertion point, if any.
+   *
+   * Note that this doesn't handle removing the node in the returned insertion
+   * point parent's shadow root.
+   */
+  const HTMLContentElement* RemoveDistributedNode(nsIContent* aContent);
+
+  /**
+   * Redistributes a node of the pool, and returns whether the distribution
+   * changed.
+   */
+  bool RedistributeElement(Element*);
+
+  /**
+   * Called when we redistribute content after insertion points have changed.
+   */
+  void DistributionChanged();
+
+  bool IsPooledNode(nsIContent* aChild) const;
+
+public:
   void AddInsertionPoint(HTMLContentElement* aInsertionPoint);
   void RemoveInsertionPoint(HTMLContentElement* aInsertionPoint);
 
   void SetInsertionPointChanged() { mInsertionPointChanged = true; }
 
   void SetAssociatedBinding(nsXBLBinding* aBinding) { mAssociatedBinding = aBinding; }
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
-  static bool IsPooledNode(nsIContent* aChild, nsIContent* aContainer,
-                           nsIContent* aHost);
   static ShadowRoot* FromNode(nsINode* aNode);
 
   static void RemoveDestInsertionPoint(nsIContent* aInsertionPoint,
                                        nsTArray<nsIContent*>& aDestInsertionPoints);
 
   // WebIDL methods.
   Element* GetElementById(const nsAString& aElementId);
   already_AddRefed<nsContentList>
--- a/dom/canvas/test/webgl-conf/generated-mochitest.ini
+++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini
@@ -7830,17 +7830,17 @@ fail-if = (os == 'mac' && os_version == 
 [generated/test_conformance__glsl__misc__non-ascii-comments.vert.html]
 [generated/test_conformance__glsl__misc__non-ascii.vert.html]
 [generated/test_conformance__glsl__misc__re-compile-re-link.html]
 fail-if = (os == 'android' && android_version == '10')
 [generated/test_conformance__glsl__misc__sequence-operator-returns-constant.html]
 [generated/test_conformance__glsl__misc__shader-precision-format-obeyed.html]
 [generated/test_conformance__glsl__misc__shader-struct-scope.html]
 [generated/test_conformance__glsl__misc__shader-uniform-packing-restrictions.html]
-skip-if = (os == 'android')
+skip-if = (os == 'android') || (os == 'win' && os_version == '6.1' && debug)
 [generated/test_conformance__glsl__misc__shader-varying-packing-restrictions.html]
 [generated/test_conformance__glsl__misc__shader-with-256-character-define.html]
 [generated/test_conformance__glsl__misc__shader-with-256-character-identifier.frag.html]
 [generated/test_conformance__glsl__misc__shader-with-257-character-define.html]
 [generated/test_conformance__glsl__misc__shader-with-257-character-identifier.frag.html]
 [generated/test_conformance__glsl__misc__shader-with-_webgl-identifier.vert.html]
 [generated/test_conformance__glsl__misc__shader-with-arbitrary-indexing.frag.html]
 [generated/test_conformance__glsl__misc__shader-with-arbitrary-indexing.vert.html]
--- a/dom/canvas/test/webgl-conf/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini
@@ -528,17 +528,18 @@ fail-if = (os == 'android' && android_ve
 skip-if = (os == 'android')
 ####################
 # Timeouts
 [generated/test_conformance__context__context-release-upon-reload.html]
 skip-if = (os == 'android')
 [generated/test_conformance__context__context-release-with-workers.html]
 skip-if = (os == 'android')
 [generated/test_conformance__glsl__misc__shader-uniform-packing-restrictions.html]
-skip-if = (os == 'android')
+# Frequent timeout on win7 debug.
+skip-if = (os == 'android') || (os == 'win' && os_version == '6.1' && debug)
 [generated/test_conformance__glsl__bugs__complex-glsl-does-not-crash.html]
 skip-if = (os == 'android')
 
 [generated/test_conformance__glsl__misc__shader-with-non-reserved-words.html]
 fail-if = (os == 'android')
 # (TODO) Generates results after calling finish()
 skip-if = 1
 
--- a/dom/html/HTMLContentElement.cpp
+++ b/dom/html/HTMLContentElement.cpp
@@ -242,28 +242,26 @@ HTMLContentElement::AfterSetAttr(int32_t
             mValidSelector = false;
             mSelectorList = nullptr;
             break;
           }
           selectors = selectors->mNext;
         }
       }
 
-      ShadowRoot* containingShadow = GetContainingShadow();
-      if (containingShadow) {
+      if (ShadowRoot* containingShadow = GetContainingShadow()) {
         containingShadow->DistributeAllNodes();
       }
     } else {
       // The select attribute was removed. This insertion point becomes
       // a universal selector.
       mValidSelector = true;
       mSelectorList = nullptr;
 
-      ShadowRoot* containingShadow = GetContainingShadow();
-      if (containingShadow) {
+      if (ShadowRoot* containingShadow = GetContainingShadow()) {
         containingShadow->DistributeAllNodes();
       }
     }
   }
 
   return nsGenericHTMLElement::AfterSetAttr(aNamespaceID, aName, aValue,
                                             aOldValue, aSubjectPrincipal, aNotify);
 }
--- a/dom/html/HTMLContentElement.h
+++ b/dom/html/HTMLContentElement.h
@@ -24,24 +24,17 @@ public:
   explicit HTMLContentElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
 
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLContentElement,
                                            nsGenericHTMLElement)
 
-  static HTMLContentElement* FromContent(nsIContent* aContent)
-  {
-    if (aContent->IsHTMLContentElement()) {
-      return static_cast<HTMLContentElement*>(aContent);
-    }
-
-    return nullptr;
-  }
+  NS_IMPL_FROMCONTENT_HELPER(HTMLContentElement, IsHTMLContentElement())
 
   virtual bool IsHTMLContentElement() const override { return true; }
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
                          bool aPreallocateChildren) const override;
 
   virtual nsIDOMNode* AsDOMNode() override { return this; }
 
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1394,16 +1394,19 @@ public:
       mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("error"));
       if (mOwner->ReadyState() == HAVE_NOTHING &&
           aErrorCode == MEDIA_ERR_ABORTED) {
         // https://html.spec.whatwg.org/multipage/embedded-content.html#media-data-processing-steps-list
         // "If the media data fetching process is aborted by the user"
         mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("abort"));
         mOwner->ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY);
         mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("emptied"));
+        if (mOwner->mDecoder) {
+          mOwner->ShutdownDecoder();
+        }
       } else if (aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED) {
         mOwner->ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE);
       } else {
         mOwner->ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_IDLE);
       }
     }
   }
 
--- a/dom/html/test/test_fullscreen-api.html
+++ b/dom/html/test/test_fullscreen-api.html
@@ -27,17 +27,16 @@ SimpleTest.requestFlakyTimeout("untriage
 // run in an iframe, which by default will not have the allowfullscreen
 // attribute set, so full-screen won't work.
 var gTestWindows = [
   "file_fullscreen-multiple.html",
   "file_fullscreen-rollback.html",
   "file_fullscreen-esc-exit.html",
   "file_fullscreen-denied.html",
   "file_fullscreen-api.html",
-  "file_fullscreen-plugins.html",
   "file_fullscreen-hidden.html",
   "file_fullscreen-svg-element.html",
   "file_fullscreen-navigation.html",
   "file_fullscreen-scrollbar.html",
   "file_fullscreen-selector.html",
   "file_fullscreen-top-layer.html",
   "file_fullscreen-backdrop.html",
   "file_fullscreen-nested.html",
@@ -63,16 +62,21 @@ function nextTest() {
 
 var gLinuxE10sSkipList = [
   { "test": "file_fullscreen-plugins.html", "reason": "bug 1330553" },
   { "test": "file_fullscreen-api.html", "reason": "bug 1332040" },
   { "test": "file_fullscreen-scrollbar.html", "reason": "bug 1350875" }
 ];
 
 function shouldSkipTest(test) {
+  if (SpecialPowers.Cc["@mozilla.org/gfx/info;1"].getService(SpecialPowers.Ci.nsIGfxInfo).isHeadless &&
+      test == "file_fullscreen-plugins.html") {
+    todo(false, `${test} skipped due to bug 1409805`);
+    return true;
+  }
   if (!SpecialPowers.isMainProcess() &&
       navigator.platform.indexOf('Linux') >= 0) {
     for (let item of gLinuxE10sSkipList) {
       if (item.test == test) {
         todo(false, `${test} skipped due to ${item.reason}`);
         return true;
       }
     }
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -250,19 +250,16 @@ var gPlayTests = [
   { name:"bug1377278.webm", type:"video/webm", duration:4.0 },
 
   // Test playback of a WebM file with non-zero start time.
   { name:"split.webm", type:"video/webm", duration:1.967 },
 
   // Test playback of a WebM file with resolution changes.
   { name:"resolution-change.webm", type:"video/webm", duration:6.533 },
 
-  // Test playback of a raw file
-  { name:"seek.yuv", type:"video/x-raw-yuv", duration:1.833 },
-
   // A really short, low sample rate, single channel file. This tests whether
   // we can handle playing files when only push very little audio data to the
   // hardware.
   { name:"spacestorm-1000Hz-100ms.ogg", type:"audio/ogg", duration:0.099 },
 
   // Opus data in an ogg container
   { name:"detodos-short.opus", type:"audio/ogg; codecs=opus", duration:0.22 },
   // Opus data in a webm container
@@ -347,18 +344,16 @@ var gSeekToNextFrameTests = [
   { name:"multiple-bos.ogg", type:"video/ogg", duration:0.431 },
   // Test playback/metadata work after a redirect
   { name:"redirect.sjs?domain=mochi.test:8888&file=320x240.ogv",
     type:"video/ogg", duration:0.266 },
   // Test playback of a webm file
   { name:"seek-short.webm", type:"video/webm", duration:0.23 },
   // Test playback of a WebM file with non-zero start time.
   { name:"split.webm", type:"video/webm", duration:1.967 },
-  // Test playback of a raw file
-  { name:"seek.yuv", type:"video/x-raw-yuv", duration:1.833 },
 
   { name:"gizmo-short.mp4", type:"video/mp4", duration:0.27 },
 
   // Test playback of a MP4 file with a non-zero start time (and audio starting
   // a second later).
   { name:"bipbop-lateaudio.mp4", type:"video/mp4" },
 ];
 
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -539,17 +539,16 @@ support-files =
   seek.ogv
   seek.ogv^headers^
   seek-short.ogv
   seek-short.ogv^headers^
   seek.webm
   seek.webm^headers^
   seek-short.webm
   seek-short.webm^headers^
-  seek.yuv
   seek_support.js
   seekLies.sjs
   seek_with_sound.ogg^headers^
   sequential.vtt
   short-cenc.mp4
   sine.webm
   sine.webm^headers^
   sintel-short-clearkey-subsample-encrypted-audio.webm
deleted file mode 100644
index 69485e8e1bd15c06f7e93ae389e393238c266e63..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
--- a/dom/tests/mochitest/webcomponents/mochitest.ini
+++ b/dom/tests/mochitest/webcomponents/mochitest.ini
@@ -46,12 +46,12 @@ skip-if = true # disabled - See bug 1390
 [test_template.html]
 [test_template_xhtml.html]
 [test_template_custom_elements.html]
 [test_shadowroot.html]
 [test_shadowroot_inert_element.html]
 [test_shadowroot_style.html]
 [test_shadowroot_style_order.html]
 [test_style_fallback_content.html]
-skip-if = stylo # bug 1409088
+skip-if = stylo # Bug 1410170
 [test_unresolved_pseudo_class.html]
 [test_link_prefetch.html]
 [test_bug1269155.html]
--- a/dom/tests/mochitest/webcomponents/test_shadowroot_style.html
+++ b/dom/tests/mochitest/webcomponents/test_shadowroot_style.html
@@ -8,16 +8,21 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <div class="tall" id="bodydiv"></div>
 <div id="container"></div>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a>
 <script>
+
+if (!SpecialPowers.DOMWindowUtils.isStyledByServo) {
+  SimpleTest.expectAssertions(3, 3); // GeckoRestyleManager stuff.
+}
+
 // Create ShadowRoot.
 var container = document.getElementById("container");
 var elem = document.createElement("div");
 container.appendChild(elem); // Put ShadowRoot host in document.
 var root = elem.createShadowRoot();
 
 // A style element that will be appended into the ShadowRoot.
 var shadowStyle = document.createElement("style");
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -208,24 +208,25 @@ WebRenderCommandBuilder::CreateWebRender
       // If we're going to create a new layer data for this item, stash the
       // ASR so that if we recurse into a sublist they will know where to stop
       // walking up their ASR chain when building scroll metadata.
       if (forceNewLayerData) {
         mAsrStack.push_back(asr);
       }
     }
 
-    // ensure the scope of ScrollingLayersHelper is maintained
-    ScrollingLayersHelper clip(item, aBuilder, aSc, mClipIdCache, apzEnabled);
+    { // ensure the scope of ScrollingLayersHelper is maintained
+      ScrollingLayersHelper clip(item, aBuilder, aSc, mClipIdCache, apzEnabled);
 
-    // Note: this call to CreateWebRenderCommands can recurse back into
-    // this function if the |item| is a wrapper for a sublist.
-    if (!item->CreateWebRenderCommands(aBuilder, aResources, aSc, mManager,
-                                       aDisplayListBuilder)) {
-      PushItemAsImage(item, aBuilder, aResources, aSc, aDisplayListBuilder);
+      // Note: this call to CreateWebRenderCommands can recurse back into
+      // this function if the |item| is a wrapper for a sublist.
+      if (!item->CreateWebRenderCommands(aBuilder, aResources, aSc, mManager,
+                                         aDisplayListBuilder)) {
+        PushItemAsImage(item, aBuilder, aResources, aSc, aDisplayListBuilder);
+      }
     }
 
     if (apzEnabled) {
       if (forceNewLayerData) {
         // Pop the thing we pushed before the recursion, so the topmost item on
         // the stack is enclosing display item's ASR (or the stack is empty)
         mAsrStack.pop_back();
         const ActiveScrolledRoot* stopAtAsr =
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -247,19 +247,21 @@ WebRenderLayerManager::EndTransactionWit
                                                   nsDisplayListBuilder* aDisplayListBuilder)
 {
   MOZ_ASSERT(aDisplayList && aDisplayListBuilder);
   WrBridge()->RemoveExpiredFontKeys();
 
   AUTO_PROFILER_TRACING("Paint", "RenderLayers");
   mTransactionIncomplete = false;
 
-  if (gfxPrefs::LayersDump()) {
-    this->Dump();
-  }
+#if 0
+  // Useful for debugging, it dumps the display list *before* we try to build
+  // WR commands from it
+  nsFrame::PrintDisplayList(aDisplayListBuilder, *aDisplayList);
+#endif
 
   // Since we don't do repeat transactions right now, just set the time
   mAnimationReadyTime = TimeStamp::Now();
 
   WrBridge()->BeginTransaction();
   DiscardCompositorAnimations();
 
   LayoutDeviceIntSize size = mWidget->GetClientSize();
--- a/ipc/chromium/src/base/process_util_mac.mm
+++ b/ipc/chromium/src/base/process_util_mac.mm
@@ -8,16 +8,17 @@
 #include <fcntl.h>
 #include <spawn.h>
 #include <sys/wait.h>
 
 #include <string>
 
 #include "base/eintr_wrapper.h"
 #include "base/logging.h"
+#include "mozilla/ScopeExit.h"
 
 namespace {
 
 static mozilla::EnvironmentLog gProcessLog("MOZ_PROCESS_LOG");
 
 }  // namespace
 
 namespace base {
@@ -37,42 +38,40 @@ bool LaunchApp(const std::vector<std::st
   bool retval = true;
 
   char* argv_copy[argv.size() + 1];
   for (size_t i = 0; i < argv.size(); i++) {
     argv_copy[i] = const_cast<char*>(argv[i].c_str());
   }
   argv_copy[argv.size()] = NULL;
 
-  // Make sure we don't leak any FDs to the child process by marking all FDs
-  // as close-on-exec.
-  SetAllFDsToCloseOnExec();
-
   EnvironmentArray vars = BuildEnvironmentArray(env_vars_to_set);
 
   posix_spawn_file_actions_t file_actions;
   if (posix_spawn_file_actions_init(&file_actions) != 0) {
     return false;
   }
+  auto file_actions_guard = mozilla::MakeScopeExit([&file_actions] {
+    posix_spawn_file_actions_destroy(&file_actions);
+  });
 
   // Turn fds_to_remap array into a set of dup2 calls.
   for (file_handle_mapping_vector::const_iterator it = fds_to_remap.begin();
        it != fds_to_remap.end();
        ++it) {
     int src_fd = it->first;
     int dest_fd = it->second;
 
     if (src_fd == dest_fd) {
       int flags = fcntl(src_fd, F_GETFD);
       if (flags != -1) {
         fcntl(src_fd, F_SETFD, flags & ~FD_CLOEXEC);
       }
     } else {
       if (posix_spawn_file_actions_adddup2(&file_actions, src_fd, dest_fd) != 0) {
-        posix_spawn_file_actions_destroy(&file_actions);
         return false;
       }
     }
   }
 
   // Set up the CPU preference array.
   cpu_type_t cpu_types[1];
   switch (arch) {
@@ -90,38 +89,50 @@ bool LaunchApp(const std::vector<std::st
       break;
   }
 
   // Initialize spawn attributes.
   posix_spawnattr_t spawnattr;
   if (posix_spawnattr_init(&spawnattr) != 0) {
     return false;
   }
+  auto spawnattr_guard = mozilla::MakeScopeExit([&spawnattr] {
+    posix_spawnattr_destroy(&spawnattr);
+  });
 
   // Set spawn attributes.
   size_t attr_count = 1;
   size_t attr_ocount = 0;
   if (posix_spawnattr_setbinpref_np(&spawnattr, attr_count, cpu_types, &attr_ocount) != 0 ||
       attr_ocount != attr_count) {
-    posix_spawnattr_destroy(&spawnattr);
+    return false;
+  }
+
+  // Prevent the child process from inheriting any file descriptors
+  // that aren't named in `file_actions`.  (This is an Apple-specific
+  // extension to posix_spawn.)
+  if (posix_spawnattr_setflags(&spawnattr, POSIX_SPAWN_CLOEXEC_DEFAULT) != 0) {
     return false;
   }
 
+  // Exempt std{in,out,err} from being closed by POSIX_SPAWN_CLOEXEC_DEFAULT.
+  for (int fd = 0; fd <= STDERR_FILENO; ++fd) {
+    if (posix_spawn_file_actions_addinherit_np(&file_actions, fd) != 0) {
+      return false;
+    }
+  }
+
   int pid = 0;
   int spawn_succeeded = (posix_spawnp(&pid,
                                       argv_copy[0],
                                       &file_actions,
                                       &spawnattr,
                                       argv_copy,
                                       vars.get()) == 0);
 
-  posix_spawn_file_actions_destroy(&file_actions);
-
-  posix_spawnattr_destroy(&spawnattr);
-
   bool process_handle_valid = pid > 0;
   if (!spawn_succeeded || !process_handle_valid) {
     retval = false;
   } else {
     gProcessLog.print("==> process %d launched child process %d\n",
                       GetCurrentProcId(), pid);
     if (wait)
       HANDLE_EINTR(waitpid(pid, 0, 0));
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -1202,19 +1202,40 @@ ServoRestyleManager::DoProcessPendingRes
   styleSet->MaybeGCRuleTree();
 
   // Note: We are in the scope of |animationsWithDestroyedFrame|, so
   //       |mAnimationsWithDestroyedFrame| is still valid.
   MOZ_ASSERT(mAnimationsWithDestroyedFrame);
   mAnimationsWithDestroyedFrame->StopAnimationsForElementsWithoutFrames();
 }
 
+#ifdef DEBUG
+static void
+VerifyFlatTree(const nsIContent& aContent)
+{
+  StyleChildrenIterator iter(&aContent);
+
+  for (auto* content = iter.GetNextChild();
+       content;
+       content = iter.GetNextChild()) {
+    MOZ_ASSERT(content->GetFlattenedTreeParentNodeForStyle() == &aContent);
+    VerifyFlatTree(*content);
+  }
+}
+#endif
+
 void
 ServoRestyleManager::ProcessPendingRestyles()
 {
+#ifdef DEBUG
+  if (auto* root = mPresContext->Document()->GetRootElement()) {
+    VerifyFlatTree(*root);
+  }
+#endif
+
   DoProcessPendingRestyles(ServoTraversalFlags::Empty);
 }
 
 void
 ServoRestyleManager::ProcessAllPendingAttributeAndStateInvalidations()
 {
   if (mSnapshots.IsEmpty()) {
     return;
new file mode 100644
--- /dev/null
+++ b/layout/base/crashtests/1404789-1.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<script>
+  // Test for content redistribution outside of the document.
+  // Passes if it doesn't assert.
+  let host = document.createElement('div');
+  host.innerHTML = "<div id='foo'></div>";
+
+  let shadowRoot = host.createShadowRoot();
+  shadowRoot.innerHTML = "<content select='#foo'></content>";
+
+  host.firstElementChild.removeAttribute('id');
+
+  // Move to the document, do the same.
+  document.documentElement.appendChild(host);
+  host.firstElementChild.setAttribute('id', 'foo');
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/base/crashtests/1404789-2.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<iframe style="display: none" src="1404789-1.html"></iframe>
new file mode 100644
--- /dev/null
+++ b/layout/base/crashtests/1409088.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<div></div>
+<script>
+let host = document.querySelector('div');
+let shadow = host.createShadowRoot();
+
+// Append two divs and three spans into host.
+host.innerHTML = '<div></div><span></span><div></div><span></span><span></span>';
+shadow.innerHTML = '<content select="div" id="divpoint"></content><content select="div, span" id="allpoint"></content>';
+
+let divPoint = shadow.getElementById("divpoint");
+let allPoint = shadow.getElementById("allpoint");
+
+shadow.removeChild(allPoint);
+shadow.insertBefore(allPoint, divPoint);
+</script>
--- a/layout/base/crashtests/crashtests.list
+++ b/layout/base/crashtests/crashtests.list
@@ -502,10 +502,13 @@ load 1397398-1.html
 load 1397398-2.html
 load 1397398-3.html
 load 1398500.html
 load 1400438-1.html
 load 1400599-1.html
 load 1401739.html
 load 1401840.html
 load 1402476.html
+load 1404789-1.html
+load 1404789-2.html
 load 1406562.html
+load 1409088.html
 load 1409147.html
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -7262,16 +7262,23 @@ nsCSSFrameConstructor::CheckBitsForLazyF
     "constructed lazily should have frames");
   NS_ASSERTION(!needsFrameBitSet, "Ancestors of nodes with frames to be "
     "constructed lazily should not have NEEDS_FRAME bit set");
 }
 #endif
 
 // For inserts aChild should be valid, for appends it should be null.
 // Returns true if this operation can be lazy, false if not.
+//
+// FIXME(emilio, bug 1410020): This function assumes that the flattened tree
+// parent of all the appended children is the same, which, afaict, is not
+// necessarily true.
+//
+// But we disable lazy frame construction for shadow trees... We should fix
+// that, too.
 bool
 nsCSSFrameConstructor::MaybeConstructLazily(Operation aOperation,
                                             nsIContent* aContainer,
                                             nsIContent* aChild)
 {
   // XXXmats no lazy frames for display:contents direct descendants yet
   // (bug 979782).
   if (mPresShell->GetPresContext()->IsChrome() || !aContainer ||
@@ -7296,16 +7303,22 @@ nsCSSFrameConstructor::MaybeConstructLaz
       if (child->IsXULElement()) {
         return false;
       }
     }
   }
 
   // We can construct lazily; just need to set suitable bits in the content
   // tree.
+  nsIContent* parent = aChild->GetFlattenedTreeParent();
+  if (!parent) {
+    // Not part of the flat tree, nothing to do.
+    return true;
+  }
+
 
   // Set NODE_NEEDS_FRAME on the new nodes.
   if (aOperation == CONTENTINSERT) {
     NS_ASSERTION(!aChild->GetPrimaryFrame() ||
                  aChild->GetPrimaryFrame()->GetContent() != aChild,
                  //XXX the aChild->GetPrimaryFrame()->GetContent() != aChild
                  // check is needed due to bug 135040. Remove it once that's
                  // fixed.
@@ -7320,17 +7333,16 @@ nsCSSFrameConstructor::MaybeConstructLaz
                    // fixed.
                    "setting NEEDS_FRAME on a node that already has a frame?");
       child->SetFlags(NODE_NEEDS_FRAME);
     }
   }
 
   // Walk up the tree setting the NODE_DESCENDANTS_NEED_FRAMES bit as we go.
   // We need different handling for servo given the scoped restyle roots.
-  nsIContent* parent = aChild->GetFlattenedTreeParent();
   CheckBitsForLazyFrameConstruction(parent);
 
   if (mozilla::GeckoRestyleManager* geckoRM = RestyleManager()->GetAsGecko()) {
     while (parent && !parent->HasFlag(NODE_DESCENDANTS_NEED_FRAMES)) {
       parent->SetFlags(NODE_DESCENDANTS_NEED_FRAMES);
       parent = parent->GetFlattenedTreeParent();
     }
     geckoRM->PostRestyleEventForLazyConstruction();
@@ -7639,64 +7651,45 @@ nsCSSFrameConstructor::ContentAppended(n
     // Just ignore tree tags, anyway we don't create any frames for them.
     if (tag == nsGkAtoms::treechildren ||
         tag == nsGkAtoms::treeitem ||
         tag == nsGkAtoms::treerow)
       return;
   }
 #endif // MOZ_XUL
 
-  bool isNewShadowTreeContent =
-    aContainer && aContainer->HasFlag(NODE_IS_IN_SHADOW_TREE) &&
-    !aContainer->IsInNativeAnonymousSubtree() &&
-    !aFirstNewContent->IsInNativeAnonymousSubtree();
-
-  if (!isNewShadowTreeContent) {
-    // See comment in ContentRangeInserted for why this is necessary.
-    if (!GetContentInsertionFrameFor(aContainer) &&
-        !aContainer->IsActiveChildrenElement()) {
-      // We're punting on frame construction because there's no container frame.
-      // The Servo-backed style system handles this case like the lazy frame
-      // construction case, except when we're already constructing frames, in
-      // which case we shouldn't need to do anything else.
-      if (aContainer->IsStyledByServo() &&
-          aInsertionKind == InsertionKind::Async) {
-        LazilyStyleNewChildRange(aFirstNewContent, nullptr);
-      }
-      return;
-    }
-
-    if (aInsertionKind == InsertionKind::Async &&
-        MaybeConstructLazily(CONTENTAPPEND, aContainer, aFirstNewContent)) {
-      if (aContainer->IsStyledByServo()) {
-        LazilyStyleNewChildRange(aFirstNewContent, nullptr);
-      }
-      return;
-    }
+  // See comment in ContentRangeInserted for why this is necessary.
+  if (!GetContentInsertionFrameFor(aContainer) &&
+      !aContainer->IsActiveChildrenElement()) {
+    // We're punting on frame construction because there's no container frame.
+    // The Servo-backed style system handles this case like the lazy frame
+    // construction case, except when we're already constructing frames, in
+    // which case we shouldn't need to do anything else.
+    if (aContainer->IsStyledByServo() &&
+        aInsertionKind == InsertionKind::Async) {
+      LazilyStyleNewChildRange(aFirstNewContent, nullptr);
+    }
+    return;
+  }
+
+  if (aInsertionKind == InsertionKind::Async &&
+      MaybeConstructLazily(CONTENTAPPEND, aContainer, aFirstNewContent)) {
+    if (aContainer->IsStyledByServo()) {
+      LazilyStyleNewChildRange(aFirstNewContent, nullptr);
+    }
+    return;
   }
 
   // We couldn't construct lazily. Make Servo eagerly traverse the new content
   // if needed (when aInsertionKind == InsertionKind::Sync, we know that the
   // styles are up-to-date already).
   if (aInsertionKind == InsertionKind::Async && aContainer->IsStyledByServo()) {
     StyleNewChildRange(aFirstNewContent, nullptr);
   }
 
-  if (isNewShadowTreeContent) {
-    // Recreate frames if content is appended into a ShadowRoot
-    // because children of ShadowRoot are rendered in place of children
-    // of the host.
-    //XXXsmaug This is super unefficient!
-    nsIContent* bindingParent = aContainer->GetBindingParent();
-    LAYOUT_PHASE_TEMP_EXIT();
-    RecreateFramesForContent(bindingParent, aInsertionKind);
-    LAYOUT_PHASE_TEMP_REENTER();
-    return;
-  }
-
   LAYOUT_PHASE_TEMP_EXIT();
   InsertionPoint insertion =
     GetRangeInsertionPoint(aContainer, aFirstNewContent, nullptr,
                            aInsertionKind);
   nsContainerFrame*& parentFrame = insertion.mParentFrame;
   LAYOUT_PHASE_TEMP_REENTER();
   if (!parentFrame) {
     return;
@@ -7787,18 +7780,20 @@ nsCSSFrameConstructor::ContentAppended(n
   }
 
   // Create some new frames
   //
   // We use the provided tree match context, or create a new one on the fly
   // otherwise.
   Maybe<TreeMatchContext> matchContext;
   if (!aProvidedTreeMatchContext && !aContainer->IsStyledByServo()) {
+    // We use GetParentElementCrossingShadowRoot to handle the case where
+    // aContainer is a ShadowRoot.
     matchContext.emplace(mDocument, TreeMatchContext::ForFrameConstruction);
-    matchContext->InitAncestors(aContainer->AsElement());
+    matchContext->InitAncestors(aFirstNewContent->GetParentElementCrossingShadowRoot());
   }
   nsFrameConstructorState state(mPresShell,
                                 matchContext.ptrOr(aProvidedTreeMatchContext),
                                 GetAbsoluteContainingBlock(parentFrame, FIXED_POS),
                                 GetAbsoluteContainingBlock(parentFrame, ABS_POS),
                                 containingBlock);
 
   LayoutFrameType frameType = parentFrame->Type();
@@ -8124,71 +8119,55 @@ nsCSSFrameConstructor::ContentRangeInser
       accService->ContentRangeInserted(mPresShell, aContainer,
                                        aStartChild, aEndChild);
     }
 #endif
 
     return;
   }
 
-  bool isNewShadowTreeContent =
-    aContainer->HasFlag(NODE_IS_IN_SHADOW_TREE) &&
-    !aContainer->IsInNativeAnonymousSubtree() &&
-    (!aStartChild || !aStartChild->IsInNativeAnonymousSubtree()) &&
-    (!aEndChild || !aEndChild->IsInNativeAnonymousSubtree());
-
-  if (!isNewShadowTreeContent) {
-    nsContainerFrame* parentFrame = GetContentInsertionFrameFor(aContainer);
-    // The xbl:children element won't have a frame, but default content can have the children as
-    // a parent. While its uncommon to change the structure of the default content itself, a label,
-    // for example, can be reframed by having its value attribute set or removed.
-    if (!parentFrame && !aContainer->IsActiveChildrenElement()) {
-      // We're punting on frame construction because there's no container frame.
-      // The Servo-backed style system handles this case like the lazy frame
-      // construction case, except when we're already constructing frames, in
-      // which case we shouldn't need to do anything else.
-      if (aContainer->IsStyledByServo() &&
-          aInsertionKind == InsertionKind::Async) {
-        LazilyStyleNewChildRange(aStartChild, aEndChild);
-      }
-      return;
-    }
-
-    // Otherwise, we've got parent content. Find its frame.
-    NS_ASSERTION(!parentFrame || parentFrame->GetContent() == aContainer ||
-                 GetDisplayContentsStyleFor(aContainer), "New XBL code is possibly wrong!");
-
-    if (aInsertionKind == InsertionKind::Async &&
-        MaybeConstructLazily(CONTENTINSERT, aContainer, aStartChild)) {
-      if (aContainer->IsStyledByServo()) {
-        LazilyStyleNewChildRange(aStartChild, aEndChild);
-      }
-      return;
-    }
+  nsContainerFrame* parentFrame = GetContentInsertionFrameFor(aContainer);
+  // The xbl:children element won't have a frame, but default content can have the children as
+  // a parent. While its uncommon to change the structure of the default content itself, a label,
+  // for example, can be reframed by having its value attribute set or removed.
+  if (!parentFrame &&
+      !(aContainer->IsActiveChildrenElement() ||
+        ShadowRoot::FromNode(aContainer))) {
+    // We're punting on frame construction because there's no container frame.
+    // The Servo-backed style system handles this case like the lazy frame
+    // construction case, except when we're already constructing frames, in
+    // which case we shouldn't need to do anything else.
+    if (aContainer->IsStyledByServo() &&
+        aInsertionKind == InsertionKind::Async) {
+      LazilyStyleNewChildRange(aStartChild, aEndChild);
+    }
+    return;
+  }
+
+  MOZ_ASSERT_IF(ShadowRoot::FromNode(aContainer), !parentFrame);
+
+  // Otherwise, we've got parent content. Find its frame.
+  NS_ASSERTION(!parentFrame || parentFrame->GetContent() == aContainer ||
+               GetDisplayContentsStyleFor(aContainer), "New XBL code is possibly wrong!");
+
+  if (aInsertionKind == InsertionKind::Async &&
+      MaybeConstructLazily(CONTENTINSERT, aContainer, aStartChild)) {
+    if (aContainer->IsStyledByServo()) {
+      LazilyStyleNewChildRange(aStartChild, aEndChild);
+    }
+    return;
   }
 
   // We couldn't construct lazily. Make Servo eagerly traverse the new content
   // if needed (when aInsertionKind == InsertionKind::Sync, we know that the
   // styles are up-to-date already).
   if (aInsertionKind == InsertionKind::Async && aContainer->IsStyledByServo()) {
     StyleNewChildRange(aStartChild, aEndChild);
   }
 
-  if (isNewShadowTreeContent) {
-    // Recreate frames if content is inserted into a ShadowRoot
-    // because children of ShadowRoot are rendered in place of
-    // the children of the host.
-    //XXXsmaug This is super unefficient!
-    nsIContent* bindingParent = aContainer->GetBindingParent();
-    LAYOUT_PHASE_TEMP_EXIT();
-    RecreateFramesForContent(bindingParent, aInsertionKind);
-    LAYOUT_PHASE_TEMP_REENTER();
-    return;
-  }
-
   InsertionPoint insertion;
   if (isSingleInsert) {
     // See if we have an XBL insertion point. If so, then that's our
     // real parent frame; if not, then the frame hasn't been built yet
     // and we just bail.
     insertion = GetInsertionPoint(aContainer, aStartChild);
   } else {
     // Get our insertion point. If we need to issue single ContentInserted's
@@ -8273,18 +8252,20 @@ nsCSSFrameConstructor::ContentRangeInser
     RecreateFramesForContent(insertion.mParentFrame->GetContent(),
                              aInsertionKind);
     LAYOUT_PHASE_TEMP_REENTER();
     return;
   }
 
   Maybe<TreeMatchContext> matchContext;
   if (!aProvidedTreeMatchContext && !aContainer->IsStyledByServo()) {
+    // We use GetParentElementCrossingShadowRoot to handle the case where
+    // aContainer is a ShadowRoot.
     matchContext.emplace(mDocument, TreeMatchContext::ForFrameConstruction);
-    matchContext->InitAncestors(aContainer ? aContainer->AsElement() : nullptr);
+    matchContext->InitAncestors(aStartChild->GetParentElementCrossingShadowRoot());
   }
   nsFrameConstructorState state(mPresShell,
                                 matchContext.ptrOr(aProvidedTreeMatchContext),
                                 GetAbsoluteContainingBlock(insertion.mParentFrame, FIXED_POS),
                                 GetAbsoluteContainingBlock(insertion.mParentFrame, ABS_POS),
                                 GetFloatContainingBlock(insertion.mParentFrame),
                                 do_AddRef(aFrameState));
 
@@ -8743,30 +8724,16 @@ nsCSSFrameConstructor::ContentRemoved(ns
       if (firstChild && firstChild->GetContent() == aChild) {
         isRoot = true;
         childFrame = firstChild;
         NS_ASSERTION(!childFrame->GetNextSibling(), "How did that happen?");
       }
     }
   }
 
-  if (aContainer && aContainer->HasFlag(NODE_IS_IN_SHADOW_TREE) &&
-      !aContainer->IsInNativeAnonymousSubtree() &&
-      !aChild->IsInNativeAnonymousSubtree()) {
-    // Recreate frames if content is removed from a ShadowRoot because it may
-    // contain an insertion point which can change how the host is rendered.
-    //
-    // XXXsmaug This is super unefficient!
-    nsIContent* bindingParent = aContainer->GetBindingParent();
-    LAYOUT_PHASE_TEMP_EXIT();
-    RecreateFramesForContent(bindingParent, InsertionKind::Async);
-    LAYOUT_PHASE_TEMP_REENTER();
-    return true;
-  }
-
   if (childFrame) {
     InvalidateCanvasIfNeeded(mPresShell, aChild);
 
     // See whether we need to remove more than just childFrame
     LAYOUT_PHASE_TEMP_EXIT();
     if (MaybeRecreateContainerForFrameRemoval(childFrame)) {
       LAYOUT_PHASE_TEMP_REENTER();
       return true;
@@ -9572,18 +9539,21 @@ nsCSSFrameConstructor::GetInsertionPoint
     // We've got an explicit insertion child. Check to see if it's
     // anonymous.
     if (aChild->GetBindingParent() == aContainer) {
       // This child content is anonymous. Don't use the insertion
       // point, since that's only for the explicit kids.
       return InsertionPoint(GetContentInsertionFrameFor(aContainer), aContainer);
     }
 
-    if (nsContentUtils::HasDistributedChildren(aContainer)) {
-      // The container distributes nodes, use the frame of the flattened tree parent.
+    if (nsContentUtils::HasDistributedChildren(aContainer) ||
+        ShadowRoot::FromNode(aContainer)) {
+      // The container distributes nodes or is a shadow root, use the frame of
+      // the flattened tree parent.
+      //
       // It may be the case that the node is distributed but not matched to any
       // insertion points, so there is no flattened parent.
       nsIContent* flattenedParent = aChild->GetFlattenedTreeParent();
       if (flattenedParent) {
         return InsertionPoint(GetContentInsertionFrameFor(flattenedParent),
                               flattenedParent);
       }
       return InsertionPoint();
--- a/layout/generic/nsBulletFrame.cpp
+++ b/layout/generic/nsBulletFrame.cpp
@@ -219,17 +219,17 @@ public:
     , mText(text)
     , mFontMetrics(fm)
     , mPoint(point)
     , mListStyleType(listStyleType)
   {
     MOZ_ASSERT(IsTextType());
   }
 
-  void
+  bool
   CreateWebRenderCommands(nsDisplayItem* aItem,
                           wr::DisplayListBuilder& aBuilder,
                           wr::IpcResourceUpdateQueue& aResources,
                           const layers::StackingContextHelper& aSc,
                           mozilla::layers::WebRenderLayerManager* aManager,
                           nsDisplayListBuilder* aDisplayListBuilder);
 
   DrawResult
@@ -264,37 +264,42 @@ public:
            mListStyleType != NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN &&
            mListStyleType != NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED &&
            !mText.IsEmpty();
   }
 
   bool
   BuildGlyphForText(nsDisplayItem* aItem, bool disableSubpixelAA);
 
+  void
+  PaintTextToContext(nsIFrame* aFrame,
+                     gfxContext* aCtx,
+                     bool aDisableSubpixelAA);
+
   bool
   IsImageContainerAvailable(layers::LayerManager* aManager, uint32_t aFlags);
 
 private:
-  void
+  bool
   CreateWebRenderCommandsForImage(nsDisplayItem* aItem,
                                   wr::DisplayListBuilder& aBuilder,
                                   wr::IpcResourceUpdateQueue& aResources,
                                   const layers::StackingContextHelper& aSc,
                                   mozilla::layers::WebRenderLayerManager* aManager,
                                   nsDisplayListBuilder* aDisplayListBuilder);
 
-  void
+  bool
   CreateWebRenderCommandsForPath(nsDisplayItem* aItem,
                                  wr::DisplayListBuilder& aBuilder,
                                  wr::IpcResourceUpdateQueue& aResources,
                                  const layers::StackingContextHelper& aSc,
                                  mozilla::layers::WebRenderLayerManager* aManager,
                                  nsDisplayListBuilder* aDisplayListBuilder);
 
-  void
+  bool
   CreateWebRenderCommandsForText(nsDisplayItem* aItem,
                                  wr::DisplayListBuilder& aBuilder,
                                  wr::IpcResourceUpdateQueue& aResources,
                                  const layers::StackingContextHelper& aSc,
                                  mozilla::layers::WebRenderLayerManager* aManager,
                                  nsDisplayListBuilder* aDisplayListBuilder);
 
 private:
@@ -317,34 +322,34 @@ private:
   nsPoint mPoint;
   RefPtr<ScaledFont> mFont;
   nsTArray<layers::GlyphArray> mGlyphs;
 
   // Store the type of list-style-type.
   int32_t mListStyleType;
 };
 
-void
+bool
 BulletRenderer::CreateWebRenderCommands(nsDisplayItem* aItem,
                                         wr::DisplayListBuilder& aBuilder,
                                         wr::IpcResourceUpdateQueue& aResources,
                                         const layers::StackingContextHelper& aSc,
                                         mozilla::layers::WebRenderLayerManager* aManager,
                                         nsDisplayListBuilder* aDisplayListBuilder)
 {
   if (IsImageType()) {
-    CreateWebRenderCommandsForImage(aItem, aBuilder, aResources,
+    return CreateWebRenderCommandsForImage(aItem, aBuilder, aResources,
                                     aSc, aManager, aDisplayListBuilder);
   } else if (IsPathType()) {
-    CreateWebRenderCommandsForPath(aItem, aBuilder, aResources,
+    return CreateWebRenderCommandsForPath(aItem, aBuilder, aResources,
                                    aSc, aManager, aDisplayListBuilder);
   } else {
     MOZ_ASSERT(IsTextType());
-    CreateWebRenderCommandsForText(aItem, aBuilder, aResources, aSc,
-                                   aManager, aDisplayListBuilder);
+    return CreateWebRenderCommandsForText(aItem, aBuilder, aResources, aSc,
+                                          aManager, aDisplayListBuilder);
   }
 }
 
 DrawResult
 BulletRenderer::Paint(gfxContext& aRenderingContext, nsPoint aPt,
                       const nsRect& aDirtyRect, uint32_t aFlags,
                       bool aDisableSubpixelAA, nsIFrame* aFrame)
 {
@@ -372,28 +377,17 @@ BulletRenderer::Paint(gfxContext& aRende
       drawTarget->Fill(mPath, ColorPattern(ToDeviceColor(mColor)));
       break;
     default:
       MOZ_CRASH("unreachable");
     }
   }
 
   if (IsTextType()) {
-    DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
-    DrawTargetAutoDisableSubpixelAntialiasing
-      disable(drawTarget, aDisableSubpixelAA);
-
-    aRenderingContext.SetColor(Color::FromABGR(mColor));
-
-    nsPresContext* presContext = aFrame->PresContext();
-    if (!presContext->BidiEnabled() && HasRTLChars(mText)) {
-      presContext->SetBidiEnabled();
-    }
-    nsLayoutUtils::DrawString(aFrame, *mFontMetrics, &aRenderingContext,
-                              mText.get(), mText.Length(), mPoint);
+    PaintTextToContext(aFrame, &aRenderingContext, aDisableSubpixelAA);
   }
 
   return DrawResult::SUCCESS;
 }
 
 bool
 BulletRenderer::BuildGlyphForText(nsDisplayItem* aItem, bool disableSubpixelAA)
 {
@@ -402,31 +396,17 @@ BulletRenderer::BuildGlyphForText(nsDisp
   RefPtr<DrawTarget> screenTarget = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
   RefPtr<DrawTargetCapture> capture =
     Factory::CreateCaptureDrawTarget(screenTarget->GetBackendType(),
                                      IntSize(),
                                      screenTarget->GetFormat());
 
   RefPtr<gfxContext> captureCtx = gfxContext::CreateOrNull(capture);
 
-  {
-    DrawTargetAutoDisableSubpixelAntialiasing
-      disable(capture, disableSubpixelAA);
-
-    captureCtx->SetColor(
-      Color::FromABGR(mColor));
-
-    nsPresContext* presContext = aItem->Frame()->PresContext();
-    if (!presContext->BidiEnabled() && HasRTLChars(mText)) {
-      presContext->SetBidiEnabled();
-    }
-
-    nsLayoutUtils::DrawString(aItem->Frame(), *mFontMetrics, captureCtx,
-                              mText.get(), mText.Length(), mPoint);
-  }
+  PaintTextToContext(aItem->Frame(), captureCtx, disableSubpixelAA);
 
   layers::GlyphArray* g = mGlyphs.AppendElement();
   std::vector<Glyph> glyphs;
   Color color;
   if (!capture->ContainsOnlyColoredGlyphs(mFont, color, glyphs)) {
     mFont = nullptr;
     mGlyphs.Clear();
     return false;
@@ -434,116 +414,132 @@ BulletRenderer::BuildGlyphForText(nsDisp
 
   g->glyphs().SetLength(glyphs.size());
   PodCopy(g->glyphs().Elements(), glyphs.data(), glyphs.size());
   g->color() = color;
 
   return true;
 }
 
+void
+BulletRenderer::PaintTextToContext(nsIFrame* aFrame,
+                                   gfxContext* aCtx,
+                                   bool aDisableSubpixelAA)
+{
+  MOZ_ASSERT(IsTextType());
+
+  DrawTarget* drawTarget = aCtx->GetDrawTarget();
+  DrawTargetAutoDisableSubpixelAntialiasing
+    disable(drawTarget, aDisableSubpixelAA);
+
+  aCtx->SetColor(Color::FromABGR(mColor));
+
+  nsPresContext* presContext = aFrame->PresContext();
+  if (!presContext->BidiEnabled() && HasRTLChars(mText)) {
+    presContext->SetBidiEnabled();
+  }
+  nsLayoutUtils::DrawString(aFrame, *mFontMetrics, aCtx,
+                            mText.get(), mText.Length(), mPoint);
+}
+
 bool
 BulletRenderer::IsImageContainerAvailable(layers::LayerManager* aManager, uint32_t aFlags)
 {
   MOZ_ASSERT(IsImageType());
 
   return mImage->IsImageContainerAvailable(aManager, aFlags);
 }
 
-void
+bool
 BulletRenderer::CreateWebRenderCommandsForImage(nsDisplayItem* aItem,
                                                 wr::DisplayListBuilder& aBuilder,
                                                 wr::IpcResourceUpdateQueue& aResources,
                                                 const layers::StackingContextHelper& aSc,
                                                 mozilla::layers::WebRenderLayerManager* aManager,
                                                 nsDisplayListBuilder* aDisplayListBuilder)
 {
-  MOZ_ASSERT(IsImageType());
-
-  if (!mImage) {
-     return;
-  }
+  MOZ_RELEASE_ASSERT(IsImageType());
+  MOZ_RELEASE_ASSERT(mImage);
 
   uint32_t flags = aDisplayListBuilder->ShouldSyncDecodeImages() ?
                    imgIContainer::FLAG_SYNC_DECODE :
                    imgIContainer::FLAG_NONE;
 
+  // FIXME: is this check always redundant with the next one?
+  if (IsImageContainerAvailable(aManager, flags)) {
+    return false;
+  }
+
   RefPtr<layers::ImageContainer> container =
     mImage->GetImageContainer(aManager, flags);
   if (!container) {
-    return;
+    return false;
   }
 
   gfx::IntSize size;
   Maybe<wr::ImageKey> key = aManager->CommandBuilder().CreateImageKey(aItem, container, aBuilder, aResources,
                                                                       aSc, size, Nothing());
   if (key.isNothing()) {
-    return;
+    return true;  // Nothing to do
   }
 
   const int32_t appUnitsPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
   LayoutDeviceRect destRect = LayoutDeviceRect::FromAppUnits(mDest, appUnitsPerDevPixel);
   wr::LayoutRect dest = aSc.ToRelativeLayoutRect(destRect);
 
   aBuilder.PushImage(dest,
                      dest,
                      !aItem->BackfaceIsHidden(),
                      wr::ImageRendering::Auto,
                      key.value());
+
+  return true;
 }
 
-void
+bool
 BulletRenderer::CreateWebRenderCommandsForPath(nsDisplayItem* aItem,
                                                wr::DisplayListBuilder& aBuilder,
                                                wr::IpcResourceUpdateQueue& aResources,
                                                const layers::StackingContextHelper& aSc,
                                                mozilla::layers::WebRenderLayerManager* aManager,
                                                nsDisplayListBuilder* aDisplayListBuilder)
 {
   MOZ_ASSERT(IsPathType());
 
   if (!aManager->CommandBuilder().PushItemAsImage(aItem, aBuilder, aResources, aSc, aDisplayListBuilder)) {
     NS_WARNING("Fail to create WebRender commands for Bullet path.");
+    return false;
   }
+
+  return true;
 }
 
-void
+bool
 BulletRenderer::CreateWebRenderCommandsForText(nsDisplayItem* aItem,
                                                wr::DisplayListBuilder& aBuilder,
                                                wr::IpcResourceUpdateQueue& aResources,
                                                const layers::StackingContextHelper& aSc,
                                                mozilla::layers::WebRenderLayerManager* aManager,
                                                nsDisplayListBuilder* aDisplayListBuilder)
 {
   MOZ_ASSERT(IsTextType());
-  MOZ_ASSERT(mFont);
-  MOZ_ASSERT(!mGlyphs.IsEmpty());
 
-  const int32_t appUnitsPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
   bool dummy;
-  LayoutDeviceRect destRect = LayoutDeviceRect::FromAppUnits(
-          aItem->GetBounds(aDisplayListBuilder, &dummy), appUnitsPerDevPixel);
-  wr::LayoutRect wrDestRect = aSc.ToRelativeLayoutRect(destRect);
-
-  nsTArray<wr::GlyphInstance> wrGlyphs;
+  nsRect bounds = aItem->GetBounds(aDisplayListBuilder, &dummy);
 
-  for (layers::GlyphArray& glyphArray : mGlyphs) {
-    const auto& glyphs = glyphArray.glyphs();
-    wrGlyphs.SetLength(glyphs.Length());
+  if (bounds.IsEmpty()) {
+    return true;
+  }
 
-    for (size_t j = 0; j < glyphs.Length(); j++) {
-      wrGlyphs[j].index = glyphs[j].mIndex;
-      wrGlyphs[j].point = aSc.ToRelativeLayoutPoint(
-              LayoutDevicePoint::FromUnknownPoint(glyphs[j].mPosition));
-    }
+  RefPtr<TextDrawTarget> textDrawer = new TextDrawTarget(aBuilder, aSc, aManager, aItem, bounds);
+  RefPtr<gfxContext> captureCtx = gfxContext::CreateOrNull(textDrawer);
+  PaintTextToContext(aItem->Frame(), captureCtx, aItem);
+  textDrawer->TerminateShadows();
 
-    aManager->WrBridge()->PushGlyphs(aBuilder, wrGlyphs, mFont,
-                                     wr::ToColorF(glyphArray.color().value()),
-                                     aSc, wrDestRect, wrDestRect,
-                                     !aItem->BackfaceIsHidden());
-  }
+  return !textDrawer->HasUnsupportedFeatures();
 }
 
 class nsDisplayBullet final : public nsDisplayItem {
 public:
   nsDisplayBullet(nsDisplayListBuilder* aBuilder, nsBulletFrame* aFrame)
     : nsDisplayItem(aBuilder, aFrame)
   {
     MOZ_COUNT_CTOR(nsDisplayBullet);
@@ -674,27 +670,29 @@ nsDisplayBullet::BuildLayer(nsDisplayLis
 
 bool
 nsDisplayBullet::CreateWebRenderCommands(wr::DisplayListBuilder& aBuilder,
                                          wr::IpcResourceUpdateQueue& aResources,
                                          const StackingContextHelper& aSc,
                                          mozilla::layers::WebRenderLayerManager* aManager,
                                          nsDisplayListBuilder* aDisplayListBuilder)
 {
-  ContainerLayerParameters parameter;
-  if (GetLayerState(aDisplayListBuilder, aManager, parameter) != LAYER_ACTIVE) {
+  // FIXME: avoid needing to make this target if we're drawing text
+  // (non-trivial refactor of all this code)
+  RefPtr<gfxContext> screenRefCtx = gfxContext::CreateOrNull(
+    gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get());
+  Maybe<BulletRenderer> br = static_cast<nsBulletFrame*>(mFrame)->
+    CreateBulletRenderer(*screenRefCtx, ToReferenceFrame());
+
+  if (!br) {
     return false;
   }
 
-  if (!mBulletRenderer)
-    return false;
-
-  mBulletRenderer->CreateWebRenderCommands(this, aBuilder, aResources, aSc,
-                                           aManager, aDisplayListBuilder);
-  return true;
+  return br->CreateWebRenderCommands(this, aBuilder, aResources, aSc,
+                                     aManager, aDisplayListBuilder);
 }
 
 void nsDisplayBullet::Paint(nsDisplayListBuilder* aBuilder,
                             gfxContext* aCtx)
 {
   uint32_t flags = imgIContainer::FLAG_NONE;
   if (aBuilder->ShouldSyncDecodeImages()) {
     flags |= imgIContainer::FLAG_SYNC_DECODE;
--- a/layout/reftests/counter-style/reftest.list
+++ b/layout/reftests/counter-style/reftest.list
@@ -1,35 +1,35 @@
 == system-cyclic.html     system-cyclic-ref.html
-fails-if(webrender) == system-fixed.html      system-fixed-ref.html
-fails-if(webrender) == system-symbolic.html   system-symbolic-ref.html
+== system-fixed.html      system-fixed-ref.html
+== system-symbolic.html   system-symbolic-ref.html
 == system-alphabetic.html system-alphabetic-ref.html
 == system-numeric.html    system-numeric-ref.html
-fails-if(webrender) == system-additive.html   system-additive-ref.html
+== system-additive.html   system-additive-ref.html
 == system-extends.html    system-extends-ref.html
 == system-cyclic-invalid.html     system-common-invalid-ref.html
 == system-fixed-invalid.html      system-common-invalid2-ref.html
 == system-symbolic-invalid.html   system-common-invalid-ref.html
 == system-alphabetic-invalid.html system-common-invalid2-ref.html
 == system-numeric-invalid.html    system-common-invalid2-ref.html
 == system-additive-invalid.html   system-common-invalid-ref.html
 == system-extends-invalid.html    system-extends-invalid-ref.html
 == descriptor-negative.html descriptor-negative-ref.html
 == descriptor-prefix.html   descriptor-prefix-ref.html
-fails-if(webrender) == descriptor-suffix.html   descriptor-suffix-ref.html
+== descriptor-suffix.html   descriptor-suffix-ref.html
 == descriptor-range.html    descriptor-range-ref.html
 == descriptor-pad.html      descriptor-pad-ref.html
 == descriptor-fallback.html descriptor-fallback-ref.html
 == descriptor-symbols.html  descriptor-symbols-ref.html
 == descriptor-negative-invalid.html descriptor-negative-invalid-ref.html
 == descriptor-prefix-invalid.html   descriptor-prefix-invalid-ref.html
 == descriptor-suffix-invalid.html   descriptor-suffix-invalid-ref.html
 == descriptor-range-invalid.html    descriptor-range-invalid-ref.html
 == descriptor-pad-invalid.html      descriptor-pad-invalid-ref.html
 == descriptor-fallback.html         descriptor-fallback-ref.html
 == descriptor-symbols-invalid.html  descriptor-symbols-invalid-ref.html
 == name-case-sensitivity.html       name-case-sensitivity-ref.html
-fails-if(webrender) == dependent-builtin.html           dependent-builtin-ref.html
+== dependent-builtin.html           dependent-builtin-ref.html
 == redefine-builtin.html            redefine-builtin-ref.html
 == redefine-attr-mapping.html       redefine-attr-mapping-ref.html
 == disclosure-styles.html           disclosure-styles-ref.html
 == symbols-function.html            symbols-function-ref.html
 == symbols-function-invalid.html    symbols-function-invalid-ref.html
--- a/layout/reftests/counters/reftest.list
+++ b/layout/reftests/counters/reftest.list
@@ -63,17 +63,17 @@
 fails-if(xulRuntime.XPCOMABI.match(/arm/)) == counter-reset-integer-range.html counter-reset-integer-range-ref.html # bug 989718
 == counter-ua-limits-00.html counter-ua-limits-00-ref.html
 == counter-ua-limits-01.html counter-ua-limits-01-ref.html
 fails-if(xulRuntime.XPCOMABI.match(/arm/)) == counter-ua-limits-02.html counter-ua-limits-02-ref.html # bug 989718
 == counter-ua-limits-03.html counter-ua-limits-03-ref.html
 == counter-ua-limits-list-00.html counter-ua-limits-list-00-ref.html
 == counter-ua-limits-list-01.html counter-ua-limits-list-01-ref.html
 == multiple-thai-counters.html multiple-thai-counters-ref.html
-fails-if(webrender) == counter-suffix.html counter-suffix-ref.html
+== counter-suffix.html counter-suffix-ref.html
 == counter-cjk-decimal.html counter-cjk-decimal-ref.html
 == counter-japanese-informal.html counter-japanese-informal-ref.html
 == counter-japanese-formal.html counter-japanese-formal-ref.html
 == counter-korean-hangul-formal.html counter-korean-hangul-formal-ref.html
 == counter-korean-hanja-informal.html counter-korean-hanja-informal-ref.html
 == counter-korean-hanja-formal.html counter-korean-hanja-formal-ref.html
 == counter-simp-chinese-informal.html counter-simp-chinese-informal-ref.html
 == counter-simp-chinese-formal.html counter-simp-chinese-formal-ref.html
--- a/layout/reftests/css-display/display-contents-shadow-dom-1-ref.html
+++ b/layout/reftests/css-display/display-contents-shadow-dom-1-ref.html
@@ -37,14 +37,12 @@ span { color:blue; }
     <div>A<span>b</span>c</div>
     <div><span>b c</span>d</div>
     <div><span>a </span>b</div>
     <div><b>One</b><i>Two</i></div>
     <div><b>One</b><i>Two</i></div>
     <div><b>One</b><i>Two</i></div>
     <div><b>One</b><i>Two</i></div>
     <b style="color:blue">One</b><i style="color:blue">Two</i>Three
-    <span style="color:green">R</span>
     <div></div>
     <b style="color:green">V</b>
-    <!-- <b style="color:green">Y</b> -->
   </body>
 </html>
--- a/layout/reftests/css-display/display-contents-shadow-dom-1.html
+++ b/layout/reftests/css-display/display-contents-shadow-dom-1.html
@@ -41,22 +41,20 @@ div.after::after {content: " Y";}
     <div id="hostJ"></div>
     <span id="hostK"></span>
     <div id="hostL"></div>
     <div id="hostM"><i>Two</i><b>One</b></div>
     <div id="hostN"><i class="c">Two</i><b>One</b></div>
     <div id="hostO"><i>Two</i><b class="c">One</b></div>
     <div id="hostP"><i class="c">Two</i><b class="c">One</b></div>
     <div id="hostQ" class="c" style="color:blue"><i class="c">Two</i><b class="c">One</b></div>Three
-    <span id="hostR"><style scoped>:scope{color:green}</style></span>
     <div id="hostS" class="c"><span class="c">S</span></div>
     <div id="hostT" class="c">T</div>
     <div id="hostU"><span class="c">U</span></div>
     <div id="hostV" class="c" style="color:red"><b class="c" style="color:inherit">V</b></div>
-    <!-- TODO(bug 1021572?) <div id="hostY" class="c" style="color:red"><b>Y</b></div> -->
 
    <script>
       function shadow(id) {
         return document.getElementById(id).createShadowRoot();
       }
       function span(s) {
         var e = document.createElement("span");
         var t = document.createTextNode(s);
@@ -98,18 +96,16 @@ div.after::after {content: " Y";}
         shadow("hostJ").innerHTML = 'A<content select=".b"></content>';
         shadow("hostK").innerHTML = '<content select=".b"></content>';
         shadow("hostL").innerHTML = '<content select=".b"></content>';
         shadow("hostM").innerHTML = '<content select="b"></content><content select="i"></content>';
         shadow("hostN").innerHTML = '<content select="b"></content><content select="i"></content>';
         shadow("hostO").innerHTML = '<content select="b"></content><content select="i"></content>';
         shadow("hostP").innerHTML = '<content select="b"></content><content select="i"></content>';
         shadow("hostQ").innerHTML = '<content select="b"></content><content select="i"></content>';
-        shadow("hostR").innerHTML = '<content select="span"></content>';
-        // TODO(bug 1021572?) shadow("hostY").innerHTML = '<content select="b"><style scoped>:scope{color:green}</style></content>';
       }
 
       function tweak() {
         document.body.offsetHeight;
 
         host1.appendChild(span("1"));
         host2.appendChild(text("2"));
         host3.appendChild(span("3"));
@@ -207,18 +203,16 @@ div.after::after {content: " Y";}
         inner.className = "before";
         var e = contents(inner);
         e.className = "b";
         hostL.appendChild(e);
         var e = span("b");
         e.className = "b";
         hostL.appendChild(e);
 
-        hostR.appendChild(span("R"));
-
         document.body.offsetHeight;
         setTimeout(function() {
             shadow("hostS");
             shadow("hostT");
             shadow("hostU");
             shadow("hostV").innerHTML = '<z style="color:green"><content select="b"></content></z>';
 
             document.body.offsetHeight;
--- a/layout/reftests/css-display/reftest.list
+++ b/layout/reftests/css-display/reftest.list
@@ -12,17 +12,17 @@ fails-if(styloVsGecko||stylo) pref(layou
 fuzzy-if(winWidget,12,100) skip-if(styloVsGecko||stylo) pref(layout.css.scoped-style.enabled,true) == display-contents-style-inheritance-1-dom-mutations.html display-contents-style-inheritance-1-ref.html
 == display-contents-tables.xhtml display-contents-tables-ref.xhtml
 == display-contents-tables-2.xhtml display-contents-tables-ref.xhtml
 == display-contents-tables-3.xhtml display-contents-tables-3-ref.xhtml
 == display-contents-visibility-hidden.html display-contents-visibility-hidden-ref.html
 == display-contents-visibility-hidden-2.html display-contents-visibility-hidden-ref.html
 == display-contents-495385-2d.html display-contents-495385-2d-ref.html
 fuzzy-if(Android,7,3935) == display-contents-xbl.xhtml display-contents-xbl-ref.html
-fuzzy-if(Android,7,1186) skip-if(stylo||styloVsGecko) pref(dom.webcomponents.enabled,true) pref(layout.css.scoped-style.enabled,true) == display-contents-shadow-dom-1.html display-contents-shadow-dom-1-ref.html # stylo: bug 1409086
+fuzzy-if(Android,7,1186) pref(dom.webcomponents.enabled,true) == display-contents-shadow-dom-1.html display-contents-shadow-dom-1-ref.html
 fails-if(styloVsGecko) == display-contents-xbl-2.xul display-contents-xbl-2-ref.xul # bug 1408235
 fails-if(styloVsGecko) == display-contents-xbl-3.xul display-contents-xbl-3-ref.xul # bug 1408235
 skip == display-contents-xbl-4.xul display-contents-xbl-4-ref.xul # fails (not just asserts) due to bug 1089223
 asserts(0-1) fuzzy-if(Android,8,3216) == display-contents-fieldset.html display-contents-fieldset-ref.html # bug 1089223
 fails-if(styloVsGecko) == display-contents-xbl-5.xul display-contents-xbl-3-ref.xul # bug 1408235
 fails-if(!stylo) == display-contents-xbl-6.xhtml display-contents-xbl-6-ref.html # bug 1345809
 == display-contents-xbl-7.xhtml display-contents-xbl-7-ref.html
 == display-contents-list-item-child.html display-contents-list-item-child-ref.html
--- a/layout/reftests/forms/legend/reftest.list
+++ b/layout/reftests/forms/legend/reftest.list
@@ -1,4 +1,4 @@
 == legend.html legend-ref.html
-fuzzy-if(skiaContent,1,7) pref(dom.webcomponents.enabled,true) skip-if(stylo||styloVsGecko) == shadow-dom.html shadow-dom-ref.html # stylo: bug 1409086
+fuzzy-if(skiaContent,1,7) pref(dom.webcomponents.enabled,true) == shadow-dom.html shadow-dom-ref.html
 == 1273433.html 1273433-ref.html
 fails-if(styloVsGecko||stylo) == 1339287.html 1339287-ref.html
--- a/layout/reftests/forms/legend/shadow-dom.html
+++ b/layout/reftests/forms/legend/shadow-dom.html
@@ -69,32 +69,39 @@ div.after::after {content: " Y";}
         host2.appendChild(legend("2"));
         host3.appendChild(legend("3"));
         host4.appendChild(legend("4"));
 
         var e = legend("5");
         e.style.display = "contents";
         host5.appendChild(e);
 
+        document.body.offsetHeight;
+
         host6.appendChild(legend("6"));
 
         var e = legend("L");
         e.className = "b";
         host7.appendChild(e);
         var e = legend("7");
         e.className = "c";
+
+        document.body.offsetHeight;
+
         host7.appendChild(e);
 
         var e = legend("L");
         e.className = "b after";
         host8.appendChild(e);
         var e = legend("8");
         e.className = "c  before";
         host8.appendChild(e);
 
+        document.body.offsetHeight;
+
         var e = legend("L2");
         e.className = "b before after";
         host9.appendChild(e);
         var e = contents(legend(" L3"));
         e.className = "b before after";
         host9.appendChild(e);
         var e = legend("9");
         e.className = "c  before";
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/reframe-shadow-child-1.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<template id="tmpl">
+  <div style="display: table">
+    Some text
+    <span style="display: table-cell">something</span>
+    More text
+  </div>
+</template>
+<div id="host"></div>
+<script>
+  let shadowRoot = document.getElementById("host").createShadowRoot();
+  let tmpl = document.getElementById("tmpl");
+  shadowRoot.appendChild(document.importNode(tmpl.content, true));
+  document.body.offsetTop;
+  shadowRoot.firstElementChild.querySelector("span").remove();
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/reframe-shadow-child-2.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<template id="tmpl">
+  <div style="display: block">
+    Some text
+    More text
+  </div>
+</template>
+<div id="host"></div>
+<script>
+  let shadowRoot = document.getElementById("host").createShadowRoot();
+  let tmpl = document.getElementById("tmpl");
+  shadowRoot.appendChild(document.importNode(tmpl.content, true));
+  document.body.offsetTop;
+  shadowRoot.firstElementChild.style.display = "table";
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/reframe-shadow-child-ref.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<div style="display: table">
+  Some text
+  More text
+</div>
--- a/layout/reftests/webcomponents/reftest.list
+++ b/layout/reftests/webcomponents/reftest.list
@@ -1,15 +1,17 @@
 pref(dom.webcomponents.enabled,true) == cross-tree-selection-1.html cross-tree-selection-1-ref.html
 pref(dom.webcomponents.enabled,true) == basic-shadow-1.html basic-shadow-1-ref.html
 pref(dom.webcomponents.enabled,true) == basic-shadow-2.html basic-shadow-2-ref.html
 pref(dom.webcomponents.enabled,true) == basic-shadow-3.html basic-shadow-3-ref.html
 pref(dom.webcomponents.enabled,true) == basic-shadow-4.html basic-shadow-4-ref.html
 pref(dom.webcomponents.enabled,true) == basic-insertion-point-1.html basic-insertion-point-1-ref.html
 pref(dom.webcomponents.enabled,true) == basic-insertion-point-2.html basic-insertion-point-2-ref.html
-pref(dom.webcomponents.enabled,true) skip-if(stylo||styloVsGecko) == fallback-content-1.html fallback-content-1-ref.html # stylo: bug 1409088
+pref(dom.webcomponents.enabled,true) == fallback-content-1.html fallback-content-1-ref.html
 pref(dom.webcomponents.enabled,true) == remove-insertion-point-1.html remove-insertion-point-1-ref.html
 pref(dom.webcomponents.enabled,true) == nested-insertion-point-1.html nested-insertion-point-1-ref.html
-pref(dom.webcomponents.enabled,true) skip-if(stylo||styloVsGecko) == update-dist-node-descendants-1.html update-dist-node-descendants-1-ref.html
+pref(dom.webcomponents.enabled,true) == update-dist-node-descendants-1.html update-dist-node-descendants-1-ref.html
 pref(dom.webcomponents.enabled,true) fuzzy-if(Android,2,7) == input-transition-1.html input-transition-1-ref.html
 pref(dom.webcomponents.enabled,true) == dynamic-insertion-point-distribution-1.html dynamic-insertion-point-distribution-1-ref.html
-pref(dom.webcomponents.enabled,true) skip-if(stylo||styloVsGecko) == dynamic-insertion-point-distribution-2.html dynamic-insertion-point-distribution-2-ref.html # bug 1409136
+pref(dom.webcomponents.enabled,true) == dynamic-insertion-point-distribution-2.html dynamic-insertion-point-distribution-2-ref.html
 pref(dom.webcomponents.enabled,true) == remove-append-shadow-host-1.html remove-append-shadow-host-1-ref.html
+pref(dom.webcomponents.enabled,true) == reframe-shadow-child-1.html reframe-shadow-child-ref.html
+pref(dom.webcomponents.enabled,true) == reframe-shadow-child-2.html reframe-shadow-child-ref.html
--- a/layout/reftests/writing-mode/reftest.list
+++ b/layout/reftests/writing-mode/reftest.list
@@ -26,17 +26,17 @@ fuzzy-if(azureSkia,255,2700) == 1090168-
 == 1096224-1b.html 1096224-1-ref.html
 fails == 1102175-1a.html 1102175-1-ref.html
 == 1102175-1b.html 1102175-1-ref.html
 == 1103613-1.html 1103613-1-ref.html
 == 1105268-1-min-max-dimensions.html 1105268-1-min-max-dimensions-ref.html
 == 1105268-2-min-max-dimensions.html 1105268-2-min-max-dimensions-ref.html
 == 1106669-1-intrinsic-for-container.html 1106669-1-intrinsic-for-container-ref.html
 == 1108923-1-percentage-margins.html 1108923-1-percentage-margins-ref.html
-fails-if(webrender) == 1111944-1-list-marker.html 1111944-1-list-marker-ref.html
+== 1111944-1-list-marker.html 1111944-1-list-marker-ref.html
 fuzzy(116,94) fuzzy-if(winWidget,135,124) HTTP(..) == 1115916-1-vertical-metrics.html 1115916-1-vertical-metrics-ref.html
 == 1117210-1-vertical-baseline-snap.html 1117210-1-vertical-baseline-snap-ref.html
 random-if(webrender) == 1117227-1-text-overflow.html 1117227-1-text-overflow-ref.html
 == 1122366-1-margin-collapse.html 1122366-1-margin-collapse-ref.html
 == 1124636-1-fieldset-max-height.html 1124636-1-fieldset-max-height-ref.html
 == 1124636-2-fieldset-min-height.html 1124636-2-fieldset-min-height-ref.html
 
 == ua-style-sheet-margin-1.html ua-style-sheet-margin-1-ref.html
--- a/layout/style/crashtests/crashtests.list
+++ b/layout/style/crashtests/crashtests.list
@@ -105,17 +105,17 @@ load 894245-1.html
 load 915440.html
 load 927734-1.html
 load 930270-1.html
 load 930270-2.html
 load 945048-1.html
 load 972199-1.html
 load 989965-1.html
 load 992333-1.html
-pref(dom.webcomponents.enabled,true) skip-if(stylo) load 1017798-1.html # bug 1409086
+pref(dom.webcomponents.enabled,true) load 1017798-1.html # bug 1409086
 load 1028514-1.html
 load 1066089-1.html
 load 1074651-1.html
 load 1135534.html
 pref(dom.webcomponents.enabled,true) load 1089463-1.html
 pref(layout.css.expensive-style-struct-assertions.enabled,true) load 1136010-1.html
 pref(layout.css.expensive-style-struct-assertions.enabled,true) load 1146101-1.html
 load 1153693-1.html
--- a/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
@@ -634,19 +634,24 @@ ClearKeySessionManager::Decrypt(const In
 
   Buffer* buffer = mHost->Allocate(aBuffer.data_size);
   assert(buffer != nullptr);
   assert(buffer->Data() != nullptr);
   assert(buffer->Capacity() >= aBuffer.data_size);
 
   memcpy(buffer->Data(), aBuffer.data, aBuffer.data_size);
 
-  Status status = mDecryptionManager->Decrypt(buffer->Data(),
-                                              buffer->Size(),
-                                              CryptoMetaData(&aBuffer));
+  Status status = Status::kSuccess;
+  // According to the comment `If |iv_size| = 0, the data is unencrypted.`
+  // Use iv_size to determine if the sample is encrypted.
+  if (aBuffer.iv_size != 0) {
+    status = mDecryptionManager->Decrypt(buffer->Data(),
+                                         buffer->Size(),
+                                         CryptoMetaData(&aBuffer));
+  }
 
   aDecryptedBlock->SetDecryptedBuffer(buffer);
   aDecryptedBlock->SetTimestamp(aBuffer.timestamp);
 
   return status;
 }
 
 void
--- a/media/mtransport/transportlayerice.cpp
+++ b/media/mtransport/transportlayerice.cpp
@@ -94,16 +94,27 @@ TransportLayerIce::TransportLayerIce(con
 
 TransportLayerIce::~TransportLayerIce() {
   // No need to do anything here, since we use smart pointers
 }
 
 void TransportLayerIce::SetParameters(RefPtr<NrIceCtx> ctx,
                                       RefPtr<NrIceMediaStream> stream,
                                       int component) {
+  // Stream could be null in the case of some badly written js that causes
+  // us to be in an ICE restart case, but not have valid streams due to
+  // not calling PeerConnectionMedia::EnsureTransports if
+  // PeerConnectionImpl::SetSignalingState_m thinks the conditions were
+  // not correct.  We also solved a case where an incoming answer was
+  // incorrectly beginning an ICE restart when the offer did not indicate one.
+  if (!stream) {
+    MOZ_ASSERT(false);
+    return;
+  }
+
   // If SetParameters is called and we already have a stream_, this means
   // we're handling an ICE restart.  We need to hold the old stream until
   // we know the new stream is working.
   if (stream_ && !old_stream_ && (stream_ != stream)) {
     // Here we leave the old stream's signals connected until we don't need
     // it anymore.  They will be disconnected if ice restart is successful.
     old_stream_ = stream_;
     MOZ_MTLOG(ML_INFO, LAYER_INFO << "SetParameters save old stream("
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -722,16 +722,17 @@ JsepSessionImpl::GetRemoteIds(const Sdp&
   return rv;
 }
 
 nsresult
 JsepSessionImpl::CreateOffer(const JsepOfferOptions& options,
                              std::string* offer)
 {
   mLastError.clear();
+  mLocalIceIsRestarting = options.mIceRestart.isSome() && *(options.mIceRestart);
 
   if (mState != kJsepStateStable) {
     JSEP_SET_ERROR("Cannot create offer in state " << GetStateStr(mState));
     return NS_ERROR_UNEXPECTED;
   }
 
   // Undo track assignments from a previous call to CreateOffer
   // (ie; if the track has not been negotiated yet, it doesn't necessarily need
@@ -1989,16 +1990,25 @@ JsepSessionImpl::ValidateRemoteDescripti
 
     if (oldMsection.GetMediaType() != newMsection.GetMediaType()) {
       JSEP_SET_ERROR("Remote description changes the media type of m-line "
                      << i);
       return NS_ERROR_INVALID_ARG;
     }
 
     bool differ = mSdpHelper.IceCredentialsDiffer(newMsection, oldMsection);
+
+    // Detect bad answer ICE restart when offer doesn't request ICE restart
+    if (mIsOfferer && differ && !mLocalIceIsRestarting) {
+      JSEP_SET_ERROR("Remote description indicates ICE restart but offer did not "
+                     "request ICE restart (new remote description changes either "
+                     "the ice-ufrag or ice-pwd)");
+      return NS_ERROR_INVALID_ARG;
+    }
+
     // Detect whether all the creds are the same or all are different
     if (!iceCredsDiffer.isSome()) {
       // for the first msection capture whether creds are different or same
       iceCredsDiffer = mozilla::Some(differ);
     } else if (iceCredsDiffer.isSome() && *iceCredsDiffer != differ) {
       // subsequent msections must match the first sections
       JSEP_SET_ERROR("Partial ICE restart is unsupported at this time "
                      "(new remote description changes either the ice-ufrag "
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
@@ -29,16 +29,17 @@ public:
 class JsepSessionImpl : public JsepSession
 {
 public:
   JsepSessionImpl(const std::string& name, UniquePtr<JsepUuidGenerator> uuidgen)
       : JsepSession(name),
         mIsOfferer(false),
         mWasOffererLastTime(false),
         mIceControlling(false),
+        mLocalIceIsRestarting(false),
         mRemoteIsIceLite(false),
         mRemoteIceIsRestarting(false),
         mBundlePolicy(kBundleBalanced),
         mSessionId(0),
         mSessionVersion(0),
         mUuidGen(Move(uuidgen)),
         mSdpHelper(&mLastError)
   {
@@ -317,16 +318,17 @@ private:
   std::vector<RefPtr<JsepTransport> > mOldTransports;
   std::vector<JsepTrackPair> mNegotiatedTrackPairs;
 
   bool mIsOfferer;
   bool mWasOffererLastTime;
   bool mIceControlling;
   std::string mIceUfrag;
   std::string mIcePwd;
+  bool mLocalIceIsRestarting;
   bool mRemoteIsIceLite;
   bool mRemoteIceIsRestarting;
   std::vector<std::string> mIceOptions;
   JsepBundlePolicy mBundlePolicy;
   std::vector<JsepDtlsFingerprint> mDtlsFingerprints;
   uint64_t mSessionId;
   uint64_t mSessionVersion;
   std::vector<SdpExtmapAttributeList::Extmap> mAudioRtpExtensions;
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -269,16 +269,23 @@ PeerConnectionMedia::StunAddrsHandler::O
 {
   CSFLogInfo(LOGTAG, "%s: receiving (%d) stun addrs", __FUNCTION__,
                                                       (int)addrs.Length());
   if (pcm_) {
     pcm_->mStunAddrs = addrs;
     pcm_->mLocalAddrsCompleted = true;
     pcm_->mStunAddrsRequest = nullptr;
     pcm_->FlushIceCtxOperationQueueIfReady();
+    // If parent process returns 0 STUN addresses, change ICE connection
+    // state to failed.
+    if (!pcm_->mStunAddrs.Length()) {
+      pcm_->SignalIceConnectionStateChange(pcm_->mIceCtxHdlr->ctx().get(),
+                                           NrIceCtx::ICE_CTX_FAILED);
+    }
+
     pcm_ = nullptr;
   }
 }
 
 PeerConnectionMedia::PeerConnectionMedia(PeerConnectionImpl *parent)
     : mParent(parent),
       mParentHandle(parent->GetHandle()),
       mParentName(parent->GetName()),
@@ -951,16 +958,28 @@ PeerConnectionMedia::EnsureIceGathering_
   if (mProxyServer) {
     mIceCtxHdlr->ctx()->SetProxyServer(*mProxyServer);
   } else if (aProxyOnly) {
     IceGatheringStateChange_s(mIceCtxHdlr->ctx().get(),
                               NrIceCtx::ICE_CTX_GATHER_COMPLETE);
     return;
   }
 
+  // Make sure we don't call NrIceCtx::StartGathering if we're in e10s mode
+  // and we received no STUN addresses from the parent process.  In the
+  // absence of previously provided STUN addresses, StartGathering will
+  // attempt to gather them (as in non-e10s mode), and this will cause a
+  // sandboxing exception in e10s mode.
+  if (!mStunAddrs.Length() && XRE_IsContentProcess()) {
+    CSFLogInfo(LOGTAG,
+               "%s: No STUN addresses returned from parent process",
+               __FUNCTION__);
+    return;
+  }
+
   // Belt and suspenders - in e10s mode, the call below to SetStunAddrs
   // needs to have the proper flags set on ice ctx.  For non-e10s,
   // setting those flags happens in StartGathering.  We could probably
   // just set them here, and only do it here.
   mIceCtxHdlr->ctx()->SetCtxFlags(aDefaultRouteOnly, aProxyOnly);
 
   if (mStunAddrs.Length()) {
     mIceCtxHdlr->ctx()->SetStunAddrs(mStunAddrs);
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/SyncPreference.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/SyncPreference.java
@@ -59,26 +59,32 @@ class SyncPreference extends Preference 
                         // http://androidxref.com/5.1.1_r6/xref/frameworks/base/core/java/android/preference/Preference.java#562
                         setIcon(null);
                         setIcon(R.drawable.sync_avatar_default);
                 }
             });
             return;
         }
 
-        // Update title from account email.
+        final ExtendedJSONObject profileJSON = fxAccount.getProfileJSON();
+        final String displayName = profileJSON != null ? profileJSON.getString(FxAccountConstants.KEY_PROFILE_JSON_USERNAME) : null;
+
+        // Update title from account email/display name.
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
-                setTitle(fxAccount.getEmail());
-                setSummary("");
+                if (!TextUtils.isEmpty(displayName)) {
+                    setTitle(displayName);
+                } else {
+                    setTitle(R.string.pref_sync_default_title);
+                }
+                setSummary(fxAccount.getEmail());
             }
         });
 
-        final ExtendedJSONObject profileJSON = fxAccount.getProfileJSON();
         if (profileJSON == null) {
             return;
         }
 
         // Avatar URI empty, return early.
         final String avatarURI = profileJSON.getString(FxAccountConstants.KEY_PROFILE_JSON_AVATAR);
         if (TextUtils.isEmpty(avatarURI)) {
             return;
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -358,16 +358,17 @@
 <!ENTITY pref_zoom_force_enabled "Always enable zoom">
 <!ENTITY pref_zoom_force_enabled_summary "Force override so you can zoom any page">
 <!ENTITY pref_voice_input "Voice input">
 <!ENTITY pref_voice_input_summary2 "Allow voice dictation in the URL bar">
 <!ENTITY pref_qrcode_enabled "QR code reader">
 <!ENTITY pref_qrcode_enabled_summary2 "Allow QR scanner in the URL bar">
 
 <!ENTITY pref_use_master_password "Use master password">
+<!ENTITY pref_sync_default_title "Firefox Account">
 <!ENTITY pref_sync2 "Sign in">
 <!ENTITY pref_sync_summary2 "Sync your tabs, bookmarks, logins, history">
 <!ENTITY pref_search_suggestions "Show search suggestions">
 <!ENTITY pref_history_search_suggestions "Show search history">
 <!ENTITY pref_import_options "Import options">
 <!ENTITY pref_import_android_summary "Import bookmarks and history from the native browser">
 <!-- Localization note (pref_private_data_openTabs): Open tabs is an option in
      the Clear Private Data  dialog and refers to currently open tabs. -->
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -255,16 +255,17 @@
   <string name="pref_zoom_force_enabled_summary">&pref_zoom_force_enabled_summary;</string>
   <string name="pref_voice_input">&pref_voice_input;</string>
   <string name="pref_voice_input_summary">&pref_voice_input_summary2;</string>
   <string name="pref_qrcode_enabled">&pref_qrcode_enabled;</string>
   <string name="pref_qrcode_enabled_summary">&pref_qrcode_enabled_summary2;</string>
   <string name="pref_restore">&pref_restore_tabs;</string>
   <string name="pref_restore_always">&pref_restore_always;</string>
   <string name="pref_restore_quit">&pref_restore_quit;</string>
+  <string name="pref_sync_default_title">&pref_sync_default_title;</string>
   <string name="pref_sync">&pref_sync2;</string>
   <string name="pref_sync_summary">&pref_sync_summary2;</string>
   <string name="pref_search_suggestions">&pref_search_suggestions;</string>
   <string name="pref_history_search_suggestions">&pref_history_search_suggestions;</string>
   <string name="pref_private_data_openTabs">&pref_private_data_openTabs;</string>
   <string name="pref_private_data_history2">&pref_private_data_history2;</string>
   <string name="pref_private_data_searchHistory">&pref_private_data_searchHistory;</string>
   <string name="pref_private_data_formdata2">&pref_private_data_formdata2;</string>
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ConfigurableServer15Repository.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ConfigurableServer15Repository.java
@@ -23,43 +23,46 @@ import org.mozilla.gecko.sync.stage.Serv
  *
  */
 public class ConfigurableServer15Repository extends Server15Repository {
   private final String sortOrder;
   private final long batchLimit;
   private final ServerSyncStage.MultipleBatches multipleBatches;
   private final ServerSyncStage.HighWaterMark highWaterMark;
   private final boolean forceFullFetch;
+  private final boolean abortOnStoreFailure;
   public ConfigurableServer15Repository(
           String collection,
           long syncDeadline,
           String storageURL,
           AuthHeaderProvider authHeaderProvider,
           InfoCollections infoCollections,
           InfoConfiguration infoConfiguration,
           long batchLimit,
           String sort,
           ServerSyncStage.MultipleBatches multipleBatches,
           ServerSyncStage.HighWaterMark highWaterMark,
           RepositoryStateProvider stateProvider,
-          boolean forceFullFetch) throws URISyntaxException {
+          boolean forceFullFetch,
+          boolean abortOnStoreFailure) throws URISyntaxException {
     super(
             collection,
             syncDeadline,
             storageURL,
             authHeaderProvider,
             infoCollections,
             infoConfiguration,
             stateProvider
     );
     this.batchLimit = batchLimit;
     this.sortOrder  = sort;
     this.multipleBatches = multipleBatches;
     this.highWaterMark = highWaterMark;
     this.forceFullFetch = forceFullFetch;
+    this.abortOnStoreFailure = abortOnStoreFailure;
 
     // Sanity check: let's ensure we're configured correctly. At this point in time, it doesn't make
     // sense to use H.W.M. with a non-persistent state provider. This might change if we start retrying
     // during a download in case of 412s.
     if (!stateProvider.isPersistent() && highWaterMark.equals(ServerSyncStage.HighWaterMark.Enabled)) {
       throw new IllegalArgumentException("Can not use H.W.M. with NonPersistentRepositoryStateProvider");
     }
   }
@@ -84,9 +87,14 @@ public class ConfigurableServer15Reposit
     return highWaterMark.equals(ServerSyncStage.HighWaterMark.Enabled);
   }
 
   @Override
   public boolean getFullFetchForced() {
     return forceFullFetch;
   }
 
+  @Override
+  public boolean getAbortOnStoreFailure() {
+    return abortOnStoreFailure;
+  }
+
 }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server15Repository.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server15Repository.java
@@ -110,16 +110,20 @@ public class Server15Repository extends 
   public boolean getAllowHighWaterMark() {
     return false;
   }
 
   public boolean getFullFetchForced() {
     return false;
   }
 
+  public boolean getAbortOnStoreFailure() {
+    return false;
+  }
+
   /**
    * A point in time by which this repository's session must complete fetch and store operations.
    * Particularly pertinent for batching downloads performed by the session (should we fetch
    * another batch?) and buffered repositories (do we have enough time to merge what we've downloaded?).
    */
   public long getSyncDeadline() {
     return syncDeadlineMillis;
   }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server15RepositorySession.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server15RepositorySession.java
@@ -38,17 +38,17 @@ public class Server15RepositorySession e
   @Override
   public void setStoreDelegate(RepositorySessionStoreDelegate storeDelegate) {
     super.setStoreDelegate(storeDelegate);
 
     // Now that we have the delegate, we can initialize our uploader.
     this.uploader = new BatchingUploader(
             this, storeWorkQueue, storeDelegate, Uri.parse(serverRepository.collectionURI.toString()),
             serverRepository.getCollectionLastModified(), serverRepository.getInfoConfiguration(),
-            serverRepository.authHeaderProvider);
+            serverRepository.authHeaderProvider, serverRepository.getAbortOnStoreFailure());
   }
 
   private void fetchSince(long timestamp, RepositorySessionFetchRecordsDelegate delegate) {
     BatchingDownloaderController.resumeFetchSinceIfPossible(
             this.downloader,
             this.serverRepository.stateProvider,
             delegate,
             timestamp,
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BatchingUploader.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BatchingUploader.java
@@ -100,25 +100,33 @@ public class BatchingUploader {
     // string containing the data of the record."
     // Sync Storage servers place a hard limit on how large a payload _field_ might be, and so we
     // maintain this limit for a single sanity check.
     private final long maxPayloadFieldBytes;
 
     // Set if this channel should ignore further calls to process.
     private volatile boolean aborted = false;
 
+    // Whether or not we should set aborted if there are any issues with the record.
+    // This is used to prevent corruption with bookmark records, as uploading
+    // only a subset of the bookmarks is very likely to cause corruption, (e.g.
+    // uploading a parent without its children or vice versa).
+    @VisibleForTesting
+    protected final boolean shouldFailBatchOnFailure;
+
     public BatchingUploader(
             final RepositorySession repositorySession, final ExecutorService workQueue,
             final RepositorySessionStoreDelegate sessionStoreDelegate, final Uri baseCollectionUri,
             final Long localCollectionLastModified, final InfoConfiguration infoConfiguration,
-            final AuthHeaderProvider authHeaderProvider) {
+            final AuthHeaderProvider authHeaderProvider, final boolean shouldAbortOnFailure) {
         this.repositorySession = repositorySession;
         this.sessionStoreDelegate = sessionStoreDelegate;
         this.collectionUri = baseCollectionUri;
         this.authHeaderProvider = authHeaderProvider;
+        this.shouldFailBatchOnFailure = shouldAbortOnFailure;
 
         this.uploaderMeta = new UploaderMeta(
                 payloadLock, infoConfiguration.maxTotalBytes, infoConfiguration.maxTotalRecords);
         this.payload = new Payload(
                 payloadLock, infoConfiguration.maxPostBytes, infoConfiguration.maxPostRecords);
 
         this.payloadDispatcher = createPayloadDispatcher(workQueue, localCollectionLastModified);
 
@@ -228,42 +236,36 @@ public class BatchingUploader {
         payloadDispatcher.finalizeQueue(uploaderMeta.needToCommit(), new Runnable() {
             @Override
             public void run() {
                 flush(true, true);
             }
         });
     }
 
-    // We fail the batch for bookmark records because uploading only a subset of bookmark records is
-    // very likely to cause corruption (e.g. uploading a parent without its children or vice versa).
-    @VisibleForTesting
-    /* package-local */ boolean shouldFailBatchOnFailure(Record record) {
-        return record instanceof BookmarkRecord;
-    }
 
     /* package-local */ void setLastStoreTimestamp(AtomicLong lastModifiedTimestamp) {
         repositorySession.setLastStoreTimestamp(lastModifiedTimestamp.get());
     }
 
     /* package-local */ void finished() {
         sessionStoreDelegate.deferredStoreDelegate(executor).onStoreCompleted();
     }
 
     // Common handling for marking a record failure and calling our delegate's onRecordStoreFailed.
     private void failRecordStore(final Exception e, final Record record, boolean sizeOverflow) {
         // There are three cases we're handling here. See bug 1362206 for some rationale here.
-        // 1. If `record` is not a bookmark and it failed sanity checks for reasons other than
+        // 1. shouldFailBatchOnFailure is false, and it failed sanity checks for reasons other than
         //    "it's too large" (say, `record`'s json is 0 bytes),
         //     - Then mark record's store as 'failed' and continue uploading
-        // 2. If `record` is not a bookmark and it failed sanity checks because it's too large,
+        // 2. shouldFailBatchOnFailure is false, and it failed sanity checks because it's too large,
         //     - Continue uploading, and don't fail synchronization because of this one.
-        // 3. If `record` *is* a bookmark, and it failed for any reason
+        // 3. shouldFailBatchOnFailure is true, and it failed for any reason
         //     - Stop uploading.
-        if (shouldFailBatchOnFailure(record)) {
+        if (shouldFailBatchOnFailure) {
             // case 3
             Logger.debug(LOG_TAG, "Batch failed with exception: " + e.toString());
             // Start ignoring records, and send off to our delegate that we failed.
             aborted = true;
             executor.execute(new PayloadDispatcher.NonPayloadContextRunnable() {
                 @Override
                 public void run() {
                     sessionStoreDelegate.onRecordStoreFailed(e, record.guid);
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/BookmarksServerSyncStage.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/BookmarksServerSyncStage.java
@@ -77,17 +77,18 @@ public class BookmarksServerSyncStage ex
             session.getAuthHeaderProvider(),
             session.config.infoCollections,
             session.config.infoConfiguration,
             BOOKMARKS_BATCH_LIMIT,
             BOOKMARKS_SORT,
             getAllowedMultipleBatches(),
             getAllowedToUseHighWaterMark(),
             getRepositoryStateProvider(),
-            false
+            false,
+            true
     );
   }
 
   @Override
   protected Repository getLocalRepository() {
     return new BufferingMiddlewareRepository(
             session.getSyncDeadline(),
             new MemoryBufferStorage(),
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FormHistoryServerSyncStage.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FormHistoryServerSyncStage.java
@@ -71,16 +71,17 @@ public class FormHistoryServerSyncStage 
             session.getAuthHeaderProvider(),
             session.config.infoCollections,
             session.config.infoConfiguration,
             FORM_HISTORY_BATCH_LIMIT,
             FORM_HISTORY_SORT,
             getAllowedMultipleBatches(),
             getAllowedToUseHighWaterMark(),
             getRepositoryStateProvider(),
+            false,
             false
     );
   }
 
   @Override
   protected Repository getLocalRepository() {
     return new BufferingMiddlewareRepository(
             session.getSyncDeadline(),
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/HistoryServerSyncStage.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/HistoryServerSyncStage.java
@@ -89,16 +89,17 @@ public class HistoryServerSyncStage exte
             session.getAuthHeaderProvider(),
             session.config.infoCollections,
             session.config.infoConfiguration,
             HISTORY_BATCH_LIMIT,
             HISTORY_SORT,
             getAllowedMultipleBatches(),
             getAllowedToUseHighWaterMark(),
             getRepositoryStateProvider(),
+            false,
             false
     );
   }
 
   @Override
   protected RecordFactory getRecordFactory() {
     return new HistoryRecordFactory();
   }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/RecentHistoryServerSyncStage.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/RecentHistoryServerSyncStage.java
@@ -90,17 +90,19 @@ public class RecentHistoryServerSyncStag
                 session.getAuthHeaderProvider(),
                 session.config.infoCollections,
                 session.config.infoConfiguration,
                 HISTORY_BATCH_LIMIT,
                 HISTORY_SORT,
                 getAllowedMultipleBatches(),
                 getAllowedToUseHighWaterMark(),
                 getRepositoryStateProvider(),
-                false);
+                false,
+                false
+        );
     }
 
     /**
      * This stage is only enabled if full history session is enabled and did not complete a sync yet.
      */
     @Override
     public boolean isEnabled() throws MetaGlobalException {
         final boolean historyStageEnabled = super.isEnabled();
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/ValidateBookmarksSyncStage.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/ValidateBookmarksSyncStage.java
@@ -111,17 +111,18 @@ public class ValidateBookmarksSyncStage 
                 session.getAuthHeaderProvider(),
                 session.config.infoCollections,
                 session.config.infoConfiguration,
                 BOOKMARKS_BATCH_LIMIT,
                 BOOKMARKS_SORT,
                 getAllowedMultipleBatches(),
                 getAllowedToUseHighWaterMark(),
                 getRepositoryStateProvider(),
-                true
+                true,
+                false // We never do any storing, so this is irrelevant
         );
     }
 
     @Override
     protected Repository getLocalRepository() {
         TelemetryStageCollector bookmarkCollector =
                 this.telemetryStageCollector.getSyncCollector().collectorFor("bookmarks");
         return new BookmarksValidationRepository(session.getClientsDelegate(), bookmarkCollector);
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/uploaders/BatchingUploaderTest.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/uploaders/BatchingUploaderTest.java
@@ -606,35 +606,28 @@ public class BatchingUploaderTest {
             return new Runnable() {
                 @Override
                 public void run() {}
             };
         }
     }
 
     class MockUploader extends BatchingUploader {
-        boolean abortOnRecordFail;
         MockUploader(final RepositorySession repositorySession, final ExecutorService workQueue,
                      final RepositorySessionStoreDelegate sessionStoreDelegate, final Uri baseCollectionUri,
                      final Long localCollectionLastModified, final InfoConfiguration infoConfiguration,
                      final AuthHeaderProvider authHeaderProvider, final boolean abortOnRecordFail) {
             super(repositorySession, workQueue, sessionStoreDelegate, baseCollectionUri,
-                    localCollectionLastModified, infoConfiguration, authHeaderProvider);
-            this.abortOnRecordFail = abortOnRecordFail;
+                    localCollectionLastModified, infoConfiguration, authHeaderProvider, abortOnRecordFail);
         }
 
         @Override
         PayloadDispatcher createPayloadDispatcher(ExecutorService workQueue, Long localCollectionLastModified) {
             return new MockPayloadDispatcher(workQueue, this, localCollectionLastModified);
         }
-
-        @Override
-        boolean shouldFailBatchOnFailure(Record r) {
-            return abortOnRecordFail;
-        }
     }
 
     private Server15Repository makeConstrainedRepository(boolean firstSync) {
         InfoCollections infoCollections;
         if (firstSync) {
             infoCollections = new InfoCollections() {
                 @Override
                 public Long getTimestamp(String collection) {
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/uploaders/PayloadUploadDelegateTest.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/uploaders/PayloadUploadDelegateTest.java
@@ -137,17 +137,18 @@ public class PayloadUploadDelegateTest {
                 null,
                 new BatchingUploader(
                         repositorySession,
                         null,
                         sessionStoreDelegate,
                         Uri.parse("https://example.com"),
                         0L,
                         mock(InfoConfiguration.class),
-                        mock(AuthHeaderProvider.class)
+                        mock(AuthHeaderProvider.class),
+                        false
                 )
         );
 
         authHeaderProvider = mock(AuthHeaderProvider.class);
     }
 
     @Test
     public void testHandleRequestSuccessNonSuccess() {
--- a/security/apps/moz.build
+++ b/security/apps/moz.build
@@ -32,17 +32,17 @@ if CONFIG['GNU_CXX']:
     # Gecko headers aren't warning-free enough for us to enable these warnings.
     CXXFLAGS += [
         '-Wno-unused-parameter',
     ]
 
 test_ssl_path = '/security/manager/ssl/tests/unit'
 
 headers_arrays_certs = [
-    ('xpcshell.inc', 'xpcshellRoot', test_ssl_path + '/test_signed_apps/trusted_ca1.der'),
+    ('xpcshell.inc', 'xpcshellRoot', test_ssl_path + '/test_signed_apps/xpcshellTestRoot.der'),
     ('addons-public.inc', 'addonsPublicRoot', 'addons-public.crt'),
     ('addons-stage.inc', 'addonsStageRoot', 'addons-stage.crt'),
     ('privileged-package-root.inc', 'privilegedPackageRoot', 'privileged-package-root.der'),
 ]
 
 for header, array_name, cert in headers_arrays_certs:
     GENERATED_FILES += [header]
     h = GENERATED_FILES[header]
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -838,35 +838,16 @@ CertVerifier::VerifyCert(CERTCertificate
                             EndEntityOrCA::MustBeEndEntity,
                             KeyUsage::keyAgreement, // ECDH/DH
                             KeyPurposeId::id_kp_emailProtection,
                             CertPolicyId::anyPolicy, stapledOCSPResponse);
       }
       break;
     }
 
-    case certificateUsageObjectSigner: {
-      NSSCertDBTrustDomain trustDomain(trustObjectSigning, defaultOCSPFetching,
-                                       mOCSPCache, pinArg, ocspGETConfig,
-                                       mOCSPTimeoutSoft, mOCSPTimeoutHard,
-                                       mCertShortLifetimeInDays,
-                                       pinningDisabled, MIN_RSA_BITS_WEAK,
-                                       ValidityCheckingMode::CheckingOff,
-                                       SHA1Mode::Allowed,
-                                       NetscapeStepUpPolicy::NeverMatch,
-                                       originAttributes, builtChain, nullptr,
-                                       nullptr);
-      rv = BuildCertChain(trustDomain, certDER, time,
-                          EndEntityOrCA::MustBeEndEntity,
-                          KeyUsage::digitalSignature,
-                          KeyPurposeId::id_kp_codeSigning,
-                          CertPolicyId::anyPolicy, stapledOCSPResponse);
-      break;
-    }
-
     default:
       rv = Result::FATAL_ERROR_INVALID_ARGS;
   }
 
   if (rv != Success) {
     return rv;
   }
 
--- a/security/manager/locales/en-US/chrome/pipnss/pipnss.properties
+++ b/security/manager/locales/en-US/chrome/pipnss/pipnss.properties
@@ -41,17 +41,16 @@ Fips140SlotDescription=FIPS 140 Cryptogr
 # 32
 InternalToken=Software Security Device
 # End of size restriction.
 VerifySSLClient=SSL Client Certificate
 VerifySSLServer=SSL Server Certificate
 VerifySSLCA=SSL Certificate Authority
 VerifyEmailSigner=Email Signer Certificate
 VerifyEmailRecip=Email Recipient Certificate
-VerifyObjSign=Object Signer
 HighGrade=High Grade
 MediumGrade=Medium Grade
 # LOCALIZATION NOTE (nick_template): $1s is the common name from a cert (e.g. "Mozilla"), $2s is the CA name (e.g. VeriSign)
 nick_template=%1$s’s %2$s ID
 #These are the strings set for the ASN1 objects in a certificate.
 CertDumpCertificate=Certificate
 CertDumpVersion=Version
 # LOCALIZATION NOTE (CertDumpVersionValue): %S is a version number (e.g. "3" in "Version 3")
--- a/security/manager/pki/resources/content/certViewer.js
+++ b/security/manager/pki/resources/content/certViewer.js
@@ -87,38 +87,35 @@ function setWindowName() {
 }
 
 // Certificate usages we care about in the certificate viewer.
 const certificateUsageSSLClient              = 0x0001;
 const certificateUsageSSLServer              = 0x0002;
 const certificateUsageSSLCA                  = 0x0008;
 const certificateUsageEmailSigner            = 0x0010;
 const certificateUsageEmailRecipient         = 0x0020;
-const certificateUsageObjectSigner           = 0x0040;
 
 // A map from the name of a certificate usage to the value of the usage.
 // Useful for printing debugging information and for enumerating all supported
 // usages.
 const certificateUsages = {
   certificateUsageSSLClient,
   certificateUsageSSLServer,
   certificateUsageSSLCA,
   certificateUsageEmailSigner,
   certificateUsageEmailRecipient,
-  certificateUsageObjectSigner,
 };
 
 // Map of certificate usage name to localization identifier.
 const certificateUsageToStringBundleName = {
   certificateUsageSSLClient: "VerifySSLClient",
   certificateUsageSSLServer: "VerifySSLServer",
   certificateUsageSSLCA: "VerifySSLCA",
   certificateUsageEmailSigner: "VerifyEmailSigner",
   certificateUsageEmailRecipient: "VerifyEmailRecip",
-  certificateUsageObjectSigner: "VerifyObjSign",
 };
 
 const PRErrorCodeSuccess = 0;
 
 const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
 const SEC_ERROR_EXPIRED_CERTIFICATE                     = SEC_ERROR_BASE + 11;
 const SEC_ERROR_REVOKED_CERTIFICATE                     = SEC_ERROR_BASE + 12;
 const SEC_ERROR_UNKNOWN_ISSUER                          = SEC_ERROR_BASE + 13;
--- a/security/manager/ssl/nsNSSCertificate.cpp
+++ b/security/manager/ssl/nsNSSCertificate.cpp
@@ -637,93 +637,87 @@ nsNSSCertificate::GetOrganizationalUnit(
     UniquePORTString orgunit(CERT_GetOrgUnitName(&mCert->subject));
     if (orgunit) {
       aOrganizationalUnit = NS_ConvertUTF8toUTF16(orgunit.get());
     }
   }
   return NS_OK;
 }
 
+static nsresult
+UniqueCERTCertListToMutableArray(/*in*/ UniqueCERTCertList& nssChain,
+                                /*out*/ nsIArray** x509CertArray)
+{
+  if (!x509CertArray) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsCOMPtr<nsIMutableArray> array = nsArrayBase::Create();
+  if (!array) {
+    return NS_ERROR_FAILURE;
+  }
+
+  CERTCertListNode* node;
+  for (node = CERT_LIST_HEAD(nssChain.get());
+       !CERT_LIST_END(node, nssChain.get());
+       node = CERT_LIST_NEXT(node)) {
+    nsCOMPtr<nsIX509Cert> cert = nsNSSCertificate::Create(node->cert);
+    nsresult rv = array->AppendElement(cert, false);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  }
+
+  array.forget(x509CertArray);
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 nsNSSCertificate::GetChain(nsIArray** _rvChain)
 {
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown())
     return NS_ERROR_NOT_AVAILABLE;
 
   NS_ENSURE_ARG(_rvChain);
 
   mozilla::pkix::Time now(mozilla::pkix::Now());
 
   RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
   NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
 
   UniqueCERTCertList nssChain;
-  // We want to test all usages, but we start with server because most of the
-  // time Firefox users care about server certs.
-  if (certVerifier->VerifyCert(mCert.get(), certificateUsageSSLServer, now,
-                               nullptr, /*XXX fixme*/
-                               nullptr, /* hostname */
-                               nssChain,
-                               CertVerifier::FLAG_LOCAL_ONLY)
-        != mozilla::pkix::Success) {
-    nssChain = nullptr;
-    // keep going
-  }
-
-  // This is the whitelist of all non-SSLServer usages that are supported by
-  // verifycert.
-  const int otherUsagesToTest = certificateUsageSSLClient |
-                                certificateUsageSSLCA |
-                                certificateUsageEmailSigner |
-                                certificateUsageEmailRecipient |
-                                certificateUsageObjectSigner;
-  for (int usage = certificateUsageSSLClient;
-       usage < certificateUsageAnyCA && !nssChain;
-       usage = usage << 1) {
-    if ((usage & otherUsagesToTest) == 0) {
-      continue;
-    }
+  // We want to test all usages supported by the certificate verifier, but we
+  // start with TLS server because most of the time Firefox users care about
+  // server certs.
+  const int usagesToTest[] = { certificateUsageSSLServer,
+                               certificateUsageSSLClient,
+                               certificateUsageSSLCA,
+                               certificateUsageEmailSigner,
+                               certificateUsageEmailRecipient };
+  for (auto usage : usagesToTest) {
     if (certVerifier->VerifyCert(mCert.get(), usage, now,
                                  nullptr, /*XXX fixme*/
                                  nullptr, /*hostname*/
                                  nssChain,
                                  CertVerifier::FLAG_LOCAL_ONLY)
-          != mozilla::pkix::Success) {
-      nssChain = nullptr;
-      // keep going
+          == mozilla::pkix::Success) {
+      return UniqueCERTCertListToMutableArray(nssChain, _rvChain);
     }
   }
 
-  if (!nssChain) {
-    // There is not verified path for the chain, however we still want to
-    // present to the user as much of a possible chain as possible, in the case
-    // where there was a problem with the cert or the issuers.
-    nssChain = UniqueCERTCertList(
-      CERT_GetCertChainFromCert(mCert.get(), PR_Now(), certUsageSSLClient));
-  }
+  // There is no verified path for the chain, however we still want to
+  // present to the user as much of a possible chain as possible, in the case
+  // where there was a problem with the cert or the issuers.
+  nssChain = UniqueCERTCertList(
+    CERT_GetCertChainFromCert(mCert.get(), PR_Now(), certUsageSSLClient));
   if (!nssChain) {
     return NS_ERROR_FAILURE;
   }
-
-  // enumerate the chain for scripting purposes
-  nsCOMPtr<nsIMutableArray> array = nsArrayBase::Create();
-  if (!array) {
-    return NS_ERROR_FAILURE;
-  }
-  CERTCertListNode* node;
-  for (node = CERT_LIST_HEAD(nssChain.get());
-       !CERT_LIST_END(node, nssChain.get());
-       node = CERT_LIST_NEXT(node)) {
-    nsCOMPtr<nsIX509Cert> cert = nsNSSCertificate::Create(node->cert);
-    array->AppendElement(cert, false);
-  }
-  *_rvChain = array;
-  NS_IF_ADDREF(*_rvChain);
-  return NS_OK;
+  return UniqueCERTCertListToMutableArray(nssChain, _rvChain);
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetSubjectName(nsAString& _subjectName)
 {
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown())
     return NS_ERROR_NOT_AVAILABLE;
--- a/security/manager/ssl/tests/mochitest/browser/browser_certViewer.js
+++ b/security/manager/ssl/tests/mochitest/browser/browser_certViewer.js
@@ -34,34 +34,34 @@ add_task(async function testEmailEndEnti
   let win = await displayCertificate(cert);
   checkUsages(win, ["Email Recipient Certificate", "Email Signer Certificate"]);
   await BrowserTestUtils.closeWindow(win);
 });
 
 add_task(async function testCodeSignEndEntity() {
   let cert = await readCertificate("code-ee.pem", ",,");
   let win = await displayCertificate(cert);
-  checkUsages(win, ["Object Signer"]);
+  checkError(win, "Could not verify this certificate for unknown reasons.");
   await BrowserTestUtils.closeWindow(win);
 });
 
 add_task(async function testExpired() {
   let cert = await readCertificate("expired-ca.pem", ",,");
   let win = await displayCertificate(cert);
   checkError(win, "Could not verify this certificate because it has expired.");
   await BrowserTestUtils.closeWindow(win);
-});
 
-add_task(async function testIssuerExpired() {
-  let cert = await readCertificate("ee-from-expired-ca.pem", ",,");
-  let win = await displayCertificate(cert);
-  checkError(win,
+  // These tasks may run in any order, so we run this additional testcase in the
+  // same task.
+  let eeCert = await readCertificate("ee-from-expired-ca.pem", ",,");
+  let eeWin = await displayCertificate(eeCert);
+  checkError(eeWin,
              "Could not verify this certificate because the CA certificate " +
              "is invalid.");
-  await BrowserTestUtils.closeWindow(win);
+  await BrowserTestUtils.closeWindow(eeWin);
 });
 
 add_task(async function testUnknownIssuer() {
   let cert = await readCertificate("unknown-issuer.pem", ",,");
   let win = await displayCertificate(cert);
   checkError(win,
              "Could not verify this certificate because the issuer is " +
              "unknown.");
@@ -79,25 +79,25 @@ add_task(async function testInsecureAlgo
 });
 
 add_task(async function testUntrusted() {
   let cert = await readCertificate("untrusted-ca.pem", "p,p,p");
   let win = await displayCertificate(cert);
   checkError(win,
              "Could not verify this certificate because it is not trusted.");
   await BrowserTestUtils.closeWindow(win);
-});
 
-add_task(async function testUntrustedIssuer() {
-  let cert = await readCertificate("ee-from-untrusted-ca.pem", ",,");
-  let win = await displayCertificate(cert);
-  checkError(win,
+  // These tasks may run in any order, so we run this additional testcase in the
+  // same task.
+  let eeCert = await readCertificate("ee-from-untrusted-ca.pem", ",,");
+  let eeWin = await displayCertificate(eeCert);
+  checkError(eeWin,
              "Could not verify this certificate because the issuer is not " +
              "trusted.");
-  await BrowserTestUtils.closeWindow(win);
+  await BrowserTestUtils.closeWindow(eeWin);
 });
 
 add_task(async function testRevoked() {
   // Note that there's currently no way to un-do this. This should only be a
   // problem if another test re-uses a certificate with this same key (perhaps
   // likely) and subject (less likely).
   let certBlocklist = Cc["@mozilla.org/security/certblocklist;1"]
                         .getService(Ci.nsICertBlocklist);
@@ -105,17 +105,17 @@ add_task(async function testRevoked() {
     "MBIxEDAOBgNVBAMMB3Jldm9rZWQ=", // CN=revoked
     "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8="); // hash of the shared key
   let cert = await readCertificate("revoked.pem", ",,");
   let win = await displayCertificate(cert);
   // As of bug 1312827, OneCRL only applies to TLS web server certificates, so
   // this certificate will actually verify successfully for every end-entity
   // usage except TLS web server.
   checkUsages(win, ["Email Recipient Certificate", "Email Signer Certificate",
-                    "Object Signer", "SSL Client Certificate"]);
+                    "SSL Client Certificate"]);
   await BrowserTestUtils.closeWindow(win);
 });
 
 add_task(async function testInvalid() {
   // This certificate has a keyUsage extension asserting cRLSign and
   // keyCertSign, but it doesn't have a basicConstraints extension. This
   // shouldn't be valid for any usage. Sadly, we give a pretty lame error
   // message in this case.
--- a/security/manager/ssl/tests/unit/head_psm.js
+++ b/security/manager/ssl/tests/unit/head_psm.js
@@ -85,28 +85,26 @@ const MOZILLA_PKIX_ERROR_REQUIRED_TLS_FE
 const MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME              = MOZILLA_PKIX_ERROR_BASE + 12;
 
 // Supported Certificate Usages
 const certificateUsageSSLClient              = 0x0001;
 const certificateUsageSSLServer              = 0x0002;
 const certificateUsageSSLCA                  = 0x0008;
 const certificateUsageEmailSigner            = 0x0010;
 const certificateUsageEmailRecipient         = 0x0020;
-const certificateUsageObjectSigner           = 0x0040;
 
 // A map from the name of a certificate usage to the value of the usage.
 // Useful for printing debugging information and for enumerating all supported
 // usages.
 const allCertificateUsages = {
   certificateUsageSSLClient,
   certificateUsageSSLServer,
   certificateUsageSSLCA,
   certificateUsageEmailSigner,
   certificateUsageEmailRecipient,
-  certificateUsageObjectSigner,
 };
 
 const NO_FLAGS = 0;
 
 // Commonly certificates are represented as PEM. The format is roughly as
 // follows:
 //
 // -----BEGIN CERTIFICATE-----
--- a/security/manager/ssl/tests/unit/moz.build
+++ b/security/manager/ssl/tests/unit/moz.build
@@ -29,11 +29,12 @@ TEST_DIRS += [
     'test_keysize',
     'test_keysize_ev',
     'test_missing_intermediate',
     'test_name_constraints',
     'test_ocsp_fetch_method',
     'test_ocsp_url',
     'test_onecrl',
     'test_pinning_dynamic',
+    'test_signed_apps',
     'test_startcom_wosign',
     'test_validity',
 ]
new file mode 100755
--- /dev/null
+++ b/security/manager/ssl/tests/unit/pycms.py
@@ -0,0 +1,184 @@
+#!/usr/bin/env python
+#
+# 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/.
+
+"""
+Reads a specification from stdin and outputs a PKCS7 (CMS) message with
+the desired properties.
+
+The specification format is as follows:
+
+hash:<hex string>
+signer:
+<pycert specification>
+
+hash is the value that will be put in the messageDigest attribute in
+each SignerInfo of the signerInfos field of the SignedData.
+The certificate specification must come last.
+Currently only SHA-1 is supported.
+"""
+
+from pyasn1.codec.der import decoder
+from pyasn1.codec.der import encoder
+from pyasn1.type import tag, univ
+from pyasn1_modules import rfc2315, rfc2459
+import StringIO
+import base64
+import pycert
+import pykey
+import sys
+
+class Error(Exception):
+    """Base class for exceptions in this module."""
+    pass
+
+
+class UnknownDirectiveError(Error):
+    """Helper exception type to handle unknown specification
+    directives."""
+
+    def __init__(self, directive):
+        super(UnknownDirectiveError, self).__init__()
+        self.directive = directive
+
+    def __str__(self):
+        return 'Unknown directive %s' % repr(self.directive)
+
+
+class CMS(object):
+    """Utility class for reading a CMS specification and
+    generating a CMS message"""
+
+    def __init__(self, paramStream):
+        self.hash = ''
+        signerSpecification = StringIO.StringIO()
+        readingSignerSpecification = False
+        for line in paramStream.readlines():
+            if readingSignerSpecification:
+                print >>signerSpecification, line.strip()
+            elif line.strip() == 'signer:':
+                readingSignerSpecification = True
+            elif line.startswith('hash:'):
+                self.hash = line.strip()[len('hash:'):]
+            else:
+                raise UnknownDirectiveError(line.strip())
+        signerSpecification.seek(0)
+        self.signer = pycert.Certificate(signerSpecification)
+        self.signingKey = pykey.keyFromSpecification('default')
+
+    def buildAuthenticatedAttributes(self, value, implicitTag=None):
+        """Utility function to build a pyasn1 AuthenticatedAttributes
+        object. Useful because when building a SignerInfo, the
+        authenticatedAttributes needs to be tagged implicitly, but when
+        signing an AuthenticatedAttributes, it needs the explicit SET
+        tag."""
+        if implicitTag:
+            authenticatedAttributes = rfc2315.Attributes().subtype(implicitTag=implicitTag)
+        else:
+            authenticatedAttributes = rfc2315.Attributes()
+        contentTypeAttribute = rfc2315.Attribute()
+        # PKCS#9 contentType
+        contentTypeAttribute['type'] = univ.ObjectIdentifier('1.2.840.113549.1.9.3')
+        contentTypeAttribute['values'] = univ.SetOf(rfc2459.AttributeValue())
+        # PKCS#7 data
+        contentTypeAttribute['values'][0] = univ.ObjectIdentifier('1.2.840.113549.1.7.1')
+        authenticatedAttributes[0] = contentTypeAttribute
+        hashAttribute = rfc2315.Attribute()
+        # PKCS#9 messageDigest
+        hashAttribute['type'] = univ.ObjectIdentifier('1.2.840.113549.1.9.4')
+        hashAttribute['values'] = univ.SetOf(rfc2459.AttributeValue())
+        hashAttribute['values'][0] = univ.OctetString(hexValue=value)
+        authenticatedAttributes[1] = hashAttribute
+        return authenticatedAttributes
+
+    def pykeyHashToDigestAlgorithm(self, pykeyHash):
+        """Given a pykey hash algorithm identifier, builds an
+        AlgorithmIdentifier for use with pyasn1."""
+        if pykeyHash == pykey.HASH_SHA1:
+            algorithmIdentifier = rfc2459.AlgorithmIdentifier()
+            algorithmIdentifier['algorithm'] = univ.ObjectIdentifier('1.3.14.3.2.26')
+            algorithmIdentifier['parameters'] = univ.Null()
+            return algorithmIdentifier
+        raise pykey.UnknownHashAlgorithmError(pykeyHash)
+
+    def buildSignerInfo(self, certificate, pykeyHash, digestValue):
+        """Given a pyasn1 certificate, a pykey hash identifier
+        and a hash value, creates a SignerInfo with the
+        appropriate values."""
+        signerInfo = rfc2315.SignerInfo()
+        signerInfo['version'] = 1
+        issuerAndSerialNumber = rfc2315.IssuerAndSerialNumber()
+        issuerAndSerialNumber['issuer'] = self.signer.getIssuer()
+        issuerAndSerialNumber['serialNumber'] = certificate['tbsCertificate']['serialNumber']
+        signerInfo['issuerAndSerialNumber'] = issuerAndSerialNumber
+        signerInfo['digestAlgorithm'] = self.pykeyHashToDigestAlgorithm(pykeyHash)
+        rsa = rfc2459.AlgorithmIdentifier()
+        rsa['algorithm'] = rfc2459.rsaEncryption
+        rsa['parameters'] = univ.Null()
+        authenticatedAttributes = self.buildAuthenticatedAttributes(digestValue,
+          implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))
+        authenticatedAttributesTBS = self.buildAuthenticatedAttributes(digestValue)
+        signerInfo['authenticatedAttributes'] = authenticatedAttributes
+        signerInfo['digestEncryptionAlgorithm'] = rsa
+        authenticatedAttributesEncoded = encoder.encode(authenticatedAttributesTBS)
+        signature = self.signingKey.sign(authenticatedAttributesEncoded, pykeyHash)
+        # signature will be a hexified bit string of the form
+        # "'<hex bytes>'H". For some reason that's what BitString wants,
+        # but since this is an OCTET STRING, we have to strip off the
+        # quotation marks and trailing "H".
+        signerInfo['encryptedDigest'] = univ.OctetString(hexValue=signature[1:-2])
+        return signerInfo
+
+    def toDER(self):
+        contentInfo = rfc2315.ContentInfo()
+        contentInfo['contentType'] = rfc2315.signedData
+
+        signedData = rfc2315.SignedData()
+        signedData['version'] = rfc2315.Version(1)
+
+        digestAlgorithms = rfc2315.DigestAlgorithmIdentifiers()
+        digestAlgorithms[0] = self.pykeyHashToDigestAlgorithm(pykey.HASH_SHA1)
+        signedData['digestAlgorithms'] = digestAlgorithms
+
+        dataContentInfo = rfc2315.ContentInfo()
+        dataContentInfo['contentType'] = rfc2315.data
+        signedData['contentInfo'] = dataContentInfo
+
+        certificates = rfc2315.ExtendedCertificatesAndCertificates().subtype(
+            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))
+        extendedCertificateOrCertificate = rfc2315.ExtendedCertificateOrCertificate()
+        certificate = decoder.decode(self.signer.toDER(),
+            asn1Spec=rfc2459.Certificate())[0]
+        extendedCertificateOrCertificate['certificate'] = certificate
+        certificates[0] = extendedCertificateOrCertificate
+        signedData['certificates'] = certificates
+
+        signerInfos = rfc2315.SignerInfos()
+
+        signerInfos[0] = self.buildSignerInfo(certificate, pykey.HASH_SHA1, self.hash)
+        signedData['signerInfos'] = signerInfos
+
+        encoded = encoder.encode(signedData)
+        anyTag = univ.Any(encoded).subtype(
+            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))
+
+        contentInfo['content'] = anyTag
+        return encoder.encode(contentInfo)
+
+    def toPEM(self):
+        output = '-----BEGIN PKCS7-----'
+        der = self.toDER()
+        b64 = base64.b64encode(der)
+        while b64:
+            output += '\n' + b64[:64]
+            b64 = b64[64:]
+        output += '\n-----END PKCS7-----'
+        return output
+
+
+# When run as a standalone program, this will read a specification from
+# stdin and output the certificate as PEM to stdout.
+if __name__ == '__main__':
+    print CMS(sys.stdin).toPEM()
old mode 100644
new mode 100755
rename from security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/sign_b2g_app.py
rename to security/manager/ssl/tests/unit/sign_app.py
--- a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/sign_b2g_app.py
+++ b/security/manager/ssl/tests/unit/sign_app.py
@@ -1,176 +1,85 @@
-# flake8: noqa
+#!/usr/bin/env python
+#
+# 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/.
 
-import argparse
+"""
+Given a directory of files, packages them up and signs the
+resulting zip file. Mainly for creating test inputs to the
+nsIX509CertDB.openSignedAppFileAsync API.
+"""
 from base64 import b64encode
 from hashlib import sha1
-import sys
+import StringIO
+import argparse
+import os
+import pycms
+import re
 import zipfile
-import ctypes
-
-import nss_ctypes
-
-# Change the limits in JarSignatureVerification.cpp when you change the limits
-# here.
-max_entry_uncompressed_len = 100 * 1024 * 1024
-max_total_uncompressed_len = 500 * 1024 * 1024
-max_entry_count = 100 * 1000
-max_entry_filename_len = 1024
-max_mf_len = max_entry_count * 50
-max_sf_len = 1024
-
-
-
-def nss_load_cert(nss_db_dir, nss_password, cert_nickname):
-  nss_ctypes.NSS_Init(nss_db_dir)
-  try:
-    wincx = nss_ctypes.SetPasswordContext(nss_password)
-    cert = nss_ctypes.PK11_FindCertFromNickname(cert_nickname, wincx)
-    return (wincx, cert)
-  except:
-    nss_ctypes.NSS_Shutdown()
-    raise
 
-def nss_create_detached_signature(cert, dataToSign, wincx):
-  certdb = nss_ctypes.CERT_GetDefaultCertDB()
-  p7 = nss_ctypes.SEC_PKCS7CreateSignedData(cert,
-                                            nss_ctypes.certUsageObjectSigner,
-                                            certdb,
-                                            nss_ctypes.SEC_OID_SHA1,
-                                            sha1(dataToSign).digest(),
-                                            wincx       )
-  try:
-    nss_ctypes.SEC_PKCS7AddSigningTime(p7)
-    nss_ctypes.SEC_PKCS7IncludeCertChain(p7, wincx)
-    return nss_ctypes.SEC_PKCS7Encode(p7, None, wincx)
-  finally:
-    nss_ctypes.SEC_PKCS7DestroyContentInfo(p7)
-
-# We receive a ids_json string for the toBeSigned app
-def sign_zip(in_zipfile_name, out_zipfile_name, cert, wincx, ids_json):
-  mf_entries = []
-  seen_entries = set()
+def walkDirectory(directory):
+    """Given a relative path to a directory, enumerates the
+    files in the tree rooted at that location. Returns a list
+    of pairs of paths to those files. The first in each pair
+    is the full path to the file. The second in each pair is
+    the path to the file relative to the directory itself."""
+    paths = []
+    for path, dirs, files in os.walk(directory):
+        for f in files:
+            fullPath = os.path.join(path, f)
+            internalPath = re.sub(r'^/', '', fullPath.replace(directory, ''))
+            paths.append((fullPath, internalPath))
+    return paths
 
-  total_uncompressed_len = 0
-  entry_count = 0
-  with zipfile.ZipFile(out_zipfile_name, 'w') as out_zip:
-    with zipfile.ZipFile(in_zipfile_name, 'r') as in_zip:
-      for entry_info in in_zip.infolist():
-        name = entry_info.filename
+def signZip(appDirectory, outputFile, issuerName, doSign):
+    """Given a directory containing the files to package up,
+    an output filename to write to, the name of the issuer of
+    the signing certificate, and whether or not to actually
+    sign the resulting package, packages up the files in the
+    directory and creates the output as appropriate."""
+    mfEntries = []
 
-        # Check for reserved and/or insane (potentially malicious) names
-        if name.endswith("/"):
-          pass
-          # Do nothing; we don't copy directory entries since they are just a
-          # waste of space.
-        elif name.lower().startswith("meta-inf/"):
-          # META-INF/* is reserved for our use
-          raise ValueError("META-INF entries are not allowed: %s" % (name))
-        elif len(name) > max_entry_filename_len:
-          raise ValueError("Entry's filename is too long: %s" % (name))
-        # TODO: elif name has invalid characters...
-        elif name in seen_entries:
-          # It is possible for a zipfile to have duplicate entries (with the exact
-          # same filenames). Python's zipfile module accepts them, but our zip
-          # reader in Gecko cannot do anything useful with them, and there's no
-          # sane reason for duplicate entries to exist, so reject them.
-          raise ValueError("Duplicate entry in input file: %s" % (name))
-        else:
-          entry_count += 1
-          if entry_count > max_entry_count:
-            raise ValueError("Too many entries in input archive")
-
-          seen_entries.add(name)
+    with zipfile.ZipFile(outputFile, 'w') as outZip:
+        for (fullPath, internalPath) in walkDirectory(appDirectory):
+            with open(fullPath) as inputFile:
+                contents = inputFile.read()
+            outZip.writestr(internalPath, contents)
 
-          # Read in the input entry, but be careful to avoid going over the
-          # various limits we have, to minimize the likelihood that we'll run
-          # out of memory. Note that we can't use the length from entry_info
-          # because that might not be accurate if the input zip file is
-          # maliciously crafted to contain misleading metadata.
-          with in_zip.open(name, 'r') as entry_file:
-            contents = entry_file.read(max_entry_uncompressed_len + 1)
-          if len(contents) > max_entry_uncompressed_len:
-            raise ValueError("Entry is too large: %s" % (name))
-          total_uncompressed_len += len(contents)
-          if total_uncompressed_len > max_total_uncompressed_len:
-            raise ValueError("Input archive is too large")
-
-          # Copy the entry, using the same compression as used in the input file
-          out_zip.writestr(entry_info, contents)
+            # Add the entry to the manifest we're building
+            base64hash = b64encode(sha1(contents).digest())
+            mfEntries.append(
+                'Name: %s\nSHA1-Digest: %s\n' % (internalPath, base64hash))
 
-          # Add the entry to the manifest we're building
-          mf_entries.append('Name: %s\nSHA1-Digest: %s\n'
-                                % (name, b64encode(sha1(contents).digest())))
-    if (ids_json):
-      mf_entries.append('Name: %s\nSHA1-Digest: %s\n'
-                        % ("META-INF/ids.json", b64encode(sha1(ids_json).digest())))
-
-    mf_contents = 'Manifest-Version: 1.0\n\n' + '\n'.join(mf_entries)
-    if len(mf_contents) > max_mf_len:
-      raise ValueError("Generated MANIFEST.MF is too large: %d" % (len(mf_contents)))
-
-    sf_contents = ('Signature-Version: 1.0\nSHA1-Digest-Manifest: %s\n'
-                                % (b64encode(sha1(mf_contents).digest())))
-    if len(sf_contents) > max_sf_len:
-      raise ValueError("Generated SIGNATURE.SF is too large: %d"
-                          % (len(mf_contents)))
-
-    p7 = nss_create_detached_signature(cert, sf_contents, wincx)
-
-    # write the signature, SF, and MF
-    out_zip.writestr("META-INF/A.RSA", p7, zipfile.ZIP_DEFLATED)
-    out_zip.writestr("META-INF/A.SF",  sf_contents, zipfile.ZIP_DEFLATED)
-    out_zip.writestr("META-INF/MANIFEST.MF", mf_contents, zipfile.ZIP_DEFLATED)
-    if (ids_json):
-        out_zip.writestr("META-INF/ids.json", ids_json, zipfile.ZIP_DEFLATED)
+        # Just exit early if we're not actually signing.
+        if not doSign:
+            return
 
-def main():
-  parser = argparse.ArgumentParser(description='Sign a B2G app.')
-  parser.add_argument('-d', action='store',
-                            required=True, help='NSS database directory')
-  parser.add_argument('-f', action='store',
-                            type=argparse.FileType('rb'),
-                            required=True, help='password file')
-  parser.add_argument('-k', action='store',
-                            required=True, help="nickname of signing cert.")
-  parser.add_argument('-i', action='store', type=argparse.FileType('rb'),
-                            required=True, help="input JAR file (unsigned)")
-  parser.add_argument('-o', action='store', type=argparse.FileType('wb'),
-                            required=True, help="output JAR file (signed)")
-  parser.add_argument('-I', '--ids-file', action='store', type=argparse.FileType('rb'),
-                     help="Path to the ids.json file", dest='I')
-  parser.add_argument('-S', '--storeId', action='store',
-                      help="Store Id for the package", dest='S')
-  parser.add_argument('-V', '--storeVersion', action='store', type=int,
-                      help="Package Version", dest='V')
-  args = parser.parse_args()
-
-  # Sadly nested groups and neccesarily inclusive groups (http://bugs.python.org/issue11588)
-  # are not implemented. Note that this means the automatic help is slighty incorrect
-  if not((not args.I and args.V and args.S) or (args.I and not args.V and not args.S)):
-      raise ValueError("Either -I or -S and -V must be specified")
+        mfContents = 'Manifest-Version: 1.0\n\n' + '\n'.join(mfEntries)
+        base64hash = b64encode(sha1(mfContents).digest())
+        sfContents = 'Signature-Version: 1.0\nSHA1-Digest-Manifest: %s\n' % base64hash
+        cmsSpecification = 'hash:%s\nsigner:\n' % sha1(sfContents).hexdigest() + \
+            'issuer:%s\n' % issuerName + \
+            'subject:xpcshell signed app test signer\n' + \
+            'extension:keyUsage:digitalSignature'
+        cmsSpecificationStream = StringIO.StringIO()
+        print >>cmsSpecificationStream, cmsSpecification
+        cmsSpecificationStream.seek(0)
+        cms = pycms.CMS(cmsSpecificationStream)
+        p7 = cms.toDER()
+        outZip.writestr('META-INF/A.RSA', p7)
+        outZip.writestr('META-INF/A.SF', sfContents)
+        outZip.writestr('META-INF/MANIFEST.MF', mfContents)
 
-  if (args.I):
-    ids_contents = args.I.read(max_entry_uncompressed_len+1)
-  else:
-    ids_contents = '''{
-  "id": "%(id)s",
-  "version": %(version)d
-}
-''' % {"id": args.S, "version": args.V}
-  if len(ids_contents) > max_entry_uncompressed_len:
-    raise ValueError("Entry is too large: %s" % (name))
-
-  db_dir = args.d
-  password = args.f.readline().strip()
-  cert_nickname = args.k
-
-  (wincx, cert) = nss_load_cert(db_dir, password, cert_nickname)
-  try:
-    sign_zip(args.i, args.o, cert, wincx, ids_contents)
-    return 0
-  finally:
-    nss_ctypes.CERT_DestroyCertificate(cert)
-    nss_ctypes.NSS_Shutdown()
-
-if __name__ == "__main__":
-    sys.exit(main())
+def main(outputFile, appPath, *args):
+    """Main entrypoint. Given an already-opened file-like
+    object, a path to the app directory to sign, and some
+    optional arguments, signs the contents of the directory and
+    writes the resulting package to the 'file'."""
+    parser = argparse.ArgumentParser(description='Sign an app.')
+    parser.add_argument('-n', '--no-sign', action='store_true',
+                        help='Don\'t actually sign - only create zip')
+    parser.add_argument('-i', '--issuer', action='store', help='Issuer name',
+                        default='xpcshell signed apps test root')
+    parsed = parser.parse_args(args)
+    signZip(appPath, outputFile, parsed.issuer, not parsed.no_sign)
--- a/security/manager/ssl/tests/unit/test_cert_keyUsage.js
+++ b/security/manager/ssl/tests/unit/test_cert_keyUsage.js
@@ -11,18 +11,18 @@ var certdb = Cc["@mozilla.org/security/x
 
 const caList = [ "ca-no-keyUsage-extension", "ca-missing-keyCertSign",
                  "ca-all-usages" ];
 const eeList = [ "ee-no-keyUsage-extension", "ee-keyCertSign-only",
                  "ee-keyEncipherment-only", "ee-keyCertSign-and-keyEncipherment" ];
 
 const caUsage = [ certificateUsageSSLCA ];
 const allEEUsages = [ certificateUsageSSLClient, certificateUsageSSLServer,
-                      certificateUsageEmailSigner, certificateUsageEmailRecipient,
-                      certificateUsageObjectSigner ];
+                      certificateUsageEmailSigner,
+                      certificateUsageEmailRecipient ];
 const serverEEUsages = [ certificateUsageSSLServer,
                          certificateUsageEmailRecipient ];
 
 const expectedUsagesMap = {
   "ca-no-keyUsage-extension": caUsage,
   "ca-missing-keyCertSign": [],
   "ca-all-usages": caUsage,
 
--- a/security/manager/ssl/tests/unit/test_cert_trust.js
+++ b/security/manager/ssl/tests/unit/test_cert_trust.js
@@ -31,34 +31,30 @@ function test_ca_distrust(ee_cert, cert_
   checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
                         certificateUsageSSLClient);
   checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_CA_CERT_INVALID,
                         certificateUsageSSLCA);
   checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
                         certificateUsageEmailSigner);
   checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
                         certificateUsageEmailRecipient);
-  checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
-                        certificateUsageObjectSigner);
 
 
   // Test of active distrust. No usage should pass.
   setCertTrust(cert_to_modify_trust, "p,p,p");
   checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_UNTRUSTED_ISSUER,
                         certificateUsageSSLServer);
   checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_UNTRUSTED_ISSUER,
                         certificateUsageSSLClient);
   checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_CA_CERT_INVALID,
                         certificateUsageSSLCA);
   checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_UNTRUSTED_ISSUER,
                         certificateUsageEmailSigner);
   checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_UNTRUSTED_ISSUER,
                         certificateUsageEmailRecipient);
-  checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_UNTRUSTED_ISSUER,
-                        certificateUsageObjectSigner);
 
   // Trust set to T  -  trusted CA to issue client certs, where client cert is
   // usageSSLClient.
   setCertTrust(cert_to_modify_trust, "T,T,T");
   checkCertErrorGeneric(certdb, ee_cert, isRootCA ? SEC_ERROR_UNKNOWN_ISSUER
                                                   : PRErrorCodeSuccess,
                         certificateUsageSSLServer);
 
@@ -71,69 +67,60 @@ function test_ca_distrust(ee_cert, cert_
                         certificateUsageSSLCA);
 
   checkCertErrorGeneric(certdb, ee_cert, isRootCA ? SEC_ERROR_UNKNOWN_ISSUER
                                                   : PRErrorCodeSuccess,
                         certificateUsageEmailSigner);
   checkCertErrorGeneric(certdb, ee_cert, isRootCA ? SEC_ERROR_UNKNOWN_ISSUER
                                                   : PRErrorCodeSuccess,
                         certificateUsageEmailRecipient);
-  checkCertErrorGeneric(certdb, ee_cert, isRootCA ? SEC_ERROR_UNKNOWN_ISSUER
-                                                  : PRErrorCodeSuccess,
-                        certificateUsageObjectSigner);
 
 
   // Now tests on the SSL trust bit
   setCertTrust(cert_to_modify_trust, "p,C,C");
   checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_UNTRUSTED_ISSUER,
                         certificateUsageSSLServer);
 
   // XXX(Bug 982340)
   checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
                         certificateUsageSSLClient);
   checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_CA_CERT_INVALID,
                         certificateUsageSSLCA);
   checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
                         certificateUsageEmailSigner);
   checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
                         certificateUsageEmailRecipient);
-  checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
-                        certificateUsageObjectSigner);
 
   // Inherited trust SSL
   setCertTrust(cert_to_modify_trust, ",C,C");
   checkCertErrorGeneric(certdb, ee_cert, isRootCA ? SEC_ERROR_UNKNOWN_ISSUER
                                                   : PRErrorCodeSuccess,
                         certificateUsageSSLServer);
   // XXX(Bug 982340)
   checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
                         certificateUsageSSLClient);
   checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_CA_CERT_INVALID,
                         certificateUsageSSLCA);
   checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
                         certificateUsageEmailSigner);
   checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
                         certificateUsageEmailRecipient);
-  checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
-                        certificateUsageObjectSigner);
 
   // Now tests on the EMAIL trust bit
   setCertTrust(cert_to_modify_trust, "C,p,C");
   checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
                         certificateUsageSSLServer);
   checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_UNTRUSTED_ISSUER,
                         certificateUsageSSLClient);
   checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_CA_CERT_INVALID,
                         certificateUsageSSLCA);
   checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_UNTRUSTED_ISSUER,
                         certificateUsageEmailSigner);
   checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_UNTRUSTED_ISSUER,
                         certificateUsageEmailRecipient);
-  checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
-                        certificateUsageObjectSigner);
 
 
   // inherited EMAIL Trust
   setCertTrust(cert_to_modify_trust, "C,,C");
   checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
                         certificateUsageSSLServer);
   checkCertErrorGeneric(certdb, ee_cert, isRootCA ? SEC_ERROR_UNKNOWN_ISSUER
                                                   : PRErrorCodeSuccess,
@@ -141,18 +128,16 @@ function test_ca_distrust(ee_cert, cert_
   checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_CA_CERT_INVALID,
                         certificateUsageSSLCA);
   checkCertErrorGeneric(certdb, ee_cert, isRootCA ? SEC_ERROR_UNKNOWN_ISSUER
                                                   : PRErrorCodeSuccess,
                         certificateUsageEmailSigner);
   checkCertErrorGeneric(certdb, ee_cert, isRootCA ? SEC_ERROR_UNKNOWN_ISSUER
                                                   : PRErrorCodeSuccess,
                         certificateUsageEmailRecipient);
-  checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
-                        certificateUsageObjectSigner);
 }
 
 
 function run_test() {
   let certList = [
     "ca",
     "int",
     "ee",
@@ -186,24 +171,20 @@ function run_test() {
   checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_UNKNOWN_ISSUER,
                         certificateUsageSSLServer);
   checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_UNKNOWN_ISSUER,
                         certificateUsageSSLClient);
   checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_UNKNOWN_ISSUER,
                         certificateUsageEmailSigner);
   checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_UNKNOWN_ISSUER,
                         certificateUsageEmailRecipient);
-  checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_UNKNOWN_ISSUER,
-                        certificateUsageObjectSigner);
 
   // Now make a CA trust anchor available.
   setCertTrust(ca_cert, "CTu,CTu,CTu");
   checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
                         certificateUsageSSLServer);
   checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
                         certificateUsageSSLClient);
   checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
                         certificateUsageEmailSigner);
   checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
                         certificateUsageEmailRecipient);
-  checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
-                        certificateUsageObjectSigner);
 }
--- a/security/manager/ssl/tests/unit/test_intermediate_basic_usage_constraints.js
+++ b/security/manager/ssl/tests/unit/test_intermediate_basic_usage_constraints.js
@@ -20,18 +20,18 @@ function test_cert_for_usages(certChainN
   }
 
   let cert = certs[0];
   return asyncTestCertificateUsages(certdb, cert, expected_usages);
 }
 
 add_task(async function() {
   let ee_usages = [ certificateUsageSSLClient, certificateUsageSSLServer,
-                    certificateUsageEmailSigner, certificateUsageEmailRecipient,
-                    certificateUsageObjectSigner ];
+                    certificateUsageEmailSigner,
+                    certificateUsageEmailRecipient ];
   let ca_usages = [ certificateUsageSSLCA ];
   let eku_usages = [ certificateUsageSSLClient, certificateUsageSSLServer ];
 
   // Load the ca into mem
   let ca_name = "ca";
   load_cert(ca_name, "CTu,CTu,CTu");
   await test_cert_for_usages([ca_name], ca_usages);
 
--- a/security/manager/ssl/tests/unit/test_nss_shutdown.js
+++ b/security/manager/ssl/tests/unit/test_nss_shutdown.js
@@ -1,17 +1,17 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 // This test attempts to ensure that PSM doesn't deadlock or crash when shutting
 // down NSS while a background thread is attempting to use NSS.
-// Uses test_signed_apps/valid_app_1.zip from test_signed_apps.js.
+// Uses test_signed_apps/signed_app.zip from test_signed_apps.js.
 
 function startAsyncNSSOperation(certdb, appFile) {
   return new Promise((resolve, reject) => {
     certdb.openSignedAppFileAsync(Ci.nsIX509CertDB.AppXPCShellRoot, appFile,
       function(rv, aZipReader, aSignerCert) {
         // rv will either indicate success (if NSS hasn't been shut down yet) or
         // it will be some error code that varies depending on when NSS got shut
         // down. As such, there's nothing really to check here. Just resolve the
@@ -23,17 +23,17 @@ function startAsyncNSSOperation(certdb, 
 
 add_task(async function() {
   do_get_profile();
   let psm = Cc["@mozilla.org/psm;1"]
               .getService(Ci.nsISupports)
               .QueryInterface(Ci.nsIObserver);
   let certdb = Cc["@mozilla.org/security/x509certdb;1"]
                  .getService(Ci.nsIX509CertDB);
-  let appFile = do_get_file("test_signed_apps/valid_app_1.zip");
+  let appFile = do_get_file("test_signed_apps/signed_app.zip");
 
   let promises = [];
   for (let i = 0; i < 25; i++) {
     promises.push(startAsyncNSSOperation(certdb, appFile));
   }
   // Trick PSM into thinking it should shut down NSS. If this test doesn't
   // hang or crash, we're good.
   psm.observe(null, "profile-before-change", null);
--- a/security/manager/ssl/tests/unit/test_signed_apps.js
+++ b/security/manager/ssl/tests/unit/test_signed_apps.js
@@ -131,95 +131,95 @@ function original_app_path(test_name) {
 }
 
 function tampered_app_path(test_name) {
   return FileUtils.getFile("TmpD", ["test_signed_app-" + test_name + ".zip"]);
 }
 
 add_test(function () {
   certdb.openSignedAppFileAsync(
-    Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("valid_app_1"),
+    Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("signed_app"),
     check_open_result("valid", Cr.NS_OK));
 });
 
 add_test(function () {
   certdb.openSignedAppFileAsync(
-    Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("unsigned_app_1"),
+    Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("unsigned_app"),
     check_open_result("unsigned", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED));
 });
 
 add_test(function () {
   certdb.openSignedAppFileAsync(
-    Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("unknown_issuer_app_1"),
+    Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("unknown_issuer_app"),
     check_open_result("unknown_issuer",
                       getXPCOMStatusFromNSS(SEC_ERROR_UNKNOWN_ISSUER)));
 });
 
 // Sanity check to ensure a no-op tampering gives a valid result
 add_test(function () {
   let tampered = tampered_app_path("identity_tampering");
-  tamper(original_app_path("valid_app_1"), tampered, { }, []);
+  tamper(original_app_path("signed_app"), tampered, { }, []);
   certdb.openSignedAppFileAsync(
-    Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("valid_app_1"),
+    Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("signed_app"),
     check_open_result("identity_tampering", Cr.NS_OK));
 });
 
 add_test(function () {
   let tampered = tampered_app_path("missing_rsa");
-  tamper(original_app_path("valid_app_1"), tampered, { "META-INF/A.RSA": removeEntry }, []);
+  tamper(original_app_path("signed_app"), tampered, { "META-INF/A.RSA": removeEntry }, []);
   certdb.openSignedAppFileAsync(
     Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
     check_open_result("missing_rsa", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED));
 });
 
 add_test(function () {
   let tampered = tampered_app_path("missing_sf");
-  tamper(original_app_path("valid_app_1"), tampered, { "META-INF/A.SF": removeEntry }, []);
+  tamper(original_app_path("signed_app"), tampered, { "META-INF/A.SF": removeEntry }, []);
   certdb.openSignedAppFileAsync(
     Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
     check_open_result("missing_sf", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
 });
 
 add_test(function () {
   let tampered = tampered_app_path("missing_manifest_mf");
-  tamper(original_app_path("valid_app_1"), tampered, { "META-INF/MANIFEST.MF": removeEntry }, []);
+  tamper(original_app_path("signed_app"), tampered, { "META-INF/MANIFEST.MF": removeEntry }, []);
   certdb.openSignedAppFileAsync(
     Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
     check_open_result("missing_manifest_mf",
                       Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
 });
 
 add_test(function () {
   let tampered = tampered_app_path("missing_entry");
-  tamper(original_app_path("valid_app_1"), tampered, { "manifest.webapp": removeEntry }, []);
+  tamper(original_app_path("signed_app"), tampered, { "manifest.json": removeEntry }, []);
   certdb.openSignedAppFileAsync(
     Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
     check_open_result("missing_entry", Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING));
 });
 
 add_test(function () {
   let tampered = tampered_app_path("truncated_entry");
-  tamper(original_app_path("valid_app_1"), tampered, { "manifest.webapp": truncateEntry }, []);
+  tamper(original_app_path("signed_app"), tampered, { "manifest.json": truncateEntry }, []);
   certdb.openSignedAppFileAsync(
     Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
     check_open_result("truncated_entry", Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY));
 });
 
 add_test(function () {
   let tampered = tampered_app_path("unsigned_entry");
-  tamper(original_app_path("valid_app_1"), tampered, {},
+  tamper(original_app_path("signed_app"), tampered, {},
     [ { "name": "unsigned.txt", "content": "unsigned content!" } ]);
   certdb.openSignedAppFileAsync(
     Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
     check_open_result("unsigned_entry", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY));
 });
 
 add_test(function () {
   let tampered = tampered_app_path("unsigned_metainf_entry");
-  tamper(original_app_path("valid_app_1"), tampered, {},
+  tamper(original_app_path("signed_app"), tampered, {},
     [ { name: "META-INF/unsigned.txt", content: "unsigned content!" } ]);
   certdb.openSignedAppFileAsync(
     Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
     check_open_result("unsigned_metainf_entry",
                       Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY));
 });
 
 // TODO: tampered MF, tampered SF
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_signed_apps/app/README
@@ -0,0 +1,1 @@
+This is the readme for the test extension.
copy from dom/manifest/test/blue-150.png
copy to security/manager/ssl/tests/unit/test_signed_apps/app/data/image.png
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_signed_apps/app/manifest.json
@@ -0,0 +1,5 @@
+{
+  "manifest_version": 2,
+  "name": "Test Extension",
+  "version": "0.0.1"
+}
deleted file mode 100644
--- a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/README.md
+++ /dev/null
@@ -1,18 +0,0 @@
-This file contains the scripts and binary files needed to regenerate the signed
-files used on the signed apps test.
-
-Prerequisites:
-
-* NSS 3.4 or higher.
-* Python 2.7 (should work with 2.6 also)
-* Bash
-
-Usage:
-
-Run
-
-./create_test_files.sh
-
-The new test files will be created at the ./testApps directory. Just copy the
-contents of this directory to the test directory (dom/apps/tests/signed and
-security/manager/ssl/tests/unit) to use the new files.
deleted file mode 100755
--- a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/create_test_files.sh
+++ /dev/null
@@ -1,213 +0,0 @@
-#!/bin/bash
-
-export NSS_DEFAULT_DB_TYPE=sql
-
-export BASE_PATH=`dirname $0`
-export SIGN_SCR_LOC=.
-export APPS_TEST_LOC=../../../../../../../dom/apps/tests/signed
-export TOOLKIT_WEBAPPS_TEST_LOC=../../../../../../../toolkit/webapps/tests/data/
-
-# Creates the entry zip files (unsigned apps) from the source directories
-packageApps() {
-APPS="unsigned_app_1 unsigned_app_origin unsigned_app_origin_toolkit_webapps"
-OLD_PWD=`pwd`
-cd ${BASE_PATH}
-for i in $APPS
-do
-  echo "Creating $i.zip"
-  cd $i && zip -r ../$i.zip . && cd ..
-done
-cd ${OLD_PWD}
-}
-
-
-# Function to create a signing database
-# Parameters:
-# $1: Output directory (where the DB will be created)
-createDb() {
-
-  db=$1
-
-  mkdir -p $db
-
-  # Insecure by design, so... please don't use this for anything serious
-  passwordfile=$db/passwordfile
-
-  echo insecurepassword > $passwordfile
-  certutil -d $db -N -f $passwordfile 2>&1 >/dev/null
-
-}
-
-# Add a CA cert and a signing cert to the database
-# Arguments:
-#   $1: DB directory
-#   $2: CA CN (don't include the CN=, just the value)
-#   $3: Signing Cert CN (don't include the CN=, just the value)
-#   $4: CA short name (don't use spaces!)
-#   $5: Signing Cert short name (don't use spaces!)
-addCerts() {
-  org="O=Examplla Corporation,L=Mountain View,ST=CA,C=US"
-  ca_subj="CN=${2},${org}"
-  ee_subj="CN=${3},${org}"
-
-  noisefile=/tmp/noise.$$
-  head -c 32 /dev/urandom > $noisefile
-
-  ca_responses=/tmp/caresponses.$$
-  ee_responses=/tmp/earesponses
-
-  echo y >  $ca_responses # Is this a CA?
-  echo >>   $ca_responses # Accept default path length constraint (no constraint)
-  echo y >> $ca_responses # Is this a critical constraint?
-  echo n >  $ee_responses # Is this a CA?
-  echo >>   $ee_responses # Accept default path length constraint (no constraint)
-  echo y >> $ee_responses # Is this a critical constraint?
-
-  make_cert="certutil -d $db -f $passwordfile -S -g 2048 -Z SHA256 \
-                    -z $noisefile -y 3 -2 --extKeyUsage critical,codeSigning"
-  $make_cert -v 480 -n ${4}        -m 1 -s "$ca_subj" \
-      --keyUsage critical,certSigning      -t ",,CTu" -x < $ca_responses 2>&1 >/dev/null
-  $make_cert -v 240 -n ${5} -c ${4} -m 2 -s "$ee_subj" \
-      --keyUsage critical,digitalSignature -t ",,,"      < $ee_responses 2>&1 >/dev/null
-
-  # In case we want to inspect the generated certs
-
-  # Also, we'll need this one later on
-  certutil -d $db -L -n ${4} -r -o $db/${4}.der
-  certutil -d $db -L -n ${5} -r -o $db/${5}.der
-
-  rm -f $noisefile $ee_responses $ca_responses
-}
-
-
-# Signs an app
-# Parameters:
-# $1: Database directory
-# $2: Unsigned ZIP file path
-# $3: Signed ZIP file path
-# $4: Store ID for the signed App
-# $5: Version of the signed App
-# $6: Nickname of the signing certificate
-signApp() {
-
-  db=$1
-
-  # Once again, this is INSECURE. It doesn't matter here but
-  # DON'T use this for anything production related
-  passwordfile=$db/passwordfile
-
-  python ${BASE_PATH}/${SIGN_SCR_LOC}/sign_b2g_app.py -d $db -f $passwordfile \
-         -k ${6} -i ${2} -o ${3} -S ${4} -V ${5}
-}
-
-DB_PATH=${BASE_PATH}/signingDB
-TEST_APP_PATH=${BASE_PATH}/testApps
-
-echo "Warning! The directories ${DB_PATH} and ${TEST_APP_PATH} will be erased!"
-echo "Do you want to proceed anyway?"
-select answer in "Yes" "No"
-do
-  case $answer in
-    Yes) break;;
-    No) exit 1;;
-  esac
-done
-
-rm -rf ${DB_PATH} ${TEST_APP_PATH}
-
-TRUSTED_EE=trusted_ee1
-UNTRUSTED_EE=untrusted_ee1
-TRUSTED_CA=trusted_ca1
-UNTRUSTED_CA=untrusted_ca1
-
-# First, we'll create a new couple of signing DBs
-createDb $DB_PATH
-addCerts $DB_PATH "Valid CA" "Store Cert" trusted_ca1 ${TRUSTED_EE}
-addCerts $DB_PATH "Invalid CA" "Invalid Cert" ${UNTRUSTED_CA} ${UNTRUSTED_EE}
-
-# Then we'll create the unsigned apps
-echo "Creating unsigned apps"
-packageApps
-
-# And then we'll create all the test apps...
-mkdir -p ${TEST_APP_PATH}
-
-# We need:
-# A valid signed file, with two different versions:
-#    valid_app_1.zip
-#    valid_app_2.zip
-VALID_UID=`uuidgen`
-signApp $DB_PATH ${BASE_PATH}/unsigned_app_1.zip \
-        $TEST_APP_PATH/valid_app_1.zip \
-        $VALID_UID 1 ${TRUSTED_EE}
-signApp $DB_PATH ${BASE_PATH}/unsigned_app_1.zip \
-        $TEST_APP_PATH/valid_app_2.zip \
-        $VALID_UID 2 ${TRUSTED_EE}
-
-
-# A corrupt_package:
-#    corrupt_app_1.zip
-# A corrupt package is a package with a entry modified, for example...
-CURDIR=`pwd`
-export TEMP_DIR=$TEST_APP_PATH/aux_unzip_$$
-mkdir -p $TEMP_DIR
-cd  $TEMP_DIR
-unzip ../valid_app_1.zip 2>&1 >/dev/null
-echo " - " >> index.html
-zip -r ../corrupt_app_1.zip * 2>&1 >/dev/null
-cd $CURDIR
-rm -rf $TEMP_DIR
-
-# A file signed by a unknown issuer
-#    unknown_issuer_app_1.zip
-INVALID_UID=`uuidgen`
-signApp $DB_PATH ${BASE_PATH}/unsigned_app_1.zip \
-        $TEST_APP_PATH/unknown_issuer_app_1.zip \
-        $INVALID_UID 1 ${UNTRUSTED_EE}
-
-# And finally a priviledged signed file that includes the origin on the manifest
-# to avoid that reverting again
-PRIV_UID=`uuidgen`
-signApp $DB_PATH ${BASE_PATH}/unsigned_app_origin.zip \
-        $TEST_APP_PATH/origin_app_1.zip \
-        $PRIV_UID 1 ${TRUSTED_EE}
-
-# A privileged signed app needed for a toolkit/webapps test
-PRIV_TOOLKIT_UID=`uuidgen`
-signApp $DB_PATH ${BASE_PATH}/unsigned_app_origin_toolkit_webapps.zip \
-        $TEST_APP_PATH/custom_origin.zip \
-        $PRIV_TOOLKIT_UID 1 ${TRUSTED_EE}
-
-# Now let's copy the trusted cert to the app directory so we have everything
-# on the same place...
-cp ${DB_PATH}/${TRUSTED_CA}.der ${TEST_APP_PATH}
-
-cat <<EOF
-
-All done. The new test files are in ${TEST_APP_PATH}. You should copy the
-contents of that directory to the dom/apps/tests/signed directory and to
-the security/manager/ssl/tests/unit/test_signed_apps (which should be the
-parent of this directory) to install them.
-
-EOF
-
-echo "Do you wish me to do that for you now?"
-select answer in "Yes" "No"
-do
-  case $answer in
-    Yes) break;;
-    No) echo "Ok, not installing the new files"
-        echo "You should run: "
-        echo cp ${TEST_APP_PATH}/* ${BASE_PATH}/${APPS_TEST_LOC}
-        echo cp ${TEST_APP_PATH}/* ${TEST_APP_PATH}/../unsigned_app_1.zip ${BASE_PATH}/..
-        echo cp ${TEST_APP_PATH}/* ${BASE_PATH}/${TOOLKIT_WEBAPPS_TEST_LOC}
-        echo "to install them"
-        exit 0;;
-  esac
-done
-
-cp ${TEST_APP_PATH}/* ${BASE_PATH}/${APPS_TEST_LOC}
-cp ${TEST_APP_PATH}/* ${TEST_APP_PATH}/../unsigned_app_1.zip ${BASE_PATH}/..
-cp ${TEST_APP_PATH}/* ${BASE_PATH}/${TOOLKIT_WEBAPPS_TEST_LOC}
-
-echo "Done!"
deleted file mode 100644
--- a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/nss_ctypes.py
+++ /dev/null
@@ -1,131 +0,0 @@
-# flake8: noqa
-
-from ctypes import *
-import os
-import sys
-
-if sys.platform == 'darwin':
-  libprefix = "lib"
-  libsuffix = ".dylib"
-elif os.name == 'posix':
-  libprefix = "lib"
-  libsuffix = ".so"
-else: # assume windows
-  libprefix = ""
-  libsuffix = ".dll"
-
-plc   = cdll.LoadLibrary(libprefix + "plc4"   + libsuffix)
-nspr  = cdll.LoadLibrary(libprefix + "nspr4"  + libsuffix)
-nss   = cdll.LoadLibrary(libprefix + "nss3"   + libsuffix)
-smime = cdll.LoadLibrary(libprefix + "smime3" + libsuffix)
-
-nspr.PR_GetError.argtypes = []
-nspr.PR_GetError.restype = c_int32
-nspr.PR_ErrorToName.argtypes = [c_int32]
-nspr.PR_ErrorToName.restype = c_char_p
-
-def raise_if_not_SECSuccess(rv):
-  SECSuccess = 0
-  if (rv != SECSuccess):
-    raise ValueError(nspr.PR_ErrorToName(nspr.PR_GetError()))
-
-def raise_if_NULL(p):
-  if not p:
-    raise ValueError(nspr.PR_ErrorToName(nspr.PR_GetError()))
-  return p
-
-PRBool = c_int
-SECStatus = c_int
-
-# from secoidt.h
-SEC_OID_SHA1 = 4
-
-# from certt.h
-certUsageObjectSigner = 6
-
-class SECItem(Structure):
-  _fields_ = [("type", c_int),
-              ("data", c_char_p),
-              ("len", c_uint)]
-
-nss.NSS_Init.argtypes = [c_char_p]
-nss.NSS_Init.restype = SECStatus
-def NSS_Init(db_dir):
-  nss.NSS_Init.argtypes = [c_char_p]
-  nss.NSS_Init.restype = SECStatus
-  raise_if_not_SECSuccess(nss.NSS_Init(db_dir))
-
-nss.NSS_Shutdown.argtypes = []
-nss.NSS_Shutdown.restype = SECStatus
-def NSS_Shutdown():
-  raise_if_not_SECSuccess(nss.NSS_Shutdown())
-
-PK11PasswordFunc = CFUNCTYPE(c_char_p, c_void_p, PRBool, c_char_p)
-
-# pass the result of this as the wincx parameter when a wincx is required
-nss.PK11_SetPasswordFunc.argtypes = [PK11PasswordFunc]
-nss.PK11_SetPasswordFunc.restype = None
-
-# Set the return type as *void so Python doesn't touch it
-plc.PL_strdup.argtypes = [c_char_p]
-plc.PL_strdup.restype = c_void_p
-def SetPasswordContext(password):
-  def callback(slot, retry, arg):
-    return plc.PL_strdup(password)
-  wincx = PK11PasswordFunc(callback)
-  nss.PK11_SetPasswordFunc(wincx)
-  return wincx
-
-nss.CERT_GetDefaultCertDB.argtypes = []
-nss.CERT_GetDefaultCertDB.restype = c_void_p
-def CERT_GetDefaultCertDB():
-  return raise_if_NULL(nss.CERT_GetDefaultCertDB())
-
-nss.PK11_FindCertFromNickname.argtypes = [c_char_p, c_void_p]
-nss.PK11_FindCertFromNickname.restype = c_void_p
-def PK11_FindCertFromNickname(nickname, wincx):
-  return raise_if_NULL(nss.PK11_FindCertFromNickname(nickname, wincx))
-
-nss.CERT_DestroyCertificate.argtypes = [c_void_p]
-nss.CERT_DestroyCertificate.restype = None
-def CERT_DestroyCertificate(cert):
-  nss.CERT_DestroyCertificate(cert)
-
-smime.SEC_PKCS7CreateSignedData.argtypes = [c_void_p, c_int, c_void_p,
-                                            c_int, c_void_p,
-                                            c_void_p, c_void_p]
-smime.SEC_PKCS7CreateSignedData.restype = c_void_p
-def SEC_PKCS7CreateSignedData(cert, certusage, certdb, digestalg, digest, wincx):
-  item = SECItem(0,  c_char_p(digest), len(digest))
-  return raise_if_NULL(smime.SEC_PKCS7CreateSignedData(cert, certusage, certdb,
-                                                       digestalg,
-                                                       pointer(item),
-                                                       None, wincx))
-
-smime.SEC_PKCS7AddSigningTime.argtypes = [c_void_p]
-smime.SEC_PKCS7AddSigningTime.restype = SECStatus
-def SEC_PKCS7AddSigningTime(p7):
-  raise_if_not_SECSuccess(smime.SEC_PKCS7AddSigningTime(p7))
-
-smime.SEC_PKCS7IncludeCertChain.argtypes = [c_void_p, c_void_p]
-smime.SEC_PKCS7IncludeCertChain.restype = SECStatus
-def SEC_PKCS7IncludeCertChain(p7, wincx):
-  raise_if_not_SECSuccess(smime.SEC_PKCS7IncludeCertChain(p7, wincx))
-
-SEC_PKCS7EncoderOutputCallback = CFUNCTYPE(None, c_void_p, c_void_p, c_long)
-smime.SEC_PKCS7Encode.argtypes = [c_void_p, SEC_PKCS7EncoderOutputCallback,
-                                  c_void_p, c_void_p, c_void_p, c_void_p]
-smime.SEC_PKCS7Encode.restype = SECStatus
-def SEC_PKCS7Encode(p7, bulkkey, wincx):
-  outputChunks = []
-  def callback(chunks, data, len):
-    outputChunks.append(string_at(data, len))
-  callbackWrapper = SEC_PKCS7EncoderOutputCallback(callback)
-  raise_if_not_SECSuccess(smime.SEC_PKCS7Encode(p7, callbackWrapper,
-                                                None, None, None, wincx))
-  return "".join(outputChunks)
-
-smime.SEC_PKCS7DestroyContentInfo.argtypes = [c_void_p]
-smime.SEC_PKCS7DestroyContentInfo.restype = None
-def SEC_PKCS7DestroyContentInfo(p7):
-  smime.SEC_PKCS7DestroyContentInfo(p7)
deleted file mode 100644
index d6fd07a4167d2f05a9a13a2eaca6a9981287dfb6..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_1/index.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<!doctype html>
-<html lang=en>
-<head><meta charset=utf-8><title>Simple App</title></head>
-<body><p>This is a Simple App.</body>
-</html>
-
deleted file mode 100644
--- a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_1/manifest.webapp
+++ /dev/null
@@ -1,8 +0,0 @@
-{ "name": "Simple App"
-, "description": "A Simple Open Web App"
-, "launch_path": "tests/dom/apps/tests/signed_app.sjs"
-, "icons": { "128" : "icon-128.png" }
-, "installs_allowed_from": [ "https://marketplace.mozilla.com", "http://mochi.test:8888" ]
-, "version": 1
-, "default_locale": "en-US"
-}
deleted file mode 100644
index d6fd07a4167d2f05a9a13a2eaca6a9981287dfb6..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin/index.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<!doctype html>
-<html lang=en>
-<head><meta charset=utf-8><title>Simple App</title></head>
-<body><p>This is a Simple App.</body>
-</html>
-
deleted file mode 100644
--- a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin/manifest.webapp
+++ /dev/null
@@ -1,10 +0,0 @@
-{ "name": "Simple App",
-  "description": "A Simple Open Web App",
-  "type": "privileged",
-  "origin": "app://test.origin.privileged.app",
-  "launch_path": "tests/dom/apps/tests/signed_app.sjs",
-  "icons": { "128" : "icon-128.png" },
-  "installs_allowed_from": [ "https://marketplace.mozilla.com", "http://mochi.test:8888" ],
-  "version": 1,
-  "default_locale": "en-US"
-}
deleted file mode 100644
--- a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin_toolkit_webapps/index.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<title>Test app</title>
-</head>
-<body>
-Test app:
-<iframe src="http://127.0.0.1:8888/chrome/toolkit/webapps/tests/app.sjs?appreq"></iframe>
-</body>
-</html>
deleted file mode 100644
--- a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin_toolkit_webapps/manifest.webapp
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "name": "Custom Origin Test",
-  "version": 1,
-  "size": 777,
-  "package_path": "custom_origin.zip",
-  "launch_path": "/index.html",
-  "origin": "app://test.origin.privileged.app",
-  "type": "privileged"
-}
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_signed_apps/moz.build
@@ -0,0 +1,34 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+@template
+def SignedAppFile(name, flags):
+    if not CONFIG['COMPILE_ENVIRONMENT']:
+        return
+
+    GENERATED_FILES += [name]
+    props = GENERATED_FILES[name]
+    props.script = '/security/manager/ssl/tests/unit/sign_app.py'
+    props.inputs = ['app/']
+    if flags:
+        props.flags = flags
+    # Turn RELATIVEDIR into list entry: like
+    # 'security/manager/ssl/tests/unit/test_signed_apps' ->
+    # TEST_HARNESS_FILES.xpcshell.security.manager.ssl.tests.unit.test_signed_apps.
+    files = TEST_HARNESS_FILES.xpcshell
+    for part in RELATIVEDIR.split('/'):
+        files = files[part]
+    files += ['!%s' % name]
+
+# Temporarily disabled. See bug 1256495.
+#signed_app_files = (
+#    ['signed_app.zip'],
+#    ['unknown_issuer_app.zip', '-i', 'unknown issuer'],
+#    ['unsigned_app.zip', '-n'],
+#)
+#
+#for signed_app_file_params in signed_app_files:
+#    SignedAppFile(signed_app_file_params[0], signed_app_file_params[1:])
deleted file mode 100644
index 3a12106c8fc0ad3f4bccc517eba2a3012ec57103..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
rename from security/manager/ssl/tests/unit/test_signed_apps/valid_app_1.zip
rename to security/manager/ssl/tests/unit/test_signed_apps/signed_app.zip
index 74a35a4208dcee164af51abcdd5715f3d90a67ea..022181dc73b95307ea3d5597b1933fdd52606f34
GIT binary patch
literal 2813
zc$^FHW@Zs#00D#OAaAJ{jZJ<)HVE?qac*K>W?E`-iC$K5eqJ?~f`SrEB)%-Qs5mn}
zPsvKbNCza5mzbLh<S2yzB^6vNN>cN{(qK7+S|tNL13g0}u3D}D6pO!k80u&P*&xgY
z#6hl(F21fI8JWcjKva^Es!)`gn39{Skd|Kr=9Yl0NrhRXhi2UDCQEiPCI$u&<^zhQ
zB$g!VXXYlRr|K2trFRDSx$|;Kae+MP>ERLtq^E%}2gqg{#==Jo42*X@T^vIy=Da<(
zk(0qd;K+vC^)Js0F*X-2jJy4A(*F+*b4JmSMLlu}1xi)>WQ6yC(u=37pUXO@geKG!
zlp(xg&P^5u1`r0NAYfv2)b;dp(|6Ph3U)MTV!6u3snzDu_MMlJooPW6%Q2wPAtpvf
zLv903HdbvuW+o|C1_N${BE|(xOkWI|m_8UZG2U9h%*4pVBqBPea@l9@ptbJ~KVbVf
z!zZ{<_xo2QJ&fE2nuh8Is%*@mEX+J|6$Qz~8L2rr3dNb}d8sK1i3J74kbF>-pI>62
zB*AZFU}$P+Xk=jk22o%E3j-qqQ>cJ}mZ64$8pJqxEXF}h1M`c3&QvnsMYx5Lm4Ug5
zk)Hu5&c)Qk$jGp(BXEUFpV&3Wzj@m_*vtfMZG>;WdSdFe<;~ZO@En!ugU3}@Jh&mh
zcjFwk*?CfJpRymVk1Ea1N-5;1S{k+7wf(!yRh2nRb!VOb$YoxzP|%#bTH&zgliNFe
z4rS-?y%lZZ|5&#+Q)=U7CaZ2O$9KyM9`N(_{!Qc7-k>sRN$t*##L5X9!zb8YvOfA?
zhll(M^UEF1@72X_gl<oZT2;q1LEoxcpn*;0j7-k-wbK%#w?CRDbfDC5G0()iTTXrd
zvsPC1GUxl9;(W_99&WDISIW<PyrpYuqU<}hYpQcsPvr13J1wKaqO)JDTmR|D{+Z&2
zJ2wCGRQ^>Ic*O83^O{={T&8b(nV1<F7#H&!@B(9uIaHRPg@u`ky#Xcqm_gxWW^Lsz
zy;zH3CU?abzEk%z^A~pH*t_-_w8x6(t8`mDEq^jePG#j|cRAhDk^u^LS_+nkUYz&!
z|IKP~u6Coz#~#__-T(AaGU>$gEm;TC6zYQKF<z}&djIs-#Xc^3XHNTbWLBc~k6WMq
zye&VH;ks?NyxB#|k4G=2J@cGwa(v>&t!LsB?KDLa_j7S<QuVqu?O+4%<L2W3d-A>4
zxEpzD36`0?5;)9vNy6!^+OO?74%eTB@J_B!TP{)=HjgWDdieX<ucR$cpZ>OF=gau-
zKLqA?l}Rqycx%B!`RtvOmq;~Tn7Z=S%<Esz*Ir)vHTcqwlIj^JgI(K$4?53G-y*<v
zj>j&-%Yf0aiE$+`(JTdK4g*IL5;TtNfyiA8><uK~fz8Qmh@9CBlo2v4hGHxtKaY1*
zhh^6;h<(0TiK)!Y^IEG>En4zsX<}puSaroW>}iy!ot2UnciP^YUr#;=;Wb^5zg?~R
zS6{GS;J5jV#r!unNcWtYRXJ^aMuhu|uJA7DbN{0jF!mmf*5Pc}t+Q%&TC}g%P0#Yk
zpVHY!W%ph7T3o<%h^b8Yv8m02{S9w-Y}=O~`&#qg^Rv5sv}$=5gc@Abp78ye-Twlk
zoF&s9hA&CIULx7!{^!%G#d9_kYo<0DM;p{_syO!TfVJkWrCBD|UX))<nP6D*zOCli
z`@`K~cf?P9`L#hiZo%PuS7p{5QSlA(3|PBy<&P^%)~EXjh`(RQapPV4Mdm#(7Fl;m
zsp!sDl6<pDwQ>Dw@88?my<NE9Nfj|oYt-MuCY~uh$sM&2OkbD##0$vwf;I<`3c+Bv
zU|^&rmXsEy>V`q8cPj-$Jp-;_4@W~?m&|lvH0t_78w*wnUan@I!7fgw$yrHGhEXPw
zg~{oWX@OBjE}<s2XmvMR^0tF#fou>KLAKP_(a+P(H8@1i*9~Scs?}V6z&41L0!pI=
z**#VYQHdc=X$4UM?m0fjC7GF?r9oK+25#9&=K1BeFzt}02dX}|l28xFEQ^Tz!VFXA
zsFLJx3k#335bgZJOkcP@)HVjHe&bB1JhM`_O!HKCZ~dSgFTV<d(16O2>|pbJkoOsx
zM3`|m!eF3*kpaC)hOQO88HLbV32#)P>p^cTA@rQUXgQ(lMXx0hdbya<>r8aL=!pxV
km!AVIsiA8{Pa+7dEgaYrOn^5lI6T;ZFa;Qn&72?}03}TjumAu6
deleted file mode 100644
index ec137ea14ad68f022490465e2630b52af2b3d3c2..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
rename from security/manager/ssl/tests/unit/test_signed_apps/unknown_issuer_app_1.zip
rename to security/manager/ssl/tests/unit/test_signed_apps/unknown_issuer_app.zip
index 374e45f6f83d20a0df2b0b64efa906b7f70a84b3..177f545d478d99507224c8e2e31690b68a9d652c
GIT binary patch
literal 2781
zc$^FHW@Zs#0D;djLEcg^8k_upY!K!J;@rf%%(T?v61}YA{Jd%|1qCITNPJmpQE_H|
zo|2V<kq$^AFEKY2$WaObN-DTkl%(c?rNMFtwMqth26~1{T(w*QC>DS7Fx1fovO$;)
zh=W`mU3^_bGBS%5fT$!RRiP*~F(o%uAuYcM%q;;~lM1s&56!sQO_uCpObiSl%m)-p
zNi0d!&&*9sPt_~POYaQubLZuf;sSZn)59eQNKXS{4v@_@jD?RF7#Qz*x;TbZ%z1lm
zBPWA_z>y8N>tCK1Vr(v47<c>Kr2iir=8U2ti+bb|3Y4n$$q4TOr58_EKbLh*2~DUe
zXwRMP20K|87(f`5f`Ez9QP<PYP2W*3DA>`UiDfGrr&gOs+jm|@cBTbQEX#mGi<lT0
z4Y>_C*;uvtn3<$l84S1)iWnC(F+DM8VtQcE#CU1}GZP~dlSu5hJGvi(!f#nQtetW=
zbXJT8^K(BWJ&fE2l7`|2qHN5eEX+K7rFq$T`Q>@Q3{hN~T4bOk!Ea<>XliI^WMKdX
zQD6ZJ10w@dsDOc%p@xAPM7w-NL2_|MYEF(qab|j6YKlT)K>;Kef%!#1XDJ!*BJ5^l
zWngY%<Yxeib1^kBGBWJy2wdUPCw9&8Z{D^JHZuWR8{wO;o|t-VdGj?RJV&Ma;BnOz
z4{pfs-8hGBcAiw*r|d`Tqe`>0QVKb$mPRdiZT~KFRb>uS-C5^9a+wz_6f`HVRygeW
z<n~UVL)kfeZ$+E<Kh~|yl-hWi$*Nn+@!j%*2mHLff77_NH>gZnQoFMwv2w!3@Cmk;
ztdBm};UT}m{Bno$dv&oJq1)4<R@E_0(6_1<Xkb%0Ba<_I?X<+`?T@Aj9Vj(i%ro)s
zmQ&yVtd&*0%=vz&IN$P&hnuVQmGUzmZ|RzvDEm(Bn(Exu6FL0MPRppU=<FBk)_?l3
zf2O$Mj?Mo(m46il9x=Siyylh!m+9MHCT2zk#>M;wyucV@4wdC+VPR%sZ$ODIW>7e3
z-rn=8dcM%@i##qxb=FrBD)_!1Y~1kdC*#qzK1wOfm%hJl=leNRpCwtl+Vr5)<QFG7
zW?E<c%I{-5^McJY{LAur&+XR;adh5ZUtQzepuhbjzt@@L9>?a@zLxOh<#N0ky<#ix
ztnF`C9J_E;aq_|DOB2uUU2*Ef<o%PUK0c^^AxiiAgOaS=YUNGR-f^*W`ORjYJ#;Q?
zK1;b*#_paiijr@#7S7zybnxRI<};VP{U<~#JzwGH?a=mo^Lc?7hWe$uoBGeyh^^k_
zYkl5rLD#|AlOB78zd7+@r$nd6_J14fE%$tO63@<=(Y)sM^5g&YU!`?&FJZRYxrw_y
zMD+s0PP^8{^N&q3Y+{@VOf*x0nZdviPXfk~{1LflfxUqQJa9Rg4UsdGfigmd#ZZhz
z<md5@>agtE1+mW;D>0Rsd0uNZszpoAEKQ6I0jsX~hCPiEwX;&v;!fLp^Xth6A-tvw
z^0%v1|LP0&3;Z^pv6%nn2I-ztvnr>p&xmk;(G}h$eeQqM0><9M(K?(ByLDF0PK);S
zy6IUS`BOUksO-MWUW*Hu4l$JpKQ^^_u)pE$j&1w$V_$3jdwzDek5(=3f>48t+7rHC
zv-@9Ql(S^o!|)}k*GnXO-2Z%9wRp~kV$IY><7k7rO%=z!9kABCwKU7*+KcjwDH9A!
z-nZ2pdw;k)?2h=UFTXZu$1ON~@2bq2BPzZ@o&jq&uKaOj$@+930rB_iIBvXazsS7j
z#Ukr2DHYw>N|J9@sWz@(?frW@ySEGXJE<b3X^r|@*u*oXC%K~*a_Q@GpLhY;UeM|u
zsgMhH3kDVoi6x~)sk&j1>f1`eP|tuX*u&9K*CjI@SaRt4LK_2C3SO>ep203orpZ}J
zPKHq?k%h_Wk!gWZMlPWywrF)TTk^JpXMt=G7D2Yu*U`_@%{4eg&({rRFsjvDe!w<{
zl>$nm1lc`S3Q>t6PH6>E0q!|I#wD4Vo~1!q1_o}~N#^<GwlM9GrUj}#w~|l~$1ID8
z{K5=V=ctn8a0?5MvJmb3!c1SdKGZe@s(#~4r#!P#w@mX?cW?cm9525LgV2D=knCXd
ze317UnM9azH?m-$fsp~d$%U>Jy_tm2S_yA7q3c0!3nBEJz-Sqv>qV~x5qi0p(d$HX
pz37Pxq4zyIT2e#Tik?IeT2nZ%Czt?lR&aQ*0bvR-9FsXgJOIG84%Ywx
rename from security/manager/ssl/tests/unit/test_signed_apps/unsigned_app_1.zip
rename to security/manager/ssl/tests/unit/test_signed_apps/unsigned_app.zip
index 731e050aba5e5b175cea6f4393e4d6541bcb382b..748386dc560aac505a5f7d490d556f5a5c295ebf
GIT binary patch
literal 971
zc$^FHW@Zs#0D;djLEcg^8k_upY!K!J;@rf%%(T?v61}YA{Jd%|1qCITNPJmpQE_H|
zo|2V<kq$^AFEKY2$WaObN-DTkl%(c?rNMFtwMqth26~1{T(w*QC>DS7Fx1fovO$;)
zh=W`mU3^_bGBS%5fT$!RRiP*~F(o%uAuYcM%q;;~lM1s&56!sQO_uCpObiSl%m)-p
zNi0d!&&*9sPt_~POYaQubLZuf;sSZn)59eQNKXS{4v@_@jD?RF7#Qz*x;TbZ%z1lm
zBPWA_z>y8N>tCK1Vr(v47<c>Kr2iir=8U2ti+bb|3Y4n$$q4TOr58_EKbLh*2~7ds
zj7%cTxbp%GG%zxt=L~eM=&2c@wGy7T(e<DwS%jVw7zsGQn-y#<GZ3x@(p=0S9suza
Bgw+55
rename from security/manager/ssl/tests/unit/test_signed_apps/trusted_ca1.der
rename to security/manager/ssl/tests/unit/test_signed_apps/xpcshellTestRoot.der
index 0200a7f35d1c8344a816e4109bd9cce07327f21f..f1f5d25f63e6b0f09810ac40cf6b7bbed7c7b8bc
GIT binary patch
literal 790
zc$_n6Viq!JV*Im!nTe5!NhCLr@lmY$+&j~@K3+R#N|92fW_q~+Hyfu`n@8JsUPeZ4
z15HD9164NWP!?t$xr&11;*8Xs9EIY{^t{v*g~Wn_Vug~_;u3|T{QMFFB?*2b14B~-
zLm&i$D3E|LvH&T@H8Cm~@FJYT$jZRn#K_M86z5`UVq|34)e*SDrBCdd<KMh(9c*R-
zwl=~yUp+DP+VbXWMtF`&^}*w+D<0gC-@9=R+w454woloQ)<>0QXQdQ!R4t8K?%Muc
z=BmmZrn<Avf8;VRSSV;tUafH0^U3X<K8Lb%_}+>(@qesan<=&NGLu!emgBqS1rPXn
zd;g|!Yj04Qw4`=tM`GoKjo}k)FIgXbu){-sh56+U=lAMjH$u0kMXjo1nxJo0EzrQG
zaz-X+`r2uU(c2$Q6FN|8xR__+-7TlS|5+=mdYSY6PI12F84ovC>nr7FKHkzbHBt7R
z+BMa=t0!{!nVptVVbR$y)~)~aWB*KX!yTLdc`E-Z3Or(Xm3hrA2`<yOy-duE42+A7
zfWgWG3{Y8N7FGjhM#lfZ=mYZjSy-5vm{bgeK^#>U9s@2m4sA9@R#tXqW|RnK1_fnQ
z==NRL%vQho(AfFL^!jV<FMr=2TCzf53fCdgi{BT@POg-&>R;6^t0}1VX7BY1<xQP;
zEVw$nZoa=d!MS3i^{4p{<E>J)bu>R&F12~{@Qg)LIfqri&Agkh16qaCGJd-l1YY4d
z*gb)L@^RtBV%b%EF0<0bGdI+jaUEm1S`wA@p<uO1XZIaJ^Gl~yKE6tx75+%m<ArU^
z)EhE(ZqBJy3#=AA`!kVqL-Mgj-|YXsRp{Cq{BMR^Org-P(AO#o8OP&~zEOI$?DCJq
zoADd32B?%?5ZRpnoAr^<be}^Gow_NKPw!-X=bclKYgjh@Z?lu6`!AVL**2e6-JL%c
NJeGd_pO?8e695E(I`#kn
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_signed_apps/xpcshellTestRoot.pem.certspec
@@ -0,0 +1,6 @@
+issuer:xpcshell signed apps test root
+subject:xpcshell signed apps test root
+validity:20150101-20350101
+extension:basicConstraints:cA,
+extension:keyUsage:keyEncipherment,keyCertSign
+extension:extKeyUsage:codeSigning
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -58,16 +58,20 @@ using namespace sandbox::bpf_dsl;
 #ifndef MADV_FREE
 #define MADV_FREE 8
 #endif
 
 #ifndef PR_SET_PTRACER
 #define PR_SET_PTRACER 0x59616d61
 #endif
 
+// The headers define O_LARGEFILE as 0 on x86_64, but we need the
+// actual value because it shows up in file flags.
+#define O_LARGEFILE_REAL 00100000
+
 // To avoid visual confusion between "ifdef ANDROID" and "ifndef ANDROID":
 #ifndef ANDROID
 #define DESKTOP
 #endif
 
 // This file defines the seccomp-bpf system call filter policies.
 // See also SandboxFilterUtil.h, for the CASES_FOR_* macros and
 // SandboxFilterBase::Evaluate{Socket,Ipc}Call.
@@ -740,21 +744,45 @@ public:
         .ElseIf(request == FIONREAD, Allow())
         // Allow anything that isn't a tty ioctl, for now; bug 1302711
         // will cover changing this to a default-deny policy.
         .ElseIf(shifted_type != kTtyIoctls, Allow())
         .Else(SandboxPolicyCommon::EvaluateSyscall(sysno));
     }
 #endif // !MOZ_ALSA
 
-    CASES_FOR_fcntl:
-      // Some fcntls have significant side effects like sending
-      // arbitrary signals, and there's probably nontrivial kernel
-      // attack surface; this should be locked down more if possible.
-      return Allow();
+    CASES_FOR_fcntl: {
+      Arg<int> cmd(1);
+      Arg<int> flags(2);
+      // Typical use of F_SETFL is to modify the flags returned by
+      // F_GETFL and write them back, including some flags that
+      // F_SETFL ignores.  This is a default-deny policy in case any
+      // new SETFL-able flags are added.  (In particular we want to
+      // forbid O_ASYNC; see bug 1328896, but also see bug 1408438.)
+      static const int ignored_flags = O_ACCMODE | O_LARGEFILE_REAL | O_CLOEXEC;
+      static const int allowed_flags = ignored_flags | O_APPEND | O_NONBLOCK;
+      return Switch(cmd)
+        // Close-on-exec is meaningless when execve isn't allowed, but
+        // NSPR reads the bit and asserts that it has the expected value.
+        .Case(F_GETFD, Allow())
+        .Case(F_SETFD,
+              If((flags & ~FD_CLOEXEC) == 0, Allow())
+              .Else(InvalidSyscall()))
+        .Case(F_GETFL, Allow())
+        .Case(F_SETFL,
+              If((flags & ~allowed_flags) == 0, Allow())
+              .Else(InvalidSyscall()))
+        .Case(F_DUPFD_CLOEXEC, Allow())
+        // Pulseaudio uses F_SETLKW.
+        .Case(F_SETLKW, Allow())
+#ifdef F_SETLKW64
+        .Case(F_SETLKW64, Allow())
+#endif
+        .Default(SandboxPolicyCommon::EvaluateSyscall(sysno));
+    }
 
     case __NR_mprotect:
     case __NR_brk:
     case __NR_madvise:
       // libc's realloc uses mremap (Bug 1286119); wasm does too (bug 1342385).
     case __NR_mremap:
       return Allow();
 
--- a/security/sandbox/linux/reporter/SandboxReporter.cpp
+++ b/security/sandbox/linux/reporter/SandboxReporter.cpp
@@ -195,16 +195,17 @@ SubmitToTelemetry(const SandboxReport& a
       }                                                     \
       break
 
     // The syscalls handled specially:
 
     ARG_HEX(clone, 0); // flags
     ARG_DECIMAL(prctl, 0); // option
     ARG_HEX(ioctl, 1); // request
+    ARG_DECIMAL(fcntl, 1); // cmd
     ARG_DECIMAL(madvise, 2); // advice
     ARG_CLOCKID(clock_gettime, 0); // clk_id
 
 #ifdef __NR_socketcall
     ARG_DECIMAL(socketcall, 0); // call
 #endif
 #ifdef __NR_ipc
     ARG_DECIMAL(ipc, 0); // call
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -343,16 +343,21 @@ name = "caseless"
 version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
+name = "cc"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
 name = "cexpr"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1092,29 +1097,30 @@ dependencies = [
  "core-graphics 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-text 7.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "fontsan 0.3.2 (git+https://github.com/servo/fontsan)",
  "freetype 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gfx_traits 0.0.1",
- "harfbuzz-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "harfbuzz-sys 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "malloc_size_of 0.0.1",
  "malloc_size_of_derive 0.0.1",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "ordered-float 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "range 0.0.1",
  "serde 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-fontconfig 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "servo_allocator 0.0.1",
  "servo_arc 0.0.1",
  "servo_atoms 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "simd 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallvec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "style 0.0.1",
  "style_traits 0.0.1",
@@ -1188,17 +1194,17 @@ dependencies = [
  "compositing 0.0.1",
  "euclid 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "libservo 0.0.1",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
- "osmesa-src 17.3.0-devel (git+https://github.com/servo/osmesa-src)",
+ "osmesa-src 17.3.1-devel (git+https://github.com/servo/osmesa-src)",
  "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "script_traits 0.0.1",
  "servo-egl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-glutin 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "style_traits 0.0.1",
@@ -1225,17 +1231,17 @@ source = "registry+https://github.com/ru
 
 [[package]]
 name = "half"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "harfbuzz-sys"
-version = "0.1.14"
+version = "0.1.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cmake 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "freetype 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1435,16 +1441,34 @@ dependencies = [
 ]
 
 [[package]]
 name = "itoa"
 version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
+name = "jemalloc-sys"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "jemallocator"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "jemalloc-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "jpeg-decoder"
 version = "0.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1566,16 +1590,17 @@ dependencies = [
  "profile_traits 0.0.1",
  "range 0.0.1",
  "rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "script 0.0.1",
  "script_layout_interface 0.0.1",
  "script_traits 0.0.1",
  "selectors 0.19.0",
  "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "servo_allocator 0.0.1",
  "servo_arc 0.0.1",
  "servo_atoms 0.0.1",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "style 0.0.1",
  "style_traits 0.0.1",
  "webrender_api 0.52.1 (git+https://github.com/servo/webrender)",
@@ -1680,20 +1705,20 @@ dependencies = [
  "webrender 0.52.1 (git+https://github.com/servo/webrender)",
  "webrender_api 0.52.1 (git+https://github.com/servo/webrender)",
  "webvr 0.0.1",
  "webvr_traits 0.0.1",
 ]
 
 [[package]]
 name = "libz-sys"
-version = "1.0.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "gcc 0.3.47 (registry+https://github.com/rust-lang/crates.io-index)",
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "log"
 version = "0.3.8"
@@ -1728,17 +1753,16 @@ dependencies = [
 name = "malloc_size_of"
 version = "0.0.1"
 dependencies = [
  "app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "hashglobe 0.1.0",
  "js 0.1.6 (git+https://github.com/servo/rust-mozjs)",
- "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_arc 0.0.1",
  "smallbitvec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallvec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "string_cache 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_api 0.52.1 (git+https://github.com/servo/webrender)",
  "xml5ever 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -1876,20 +1900,20 @@ dependencies = [
 [[package]]
 name = "mitochondria"
 version = "1.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "mozjs_sys"
 version = "0.0.0"
-source = "git+https://github.com/servo/mozjs#834ce35c3f008010213351107b68f397989d2ffd"
+source = "git+https://github.com/servo/mozjs#424067c8d176bf1ee4c7f90c56916d35542de672"
 dependencies = [
  "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
- "libz-sys 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libz-sys 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "mp3-metadata"
 version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -2206,18 +2230,18 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
  "unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "osmesa-src"
-version = "17.3.0-devel"
-source = "git+https://github.com/servo/osmesa-src#100789c549a5716773a74a5db0a2e929470e2c9f"
+version = "17.3.1-devel"
+source = "git+https://github.com/servo/osmesa-src#6d23daede16f7edf7c66bdcce73c7583d0853733"
 
 [[package]]
 name = "osmesa-sys"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "shared_library 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -2358,16 +2382,17 @@ source = "registry+https://github.com/ru
 
 [[package]]
 name = "profile"
 version = "0.0.1"
 dependencies = [
  "heartbeats-simple 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "influent 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "jemalloc-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "profile_traits 0.0.1",
  "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "task_info 0.0.1",
@@ -2376,16 +2401,17 @@ dependencies = [
 
 [[package]]
 name = "profile_tests"
 version = "0.0.1"
 dependencies = [
  "ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "profile 0.0.1",
  "profile_traits 0.0.1",
+ "servo_allocator 0.0.1",
 ]
 
 [[package]]
 name = "profile_traits"
 version = "0.0.1"
 dependencies = [
  "energy-monitor 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "energymon 0.3.0 (git+https://github.com/energymon/energymon-rust.git)",
@@ -2625,16 +2651,17 @@ dependencies = [
  "ref_slice 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "script_layout_interface 0.0.1",
  "script_plugins 0.0.1",
  "script_traits 0.0.1",
  "selectors 0.19.0",
  "serde 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "servo_allocator 0.0.1",
  "servo_arc 0.0.1",
  "servo_atoms 0.0.1",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_rand 0.0.1",
  "servo_url 0.0.1",
  "smallvec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "style 0.0.1",
@@ -2832,22 +2859,22 @@ dependencies = [
 ]
 
 [[package]]
 name = "servo-fontconfig"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
- "servo-fontconfig-sys 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "servo-fontconfig-sys 4.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "servo-fontconfig-sys"
-version = "4.0.3"
+version = "4.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "expat-sys 2.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-freetype-sys 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2894,17 +2921,17 @@ dependencies = [
  "cmake 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "expat-sys 2.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "glx 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "io-surface 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-egl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "servo-fontconfig-sys 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "servo-fontconfig-sys 4.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-freetype-sys 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-glutin 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "x11 2.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "servo-websocket"
 version = "0.19.1"
@@ -2917,16 +2944,24 @@ dependencies = [
  "openssl 0.9.12 (registry+https://github.com/rust-lang/crates.io-index)",
  "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
+name = "servo_allocator"
+version = "0.0.1"
+dependencies = [
+ "jemallocator 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "servo_arc"
 version = "0.0.1"
 dependencies = [
  "nodrop 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -3438,17 +3473,17 @@ name = "unicode-normalization"
 version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "unicode-script"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "harfbuzz-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "harfbuzz-sys 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "unicode-segmentation"
 version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -3777,16 +3812,17 @@ dependencies = [
 "checksum blurz 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e73bda0f4c71c63a047351070097f3f507e6718e86b9ee525173371ef7b94b73"
 "checksum bow 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c0ad826831ef9226adeaa7fa94a866b2dd316258219ec9cefb58595818dc5c52"
 "checksum brotli 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "951f20a2cc403194b2746d9ac6f796292e3c0344e983c72c7d6bd6cff6c3d102"
 "checksum brotli-decompressor 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e80402aa0457c3c03d3996a36af7c1e7efa2ad97ee8ec7c2761b07666bab5566"
 "checksum browserhtml 0.1.17 (git+https://github.com/browserhtml/browserhtml?branch=crate)" = "<none>"
 "checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8"
 "checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27"
 "checksum caseless 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8950b075cff75cdabadee97148a8b5816c7cf62e5948a6005b5255d564b42fe7"
+"checksum cc 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2c674f0870e3dbd4105184ea035acb1c32c8ae69939c9e228d2b11bbfe29efad"
 "checksum cexpr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "393a5f0088efbe41f9d1fcd062f24e83c278608420e62109feb2c8abee07de7d"
 "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
 "checksum cgl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "86765cb42c2a2c497e142af72517c1b4d7ae5bb2f25dfa77a5c69642f2342d89"
 "checksum clang-sys 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5955eab05fa8e6ff2b353753dc73a0608daa36e472a21c69f2eb51f43f593544"
 "checksum clap 2.20.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7db281b0520e97fbd15cd615dcd8f8bcad0c26f5f7d5effe705f090f39e9a758"
 "checksum clipboard 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd3a9a938558f33ec1baaa6ca631a69c104aafaacbc66868d9ad28cf5f30564f"
 "checksum clipboard-win 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "693b1280c514045382dfdbb78d1594b1b03cdb66320aeb7ebd2bd38d49bae959"
 "checksum cmake 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "d18d68987ed4c516dcc3e7913659bfa4076f5182eea4a7e0038bb060953e76ac"
@@ -3848,17 +3884,17 @@ dependencies = [
 "checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685"
 "checksum gif 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8a80d6fe9e52f637df9afd4779449a7be17c39cc9c35b01589bb833f956ba596"
 "checksum gl_generator 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0940975a4ca12b088d32b5d5134826c47d2e73de4b0b459b05244c01503eccbb"
 "checksum gleam 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "bf887141f0c2a83eae026cbf3fba74f0a5cb0f01d20e5cdfcd8c4ad39295be1e"
 "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
 "checksum glx 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b280007fa9c7442cfd1e0b1addb8d1a59240267110e8705f8f7e2c7bfb7e2f72"
 "checksum gvr-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e84ba5e13cd925de87b669475525f956f8e936e67ddb24fbb1a077d96bbe174c"
 "checksum half 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "63d68db75012a85555434ee079e7e6337931f87a087ab2988becbadf64673a7f"
-"checksum harfbuzz-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "a2caaa66078fdfacea32db1351223697a1167ad2d4bbee6b8d4ca220ce5b10b3"
+"checksum harfbuzz-sys 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "52aa65c5649a0a2f1b27ab30093b3cc84681e17ddb552267e21948c5a6fa6b05"
 "checksum heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4c7593b1522161003928c959c20a2ca421c68e940d63d75573316a009e48a6d4"
 "checksum heartbeats-simple 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9ad003ce233955e9d95f2c69cde84e68302ba9ba4a673d351c9bff93c738aadc"
 "checksum heartbeats-simple-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e1a408c0011427cc0e0049f7861c70377819aedfc006e8c901b1c70fd98fb1a4"
 "checksum html5ever 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5bfb46978eb757a603b7dfe2dafb1c62cb4dee3428d8ac1de734d83d6b022d06"
 "checksum httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e7a63e511f9edffbab707141fbb8707d1a3098615fb2adbd5769cdfcc9b17d"
 "checksum hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)" = "368cb56b2740ebf4230520e2b90ebb0461e69034d85d1945febd9b3971426db2"
 "checksum hyper-openssl 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "85a372eb692590b3fe014c196c30f9f52d4c42f58cd49dd94caeee1593c9cc37"
 "checksum hyper_serde 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cbe43f514f80494e9329c9fc47d61b85b167d245685424637a0f4a409177e444"
@@ -3868,29 +3904,31 @@ dependencies = [
 "checksum immeta 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0b9260463a221bfe3f02100c56e2d14c050d5ffe7e44a43d0a1b2b1f2b523502"
 "checksum inflate 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "10ec05638adf7c5c788bc0cfa608cd479a13572beda20feb4898fe1d85d2c64b"
 "checksum influent 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a22b311b83431be3ab9af96ca9ea41554bb4a8551ea871ae44c3ce0c57e55f2c"
 "checksum io-surface 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad4578cee6ed49a17766fa608a4425008313c55ebb7a267622cbddd6b01751e2"
 "checksum iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "29d062ee61fccdf25be172e70f34c9f6efc597e1fb8f6526e8437b2046ab26be"
 "checksum ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c10ed089b1921b01ef342c736a37ee0788eeb9a5f373bb2df1ba88d01125064f"
 "checksum itertools 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4833d6978da405305126af4ac88569b5d71ff758581ce5a987dbfa3755f694fc"
 "checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c"
+"checksum jemalloc-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94fb624d7e8345e5c42caab8d1db6ec925fdadff3fd0cb7dd781b41be8442828"
+"checksum jemallocator 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1850725977c344d63af66e8fd00857646e3ec936c490cd63667860b7b03ab5c1"
 "checksum jpeg-decoder 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2805ccb10ffe4d10e06ef68a158ff94c255211ecbae848fbde2146b098f93ce7"
 "checksum js 0.1.6 (git+https://github.com/servo/rust-mozjs)" = "<none>"
 "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
 "checksum khronos_api 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d5a08e2a31d665af8f1ca437eab6d00a93c9d62a549f73f9ed8fc2e55b5a91a7"
 "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
 "checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf"
 "checksum lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce12306c4739d86ee97c23139f3a34ddf0387bbf181bc7929d287025a8c3ef6b"
 "checksum leak 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bd100e01f1154f2908dfa7d02219aeab25d0b9c7fa955164192e3245255a0c73"
 "checksum leaky-cow 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40a8225d44241fd324a8af2806ba635fc7c8a7e9a7de4d5cf3ef54e71f5926fc"
 "checksum len-trait 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "723558ab8acaa07cb831b424cd164b587ddc1648b34748a30953c404e9a4a65b"
 "checksum libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)" = "e7eb6b826bfc1fdea7935d46556250d1799b7fe2d9f7951071f4291710665e3e"
 "checksum libloading 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "be99f814beb3e9503a786a592c909692bb6d4fc5a695f6ed7987223acfbd5194"
-"checksum libz-sys 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "3fdd64ef8ee652185674455c1d450b83cbc8ad895625d543b5324d923f82e4d8"
+"checksum libz-sys 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "87f737ad6cc6fd6eefe3d9dc5412f1573865bded441300904d2f42269e140f16"
 "checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b"
 "checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084"
 "checksum mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
 "checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
 "checksum markup5ever 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "047150a0e03b57e638fc45af33a0b63a0362305d5b9f92ecef81df472a4cceb0"
 "checksum matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efd7622e3022e1a6eaa602c4cea8912254e5582c9c692e9167714182244801b1"
 "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4"
 "checksum metadeps 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829fffe7ea1d747e23f64be972991bc516b2f1ac2ae4a3b33d8bea150c410151"
@@ -3918,17 +3956,17 @@ dependencies = [
 "checksum odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "c3df9b730298cea3a1c3faa90b7e2f9df3a9c400d0936d6015e6165734eefcba"
 "checksum offscreen_gl_context 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ee0b17158af8e1fb6c8f8df048bd3f1c4b78bebbf4883f189e2a83ce853ce9bd"
 "checksum ogg 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7137bf02687385302f4c0aecd77cfce052b69f5b4ee937be778e125c62f67e30"
 "checksum ogg_metadata 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fc665717454399cba557c55ad226148996e9266ee291f8a37a98bb2cded0a490"
 "checksum open 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3478ed1686bd1300c8a981a940abc92b06fac9cbef747f4c668d4e032ff7b842"
 "checksum openssl 0.9.12 (registry+https://github.com/rust-lang/crates.io-index)" = "bb5d1663b73d10c6a3eda53e2e9d0346f822394e7b858d7257718f65f61dfbe2"
 "checksum openssl-sys 0.9.12 (registry+https://github.com/rust-lang/crates.io-index)" = "3a5886d87d3e2a0d890bf62dc8944f5e3769a405f7e1e9ef6e517e47fd7a0897"
 "checksum ordered-float 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "da12c96037889ae0be29dd2bdd260e5a62a7df24e6466d5a15bb8131c1c200a8"
-"checksum osmesa-src 17.3.0-devel (git+https://github.com/servo/osmesa-src)" = "<none>"
+"checksum osmesa-src 17.3.1-devel (git+https://github.com/servo/osmesa-src)" = "<none>"
 "checksum osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b"
 "checksum ovr-mobile-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7b5f9389b2015f8340f0566c488f3e96735e2e8fd7b85d571832cd274ac2998"
 "checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37"
 "checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e"
 "checksum parking_lot_core 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4f610cb9664da38e417ea3225f23051f589851999535290e077939838ab7a595"
 "checksum parse-hosts 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f3842db828281691db6e6f1709ed6a73025747c0cd15cc10346a5cb021e2bc28"
 "checksum peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
 "checksum percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de154f638187706bde41d9b4738748933d64e6b37bdbffc0b47a97d16a6ae356"
@@ -3965,17 +4003,17 @@ dependencies = [
 "checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac"
 "checksum serde 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "bcb6a7637a47663ee073391a139ed07851f27ed2532c2abc88c6bf27a16cdf34"
 "checksum serde_bytes 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a73f5ad9bb83e1e407254c7a355f4efdaffe3c1442fc0657ddb8b9b6b225655"
 "checksum serde_derive 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "812ff66056fd9a9a5b7c119714243b0862cf98340e7d4b5ee05a932c40d5ea6c"
 "checksum serde_derive_internals 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd381f6d01a6616cdba8530492d453b7761b456ba974e98768a18cad2cd76f58"
 "checksum serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "48b04779552e92037212c3615370f6bd57a40ebba7f20e554ff9f55e41a69a7b"
 "checksum servo-egl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "21069a884c33fe6ee596975e1f3849ed88c4ec857fbaf11d33672d8ebe051217"
 "checksum servo-fontconfig 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "93f799b649b4a2bf362398910eca35240704c7e765e780349b2bb1070d892262"
-"checksum servo-fontconfig-sys 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6be80777ee6edecbbbf8774c76e19dddfe336256c57a4ded06d6ad3df7be358e"
+"checksum servo-fontconfig-sys 4.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "38b494f03009ee81914b0e7d387ad7c145cafcd69747c2ec89b0e17bb94f303a"
 "checksum servo-freetype-sys 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9232032c2e85118c0282c6562c84cab12316e655491ba0a5d1905b2320060d1b"
 "checksum servo-glutin 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05bfa660ed7bceb5df3d79ca5a9c99a59fa7019c4477cbe52b70f80034bce568"
 "checksum servo-skia 0.30000006.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c104b3a0b3938f760772c09584fd6b2bb7ca205e2553a767b357f8ddbfbccbc"
 "checksum servo-websocket 0.19.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8a1ff13c5d852c2793805226e688044309f2c1d8f063784805a13e99cb75b611"
 "checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c"
 "checksum shared_library 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fb04126b6fcfd2710fb5b6d18f4207b6c535f2850a7e1a43bcd526d44f30a79a"
 "checksum shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72f20b8f3c060374edb8046591ba28f62448c369ccbdc7b02075103fb3a9e38d"
 "checksum sig 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c6649e43c1a1e68d29ed56d0dc3b5b6cf3b901da77cf107c4066b9e3da036df5"
new file mode 100644
--- /dev/null
+++ b/servo/components/allocator/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "servo_allocator"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+license = "MPL-2.0"
+publish = false
+
+[lib]
+path = "lib.rs"
+
+[features]
+unstable = ["kernel32-sys", "jemallocator"]
+
+[target.'cfg(not(windows))'.dependencies]
+jemallocator = { version = "0.1.3", optional = true }
+
+[target.'cfg(windows)'.dependencies]
+kernel32-sys = { version = "0.2.1", optional = true }
new file mode 100644
--- /dev/null
+++ b/servo/components/allocator/lib.rs
@@ -0,0 +1,62 @@
+/* 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/. */
+
+//! Selecting the default global allocator for Servo
+
+#![cfg_attr(all(feature = "unstable", windows), feature(alloc_system, allocator_api))]
+#![cfg_attr(feature = "unstable", feature(global_allocator))]
+
+#[cfg(feature = "unstable")]
+#[global_allocator]
+static ALLOC: platform::Allocator = platform::Allocator;
+
+pub use platform::usable_size;
+
+
+#[cfg(all(feature = "unstable", not(windows)))]
+mod platform {
+    extern crate jemallocator;
+
+    pub use self::jemallocator::Jemalloc as Allocator;
+    use std::os::raw::c_void;
+
+    /// Get the size of a heap block.
+    pub unsafe extern "C" fn usable_size(ptr: *const c_void) -> usize {
+        jemallocator::usable_size(ptr)
+    }
+}
+
+#[cfg(all(feature = "unstable", windows))]
+mod platform {
+    extern crate alloc_system;
+    extern crate kernel32;
+
+    pub use self::alloc_system::System as Allocator;
+    use self::kernel32::{GetProcessHeap, HeapSize, HeapValidate};
+    use std::os::raw::c_void;
+
+    /// Get the size of a heap block.
+    pub unsafe extern "C" fn usable_size(mut ptr: *const c_void) -> usize {
+        let heap = GetProcessHeap();
+
+        if HeapValidate(heap, 0, ptr) == 0 {
+            ptr = *(ptr as *const *const c_void).offset(-1);
+        }
+
+        HeapSize(heap, 0, ptr) as usize
+    }
+}
+
+#[cfg(not(feature = "unstable"))]
+mod platform {
+    use std::os::raw::c_void;
+
+    /// Without `#[global_allocator]` we cannot be certain of what allocator is used
+    /// or how it is linked. We therefore disable memory reporting. (Return zero.)
+    pub unsafe extern "C" fn usable_size(_ptr: *const c_void) -> usize {
+        0
+    }
+}
+
+
--- a/servo/components/gfx/Cargo.toml
+++ b/servo/components/gfx/Cargo.toml
@@ -48,16 +48,17 @@ xi-unicode = "0.1.0"
 [target.'cfg(target_os = "macos")'.dependencies]
 byteorder = "1.0"
 core-foundation = "0.4"
 core-graphics = "0.9"
 core-text = "7.0"
 
 [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
 freetype = "0.3"
+servo_allocator = {path = "../allocator"}
 
 [target.'cfg(target_os = "linux")'.dependencies]
 servo-fontconfig = "0.2.1"
 
 [target.'cfg(target_os = "android")'.dependencies]
 xml5ever = {version = "0.10"}
 
 [target.'cfg(any(target_feature = "sse2", target_feature = "neon"))'.dependencies]
--- a/servo/components/gfx/lib.rs
+++ b/servo/components/gfx/lib.rs
@@ -22,18 +22,18 @@ extern crate bitflags;
 #[cfg(target_os = "windows")] extern crate truetype;
 
 extern crate euclid;
 extern crate fnv;
 
 #[cfg(target_os = "linux")]
 extern crate fontconfig;
 extern crate fontsan;
-#[cfg(any(target_os = "linux", target_os = "android"))]
-extern crate freetype;
+#[cfg(any(target_os = "linux", target_os = "android"))] extern crate freetype;
+#[cfg(any(target_os = "linux", target_os = "android"))] extern crate servo_allocator;
 extern crate gfx_traits;
 
 // Eventually we would like the shaper to be pluggable, as many operating systems have their own
 // shapers. For now, however, this is a hard dependency.
 extern crate harfbuzz_sys as harfbuzz;
 
 extern crate ipc_channel;
 #[macro_use]
--- a/servo/components/gfx/platform/freetype/font_context.rs
+++ b/servo/components/gfx/platform/freetype/font_context.rs
@@ -3,17 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use freetype::freetype::FT_Add_Default_Modules;
 use freetype::freetype::FT_Done_Library;
 use freetype::freetype::FT_Library;
 use freetype::freetype::FT_Memory;
 use freetype::freetype::FT_MemoryRec_;
 use freetype::freetype::FT_New_Library;
-use malloc_size_of::{malloc_size_of, MallocSizeOf, MallocSizeOfOps};
+use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
+use servo_allocator::usable_size;
 use std::mem;
 use std::os::raw::{c_long, c_void};
 use std::ptr;
 use std::rc::Rc;
 
 // We pass a |User| struct -- via an opaque |void*| -- to FreeType each time a new instance is
 // created. FreeType passes it back to the ft_alloc/ft_realloc/ft_free callbacks. We use it to
 // record the memory usage of each FreeType instance.
@@ -26,57 +27,58 @@ const FT_ALIGNMENT: usize = 1;
 
 extern fn ft_alloc(mem: FT_Memory, req_size: c_long) -> *mut c_void {
     assert!(FT_ALIGNMENT == 1);
     let mut vec = Vec::<u8>::with_capacity(req_size as usize);
     let ptr = vec.as_mut_ptr() as *mut c_void;
     mem::forget(vec);
 
     unsafe {
-        let actual_size = malloc_size_of(ptr as *const _);
+        let actual_size = usable_size(ptr as *const _);
         let user = (*mem).user as *mut User;
         (*user).size += actual_size;
     }
 
     ptr
 }
 
 extern fn ft_free(mem: FT_Memory, ptr: *mut c_void) {
     unsafe {
-        let actual_size = malloc_size_of(ptr as *const _);
+        let actual_size = usable_size(ptr as *const _);
         let user = (*mem).user as *mut User;
         (*user).size -= actual_size;
 
         assert!(FT_ALIGNMENT == 1);
         mem::drop(Vec::<u8>::from_raw_parts(ptr as *mut u8, actual_size, 0))
     }
 }
 
-extern fn ft_realloc(mem: FT_Memory, _cur_size: c_long, new_req_size: c_long,
+extern fn ft_realloc(mem: FT_Memory, old_size: c_long, new_req_size: c_long,
                      old_ptr: *mut c_void) -> *mut c_void {
     let old_actual_size;
     let mut vec;
     unsafe {
-        old_actual_size = malloc_size_of(old_ptr as *const _);
-        vec = Vec::<u8>::from_raw_parts(old_ptr as *mut u8, old_actual_size, old_actual_size);
+        old_actual_size = usable_size(old_ptr as *const _);
+        let old_size = old_size as usize;
+        vec = Vec::<u8>::from_raw_parts(old_ptr as *mut u8, old_size, old_size);
     };
 
     let new_req_size = new_req_size as usize;
     if new_req_size > old_actual_size {
         vec.reserve_exact(new_req_size - old_actual_size)
     } else if new_req_size < old_actual_size {
         vec.truncate(new_req_size);
         vec.shrink_to_fit()
     }
 
     let new_ptr = vec.as_mut_ptr() as *mut c_void;
     mem::forget(vec);
 
     unsafe {
-        let new_actual_size = malloc_size_of(new_ptr as *const _);
+        let new_actual_size = usable_size(new_ptr as *const _);
         let user = (*mem).user as *mut User;
         (*user).size += new_actual_size;
         (*user).size -= old_actual_size;
     }
 
     new_ptr
 }
 
--- a/servo/components/layout_thread/Cargo.toml
+++ b/servo/components/layout_thread/Cargo.toml
@@ -35,16 +35,17 @@ parking_lot = "0.4"
 profile_traits = {path = "../profile_traits"}
 range = {path = "../range"}
 rayon = "0.8"
 script = {path = "../script"}
 script_layout_interface = {path = "../script_layout_interface"}
 script_traits = {path = "../script_traits"}
 selectors = { path = "../selectors" }
 serde_json = "1.0"
+servo_allocator = {path = "../allocator"}
 servo_arc = {path = "../servo_arc"}
 servo_atoms = {path = "../atoms"}
 servo_config = {path = "../config"}
 servo_geometry = {path = "../geometry"}
 servo_url = {path = "../url"}
 style = {path = "../style"}
 style_traits = {path = "../style_traits"}
 webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]}
--- a/servo/components/layout_thread/dom_wrapper.rs
+++ b/servo/components/layout_thread/dom_wrapper.rs
@@ -681,31 +681,33 @@ impl<'le> ::selectors::Element for Servo
         self.element.local_name()
     }
 
     #[inline]
     fn get_namespace(&self) -> &Namespace {
         self.element.namespace()
     }
 
-    fn match_pseudo_element(&self,
-                            _pseudo: &PseudoElement,
-                            _context: &mut MatchingContext)
-                            -> bool
-    {
+    fn match_pseudo_element(
+        &self,
+        _pseudo: &PseudoElement,
+        _context: &mut MatchingContext<Self::Impl>,
+    ) -> bool {
         false
     }
 
-    fn match_non_ts_pseudo_class<F>(&self,
-                                    pseudo_class: &NonTSPseudoClass,
-                                    _: &mut MatchingContext,
-                                    _: &RelevantLinkStatus,
-                                    _: &mut F)
-                                    -> bool
-        where F: FnMut(&Self, ElementSelectorFlags),
+    fn match_non_ts_pseudo_class<F>(
+        &self,
+        pseudo_class: &NonTSPseudoClass,
+        _: &mut MatchingContext<Self::Impl>,
+        _: &RelevantLinkStatus,
+        _: &mut F,
+    ) -> bool
+    where
+        F: FnMut(&Self, ElementSelectorFlags),
     {
         match *pseudo_class {
             // https://github.com/servo/servo/issues/8718
             NonTSPseudoClass::Link |
             NonTSPseudoClass::AnyLink => self.is_link(),
             NonTSPseudoClass::Visited => false,
 
             NonTSPseudoClass::Lang(ref lang) => self.match_element_lang(None, &*lang),
@@ -1171,21 +1173,21 @@ impl<'le> ::selectors::Element for Servo
         self.element.get_local_name()
     }
 
     #[inline]
     fn get_namespace(&self) -> &Namespace {
         self.element.get_namespace()
     }
 
-    fn match_pseudo_element(&self,
-                            _pseudo: &PseudoElement,
-                            _context: &mut MatchingContext)
-                            -> bool
-    {
+    fn match_pseudo_element(
+        &self,
+        _pseudo: &PseudoElement,
+        _context: &mut MatchingContext<Self::Impl>
+    ) -> bool {
         false
     }
 
     fn attr_matches(&self,
                     ns: &NamespaceConstraint<&Namespace>,
                     local_name: &LocalName,
                     operation: &AttrSelectorOperation<&String>)
                     -> bool {
@@ -1198,23 +1200,25 @@ impl<'le> ::selectors::Element for Servo
                 let values = unsafe {
                     (*self.element.element.unsafe_get()).get_attr_vals_for_layout(local_name)
                 };
                 values.iter().any(|v| v.eval_selector(operation))
             }
         }
     }
 
-    fn match_non_ts_pseudo_class<F>(&self,
-                                    _: &NonTSPseudoClass,
-                                    _: &mut MatchingContext,
-                                    _: &RelevantLinkStatus,
-                                    _: &mut F)
-                                    -> bool
-        where F: FnMut(&Self, ElementSelectorFlags),
+    fn match_non_ts_pseudo_class<F>(
+        &self,
+        _: &NonTSPseudoClass,
+        _: &mut MatchingContext<Self::Impl>,
+        _: &RelevantLinkStatus,
+        _: &mut F,
+    ) -> bool
+    where
+        F: FnMut(&Self, ElementSelectorFlags),
     {
         // NB: This could maybe be implemented
         warn!("ServoThreadSafeLayoutElement::match_non_ts_pseudo_class called");
         false
     }
 
     fn is_link(&self) -> bool {
         warn!("ServoThreadSafeLayoutElement::is_link called");
--- a/servo/components/layout_thread/lib.rs
+++ b/servo/components/layout_thread/lib.rs
@@ -34,16 +34,17 @@ extern crate parking_lot;
 extern crate profile_traits;
 extern crate range;
 extern crate rayon;
 extern crate script;
 extern crate script_layout_interface;
 extern crate script_traits;
 extern crate selectors;
 extern crate serde_json;
+extern crate servo_allocator;
 extern crate servo_arc;
 extern crate servo_atoms;
 extern crate servo_config;
 extern crate servo_geometry;
 extern crate servo_url;
 extern crate style;
 extern crate style_traits;
 extern crate webrender_api;
@@ -79,17 +80,17 @@ use layout::query::{process_margin_style
 use layout::query::{process_node_geometry_request, process_node_scroll_area_request};
 use layout::query::{process_node_scroll_root_id_request, process_offset_parent_query};
 use layout::sequential;
 use layout::traversal::{ComputeStackingRelativePositions, PreorderFlowTraversal, RecalcStyleAndConstructFlows};
 use layout::webrender_helpers::WebRenderDisplayListConverter;
 use layout::wrapper::LayoutNodeLayoutData;
 use layout_traits::LayoutThreadFactory;
 use libc::c_void;
-use malloc_size_of::{malloc_size_of, MallocSizeOf, MallocSizeOfOps};
+use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
 use metrics::{PaintTimeMetrics, ProfilerMetadataFactory};
 use msg::constellation_msg::PipelineId;
 use msg::constellation_msg::TopLevelBrowsingContextId;
 use net_traits::image_cache::{ImageCache, UsePlaceholder};
 use parking_lot::RwLock;
 use profile_traits::mem::{self, Report, ReportKind, ReportsChan};
 use profile_traits::time::{self, TimerMetadata, profile};
 use profile_traits::time::{TimerMetadataFrameType, TimerMetadataReflowType};
@@ -770,17 +771,17 @@ impl LayoutThread {
     }
 
     fn collect_reports<'a, 'b>(&self,
                                reports_chan: ReportsChan,
                                possibly_locked_rw_data: &mut RwData<'a, 'b>) {
         let mut reports = vec![];
         // Servo uses vanilla jemalloc, which doesn't have a
         // malloc_enclosing_size_of function.
-        let mut ops = MallocSizeOfOps::new(malloc_size_of, None, None);
+        let mut ops = MallocSizeOfOps::new(::servo_allocator::usable_size, None, None);
 
         // FIXME(njn): Just measuring the display tree for now.
         let rw_data = possibly_locked_rw_data.lock();
         let display_list = rw_data.display_list.as_ref();
         let formatted_url = &format!("url({})", self.url);
         reports.push(Report {
             path: path![formatted_url, "layout-thread", "display-list"],
             kind: ReportKind::ExplicitJemallocHeapSize,
--- a/servo/components/malloc_size_of/Cargo.toml
+++ b/servo/components/malloc_size_of/Cargo.toml
@@ -3,19 +3,16 @@ name = "malloc_size_of"
 version = "0.0.1"
 authors = ["The Servo Project Developers"]
 license = "MIT/Apache-2.0"
 publish = false
 
 [lib]
 path = "lib.rs"
 
-[target.'cfg(windows)'.dependencies]
-kernel32-sys = "0.2.1"
-
 [features]
 servo = ["js", "string_cache", "url", "webrender_api", "xml5ever"]
 
 [dependencies]
 app_units = "0.5.5"
 cssparser = "0.22.0"
 euclid = "0.15"
 hashglobe = { path = "../hashglobe" }
--- a/servo/components/malloc_size_of/lib.rs
+++ b/servo/components/malloc_size_of/lib.rs
@@ -44,32 +44,28 @@
 //!   `<Box<_> as MallocSizeOf>::size_of(field, ops)`.
 
 extern crate app_units;
 extern crate cssparser;
 extern crate euclid;
 extern crate hashglobe;
 #[cfg(feature = "servo")]
 extern crate js;
-#[cfg(target_os = "windows")]
-extern crate kernel32;
 extern crate servo_arc;
 extern crate smallbitvec;
 extern crate smallvec;
 #[cfg(feature = "servo")]
 extern crate string_cache;
 #[cfg(feature = "servo")]
 extern crate url;
 #[cfg(feature = "servo")]
 extern crate webrender_api;
 #[cfg(feature = "servo")]
 extern crate xml5ever;
 
-#[cfg(target_os = "windows")]
-use kernel32::{GetProcessHeap, HeapSize, HeapValidate};
 use std::hash::{BuildHasher, Hash};
 use std::mem::size_of;
 use std::ops::Range;
 use std::os::raw::c_void;
 
 /// A C function that takes a pointer to a heap allocation and returns its size.
 type VoidPtrToSizeFn = unsafe extern "C" fn(ptr: *const c_void) -> usize;
 
@@ -141,43 +137,16 @@ impl MallocSizeOfOps {
 
     /// Call `have_seen_ptr_op` on `ptr`.
     pub fn have_seen_ptr<T>(&mut self, ptr: *const T) -> bool {
         let have_seen_ptr_op = self.have_seen_ptr_op.as_mut().expect("missing have_seen_ptr_op");
         have_seen_ptr_op(ptr as *const c_void)
     }
 }
 
-/// Get the size of a heap block.
-#[cfg(not(target_os = "windows"))]
-pub unsafe extern "C" fn malloc_size_of(ptr: *const c_void) -> usize {
-    // The C prototype is `je_malloc_usable_size(JEMALLOC_USABLE_SIZE_CONST void *ptr)`. On some
-    // platforms `JEMALLOC_USABLE_SIZE_CONST` is `const` and on some it is empty. But in practice
-    // this function doesn't modify the contents of the block that `ptr` points to, so we use
-    // `*const c_void` here.
-    extern "C" {
-        #[cfg_attr(any(prefixed_jemalloc, target_os = "macos", target_os = "ios", target_os = "android"),
-                   link_name = "je_malloc_usable_size")]
-        fn malloc_usable_size(ptr: *const c_void) -> usize;
-    }
-    malloc_usable_size(ptr)
-}
-
-/// Get the size of a heap block.
-#[cfg(target_os = "windows")]
-pub unsafe extern "C" fn malloc_size_of(mut ptr: *const c_void) -> usize {
-    let heap = GetProcessHeap();
-
-    if HeapValidate(heap, 0, ptr) == 0 {
-        ptr = *(ptr as *const *const c_void).offset(-1);
-    }
-
-    HeapSize(heap, 0, ptr) as usize
-}
-
 /// Trait for measuring the "deep" heap usage of a data structure. This is the
 /// most commonly-used of the traits.
 pub trait MallocSizeOf {
     /// Measure the heap usage of all descendant heap-allocated structures, but
     /// not the space taken up by the value itself.
     fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize;
 }
 
--- a/servo/components/profile/Cargo.toml
+++ b/servo/components/profile/Cargo.toml
@@ -5,17 +5,17 @@ authors = ["The Servo Project Developers
 license = "MPL-2.0"
 publish = false
 
 [lib]
 name = "profile"
 path = "lib.rs"
 
 [features]
-unstable = []
+unstable = ["jemalloc-sys"]
 
 [dependencies]
 profile_traits = {path = "../profile_traits"}
 influent = "0.4"
 ipc-channel = "0.9"
 heartbeats-simple = "0.4"
 log = "0.3.5"
 serde = "1.0"
@@ -26,8 +26,9 @@ time = "0.1.12"
 [target.'cfg(target_os = "macos")'.dependencies]
 task_info = {path = "../../support/rust-task_info"}
 
 [target.'cfg(target_os = "linux")'.dependencies]
 regex = "0.2"
 
 [target.'cfg(not(target_os = "windows"))'.dependencies]
 libc = "0.2"
+jemalloc-sys = {version = "0.1.3", optional = true}
--- a/servo/components/profile/lib.rs
+++ b/servo/components/profile/lib.rs
@@ -1,23 +1,20 @@
 /* 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/. */
 
-#![cfg_attr(all(feature = "unstable", not(target_os = "windows")), feature(alloc_jemalloc))]
-
 #![deny(unsafe_code)]
 
 #[allow(unused_extern_crates)]
-#[cfg(all(feature = "unstable", not(target_os = "windows")))]
-extern crate alloc_jemalloc;
 extern crate heartbeats_simple;
 extern crate influent;
 extern crate ipc_channel;
-#[allow(unused_extern_crates)]
+#[cfg(all(feature = "unstable", not(target_os = "windows")))]
+extern crate jemalloc_sys;
 #[cfg(not(target_os = "windows"))]
 extern crate libc;
 #[macro_use]
 extern crate log;
 #[macro_use]
 extern crate profile_traits;
 #[cfg(target_os = "linux")]
 extern crate regex;
--- a/servo/components/profile/mem.rs
+++ b/servo/components/profile/mem.rs
@@ -349,17 +349,17 @@ impl ReportsForest {
         }
     }
 }
 
 //---------------------------------------------------------------------------
 
 mod system_reporter {
     #[cfg(all(feature = "unstable", not(target_os = "windows")))]
-    use libc::{c_char, c_void, size_t};
+    use libc::{c_void, size_t};
     #[cfg(target_os = "linux")]
     use libc::c_int;
     use profile_traits::mem::{Report, ReportKind, ReporterRequest};
     #[cfg(all(feature = "unstable", not(target_os = "windows")))]
     use std::ffi::CString;
     #[cfg(all(feature = "unstable", not(target_os = "windows")))]
     use std::mem::size_of;
     #[cfg(all(feature = "unstable", not(target_os = "windows")))]
@@ -455,21 +455,17 @@ mod system_reporter {
     }
 
     #[cfg(not(target_os = "linux"))]
     fn system_heap_allocated() -> Option<usize> {
         None
     }
 
     #[cfg(all(feature = "unstable", not(target_os = "windows")))]
-    extern {
-        #[cfg_attr(any(target_os = "macos", target_os = "android"), link_name = "je_mallctl")]
-        fn mallctl(name: *const c_char, oldp: *mut c_void, oldlenp: *mut size_t,
-                   newp: *mut c_void, newlen: size_t) -> ::libc::c_int;
-    }
+    use jemalloc_sys::mallctl;
 
     #[cfg(all(feature = "unstable", not(target_os = "windows")))]
     fn jemalloc_stat(value_name: &str) -> Option<usize> {
         // Before we request the measurement of interest, we first send an "epoch"
         // request. Without that jemalloc gives cached statistics(!) which can be
         // highly inaccurate.
         let epoch_name = "epoch";
         let epoch_c_name = CString::new(epoch_name).unwrap();
--- a/servo/components/script/Cargo.toml
+++ b/servo/components/script/Cargo.toml
@@ -8,17 +8,17 @@ publish = false
 build = "build.rs"
 
 [lib]
 name = "script"
 path = "lib.rs"
 
 [features]
 debugmozjs = ['js/debugmozjs']
-unstable = []
+unstable = ["servo_allocator/unstable"]
 
 [build-dependencies]
 cmake = "0.1"
 phf_codegen = "0.7.18"
 phf_shared = "0.7.18"
 serde_json = "1.0"
 
 [target.'cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))'.dependencies]
@@ -72,16 +72,17 @@ profile_traits = {path = "../profile_tra
 ref_filter_map = "1.0.1"
 ref_slice = "1.0"
 regex = "0.2"
 script_layout_interface = {path = "../script_layout_interface"}
 script_plugins = {path = "../script_plugins"}
 script_traits = {path = "../script_traits"}
 selectors = { path = "../selectors" }
 serde = "1.0"
+servo_allocator = {path = "../allocator"}
 servo_arc = {path = "../servo_arc"}
 servo_atoms = {path = "../atoms"}
 servo_config = {path = "../config"}
 servo_geometry = {path = "../geometry" }
 servo_rand = {path = "../rand"}
 servo_url = {path = "../url"}
 smallvec = "0.4"
 style = {path = "../style"}
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -2523,21 +2523,21 @@ impl<'a> SelectorsElement for DomRoot<El
     fn opaque(&self) -> ::selectors::OpaqueElement {
         ::selectors::OpaqueElement::new(self.reflector().get_jsobject().get())
     }
 
     fn parent_element(&self) -> Option<DomRoot<Element>> {
         self.upcast::<Node>().GetParentElement()
     }
 
-    fn match_pseudo_element(&self,
-                            _pseudo: &PseudoElement,
-                            _context: &mut MatchingContext)
-                            -> bool
-    {
+    fn match_pseudo_element(
+        &self,
+        _pseudo: &PseudoElement,
+        _context: &mut MatchingContext<Self::Impl>,
+    ) -> bool {
         false
     }
 
 
     fn first_child_element(&self) -> Option<DomRoot<Element>> {
         self.node.child_elements().next()
     }
 
@@ -2589,23 +2589,25 @@ impl<'a> SelectorsElement for DomRoot<El
     fn get_local_name(&self) -> &LocalName {
         self.local_name()
     }
 
     fn get_namespace(&self) -> &Namespace {
         self.namespace()
     }
 
-    fn match_non_ts_pseudo_class<F>(&self,
-                                    pseudo_class: &NonTSPseudoClass,
-                                    _: &mut MatchingContext,
-                                    _: &RelevantLinkStatus,
-                                    _: &mut F)
-                                    -> bool
-        where F: FnMut(&Self, ElementSelectorFlags),
+    fn match_non_ts_pseudo_class<F>(
+        &self,
+        pseudo_class: &NonTSPseudoClass,
+        _: &mut MatchingContext<Self::Impl>,
+        _: &RelevantLinkStatus,
+        _: &mut F,
+    ) -> bool
+    where
+        F: FnMut(&Self, ElementSelectorFlags),
     {
         match *pseudo_class {
             // https://github.com/servo/servo/issues/8718
             NonTSPseudoClass::Link |
             NonTSPseudoClass::AnyLink => self.is_link(),
             NonTSPseudoClass::Visited => false,
 
             NonTSPseudoClass::ServoNonZeroBorder => {
--- a/servo/components/script/lib.rs
+++ b/servo/components/script/lib.rs
@@ -73,16 +73,17 @@ extern crate phf;
 extern crate profile_traits;
 extern crate ref_filter_map;
 extern crate ref_slice;
 extern crate regex;
 extern crate script_layout_interface;
 extern crate script_traits;
 extern crate selectors;
 extern crate serde;
+extern crate servo_allocator;
 extern crate servo_arc;
 #[macro_use] extern crate servo_atoms;
 extern crate servo_config;
 extern crate servo_geometry;
 extern crate servo_rand;
 extern crate servo_url;
 extern crate smallvec;
 #[macro_use]
--- a/servo/components/script/script_thread.rs
+++ b/servo/components/script/script_thread.rs
@@ -68,17 +68,17 @@ use hyper::mime::{Mime, SubLevel, TopLev
 use hyper_serde::Serde;
 use ipc_channel::ipc::{self, IpcSender};
 use ipc_channel::router::ROUTER;
 use js::glue::GetWindowProxyClass;
 use js::jsapi::{JSAutoCompartment, JSContext, JS_SetWrapObjectCallbacks};
 use js::jsapi::{JSTracer, SetWindowProxyClass};
 use js::jsval::UndefinedValue;
 use js::rust::Runtime;
-use malloc_size_of::{malloc_size_of, MallocSizeOfOps};
+use malloc_size_of::MallocSizeOfOps;
 use mem::malloc_size_of_including_self;
 use metrics::PaintTimeMetrics;
 use microtask::{MicrotaskQueue, Microtask};
 use msg::constellation_msg::{BrowsingContextId, FrameType, PipelineId, PipelineNamespace, TopLevelBrowsingContextId};
 use net_traits::{FetchMetadata, FetchResponseListener, FetchResponseMsg};
 use net_traits::{Metadata, NetworkError, ReferrerPolicy, ResourceThreads};
 use net_traits::image_cache::{ImageCache, PendingImageResponse};
 use net_traits::request::{CredentialsMode, Destination, RedirectMode, RequestInit};
@@ -1501,17 +1501,17 @@ impl ScriptThread {
     }
 
     fn collect_reports(&self, reports_chan: ReportsChan) {
         let mut path_seg = String::from("url(");
         let mut dom_tree_size = 0;
         let mut reports = vec![];
         // Servo uses vanilla jemalloc, which doesn't have a
         // malloc_enclosing_size_of function.
-        let mut ops = MallocSizeOfOps::new(malloc_size_of, None, None);
+        let mut ops = MallocSizeOfOps::new(::servo_allocator::usable_size, None, None);
 
         for (_, document) in self.documents.borrow().iter() {
             let current_url = document.url();
 
             for child in document.upcast::<Node>().traverse_preorder() {
                 dom_tree_size += malloc_size_of_including_self(&mut ops, &*child);
             }
             dom_tree_size += malloc_size_of_including_self(&mut ops, document.window());
--- a/servo/components/selectors/context.rs
+++ b/servo/components/selectors/context.rs
@@ -1,15 +1,16 @@
 /* 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/. */
 
 use attr::CaseSensitivity;
 use bloom::BloomFilter;
 use nth_index_cache::NthIndexCache;
+use parser::SelectorImpl;
 use tree::OpaqueElement;
 
 /// What kind of selector matching mode we should use.
 ///
 /// There are two modes of selector matching. The difference is only noticeable
 /// in presence of pseudo-elements.
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub enum MatchingMode {
@@ -69,17 +70,20 @@ impl QuirksMode {
             QuirksMode::Quirks => CaseSensitivity::AsciiCaseInsensitive,
         }
     }
 }
 
 /// Data associated with the matching process for a element.  This context is
 /// used across many selectors for an element, so it's not appropriate for
 /// transient data that applies to only a single selector.
-pub struct MatchingContext<'a> {
+pub struct MatchingContext<'a, Impl>
+where
+    Impl: SelectorImpl,
+{
     /// Input with the matching mode we should use when matching selectors.
     pub matching_mode: MatchingMode,
     /// Input with the bloom filter used to fast-reject selectors.
     pub bloom_filter: Option<&'a BloomFilter>,
     /// An optional cache to speed up nth-index-like selectors.
     pub nth_index_cache: Option<&'a mut NthIndexCache>,
     /// Input that controls how matching for links is handled.
     pub visited_handling: VisitedHandlingMode,
@@ -102,19 +106,23 @@ pub struct MatchingContext<'a> {
     /// See https://drafts.csswg.org/selectors-4/#scope-pseudo
     pub scope_element: Option<OpaqueElement>,
 
     /// The current nesting level of selectors that we're matching.
     pub nesting_level: usize,
 
     quirks_mode: QuirksMode,
     classes_and_ids_case_sensitivity: CaseSensitivity,
+    _impl: ::std::marker::PhantomData<Impl>,
 }
 
-impl<'a> MatchingContext<'a> {
+impl<'a, Impl> MatchingContext<'a, Impl>
+where
+    Impl: SelectorImpl,
+{
     /// Constructs a new `MatchingContext`.
     pub fn new(
         matching_mode: MatchingMode,
         bloom_filter: Option<&'a BloomFilter>,
         nth_index_cache: Option<&'a mut NthIndexCache>,
         quirks_mode: QuirksMode,
     ) -> Self {
         Self::new_for_visited(
@@ -139,16 +147,17 @@ impl<'a> MatchingContext<'a> {
             bloom_filter,
             visited_handling,
             nth_index_cache,
             quirks_mode,
             relevant_link_found: false,
             classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(),
             scope_element: None,
             nesting_level: 0,
+            _impl: ::std::marker::PhantomData,
         }
     }
 
     /// The quirks mode of the document.
     #[inline]
     pub fn quirks_mode(&self) -> QuirksMode {
         self.quirks_mode
     }
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -50,25 +50,25 @@ impl ElementSelectorFlags {
 
     /// Returns the subset of flags that apply to the parent.
     pub fn for_parent(self) -> ElementSelectorFlags {
         self & (HAS_SLOW_SELECTOR | HAS_SLOW_SELECTOR_LATER_SIBLINGS | HAS_EDGE_CHILD_SELECTOR)
     }
 }
 
 /// Holds per-compound-selector data.
-struct LocalMatchingContext<'a, 'b: 'a> {
-    shared: &'a mut MatchingContext<'b>,
+struct LocalMatchingContext<'a, 'b: 'a, Impl: SelectorImpl> {
+    shared: &'a mut MatchingContext<'b, Impl>,
     matches_hover_and_active_quirk: bool,
 }
 
 pub fn matches_selector_list<E>(
     selector_list: &SelectorList<E::Impl>,
     element: &E,
-    context: &mut MatchingContext,
+    context: &mut MatchingContext<E::Impl>,
 ) -> bool
 where
     E: Element
 {
     selector_list.0.iter().any(|selector| {
         matches_selector(selector,
                          0,
                          None,
@@ -136,19 +136,23 @@ impl Default for RelevantLinkStatus {
         RelevantLinkStatus::NotLooking
     }
 }
 
 impl RelevantLinkStatus {
     /// If we found the relevant link for this element, record that in the
     /// overall matching context for the element as a whole and stop looking for
     /// addtional links.
-    fn examine_potential_link<E>(&self, element: &E, context: &mut MatchingContext)
-                                 -> RelevantLinkStatus
-        where E: Element,
+    fn examine_potential_link<E>(
+        &self,
+        element: &E,
+        context: &mut MatchingContext<E::Impl>,
+    ) -> RelevantLinkStatus
+    where
+        E: Element,
     {
         // If a relevant link was previously found, we no longer want to look
         // for links.  Only the nearest ancestor link is considered relevant.
         if *self != RelevantLinkStatus::Looking {
             return RelevantLinkStatus::NotLooking
         }
 
         if !element.is_link() {
@@ -163,18 +167,23 @@ impl RelevantLinkStatus {
         // specific selector's matching process.
         RelevantLinkStatus::Found
     }
 
     /// Returns whether an element is considered visited for the purposes of
     /// matching.  This is true only if the element is a link, an relevant link
     /// exists for the element, and the visited handling mode is set to accept
     /// relevant links as visited.
-    pub fn is_visited<E>(&self, element: &E, context: &MatchingContext) -> bool
-        where E: Element,
+    pub fn is_visited<E>(
+        &self,
+        element: &E,
+        context: &MatchingContext<E::Impl>,
+    ) -> bool
+    where
+        E: Element,
     {
         if !element.is_link() {
             return false
         }
 
         if context.visited_handling == VisitedHandlingMode::AllLinksVisitedAndUnvisited {
             return true;
         }
@@ -188,18 +197,23 @@ impl RelevantLinkStatus {
     }
 
     /// Returns whether an element is considered unvisited for the purposes of
     /// matching.  Assuming the element is a link, this is always true for
     /// non-relevant links, since only relevant links can potentially be treated
     /// as visited.  If this is a relevant link, then is it unvisited if the
     /// visited handling mode is set to treat all links as unvisted (including
     /// relevant links).
-    pub fn is_unvisited<E>(&self, element: &E, context: &MatchingContext) -> bool
-        where E: Element,
+    pub fn is_unvisited<E>(
+        &self,
+        element: &E,
+        context: &MatchingContext<E::Impl>
+    ) -> bool
+    where
+        E: Element,
     {
         if !element.is_link() {
             return false
         }
 
         if context.visited_handling == VisitedHandlingMode::AllLinksVisitedAndUnvisited {
             return true;
         }
@@ -272,17 +286,17 @@ enum SelectorMatchingResult {
 /// unncessary cache miss for cases when we can fast-reject with AncestorHashes
 /// (which the caller can store inline with the selector pointer).
 #[inline(always)]
 pub fn matches_selector<E, F>(
     selector: &Selector<E::Impl>,
     offset: usize,
     hashes: Option<&AncestorHashes>,
     element: &E,
-    context: &mut MatchingContext,
+    context: &mut MatchingContext<E::Impl>,
     flags_setter: &mut F,
 ) -> bool
 where
     E: Element,
     F: FnMut(&E, ElementSelectorFlags),
 {
     // Use the bloom filter to fast-reject.
     if let Some(hashes) = hashes {
@@ -313,17 +327,17 @@ pub enum CompoundSelectorMatchingResult 
 ///
 /// Requires that `from_offset` points to a `Combinator`.
 ///
 /// NOTE(emilio): This doesn't allow to match in the leftmost sequence of the
 /// complex selector, but it happens to be the case we don't need it.
 pub fn matches_compound_selector<E>(
     selector: &Selector<E::Impl>,
     mut from_offset: usize,
-    context: &mut MatchingContext,
+    context: &mut MatchingContext<E::Impl>,
     element: &E,
 ) -> CompoundSelectorMatchingResult
 where
     E: Element
 {
     debug_assert_ne!(from_offset, 0);
     if cfg!(debug_assertions) {
         selector.combinator_at_parse_order(from_offset - 1); // This asserts.
@@ -356,17 +370,17 @@ where
 
     CompoundSelectorMatchingResult::FullyMatched
 }
 
 /// Matches a complex selector.
 pub fn matches_complex_selector<E, F>(
     mut iter: SelectorIter<E::Impl>,
     element: &E,
-    context: &mut MatchingContext,
+    context: &mut MatchingContext<E::Impl>,
     flags_setter: &mut F,
 ) -> bool
 where
     E: Element,
     F: FnMut(&E, ElementSelectorFlags),
 {
     // If this is the special pseudo-element mode, consume the ::pseudo-element
     // before proceeding, since the caller has already handled that part.
@@ -405,17 +419,17 @@ where
         SelectorMatchingResult::Matched => true,
         _ => false
     }
 }
 
 #[inline]
 fn matches_hover_and_active_quirk<Impl: SelectorImpl>(
     selector_iter: &SelectorIter<Impl>,
-    context: &MatchingContext,
+    context: &MatchingContext<Impl>,
     rightmost: Rightmost,
 ) -> bool {
     if context.quirks_mode() != QuirksMode::Quirks {
         return false;
     }
 
     if context.nesting_level != 0 {
         return false;
@@ -460,17 +474,17 @@ fn matches_hover_and_active_quirk<Impl: 
 enum Rightmost {
     Yes,
     No,
 }
 
 fn matches_complex_selector_internal<E, F>(
     mut selector_iter: SelectorIter<E::Impl>,
     element: &E,
-    context: &mut MatchingContext,
+    context: &mut MatchingContext<E::Impl>,
     relevant_link: &mut RelevantLinkStatus,
     flags_setter: &mut F,
     rightmost: Rightmost,
 ) -> SelectorMatchingResult
 where
     E: Element,
     F: FnMut(&E, ElementSelectorFlags),
 {
@@ -584,17 +598,17 @@ where
     }
 }
 
 /// Determines whether the given element matches the given single selector.
 #[inline]
 fn matches_simple_selector<E, F>(
     selector: &Component<E::Impl>,
     element: &E,
-    context: &mut LocalMatchingContext,
+    context: &mut LocalMatchingContext<E::Impl>,
     relevant_link: &RelevantLinkStatus,
     flags_setter: &mut F,
 ) -> bool
 where
     E: Element,
     F: FnMut(&E, ElementSelectorFlags),
 {
     match *selector {
@@ -756,17 +770,17 @@ fn select_name<'a, T>(is_html: bool, loc
     } else {
         local_name
     }
 }
 
 #[inline]
 fn matches_generic_nth_child<E, F>(
     element: &E,
-    context: &mut LocalMatchingContext,
+    context: &mut LocalMatchingContext<E::Impl>,
     a: i32,
     b: i32,
     is_of_type: bool,
     is_from_end: bool,
     flags_setter: &mut F,
 ) -> bool
 where
     E: Element,
--- a/servo/components/selectors/tree.rs
+++ b/servo/components/selectors/tree.rs
@@ -62,27 +62,28 @@ pub trait Element: Sized + Clone + Debug
                     ns: &NamespaceConstraint<&<Self::Impl as SelectorImpl>::NamespaceUrl>,
                     local_name: &<Self::Impl as SelectorImpl>::LocalName,
                     operation: &AttrSelectorOperation<&<Self::Impl as SelectorImpl>::AttrValue>)
                     -> bool;
 
     fn match_non_ts_pseudo_class<F>(
         &self,
         pc: &<Self::Impl as SelectorImpl>::NonTSPseudoClass,
-        context: &mut MatchingContext,
+        context: &mut MatchingContext<Self::Impl>,
         relevant_link: &RelevantLinkStatus,
         flags_setter: &mut F,
     ) -> bool
     where
         F: FnMut(&Self, ElementSelectorFlags);
 
-    fn match_pseudo_element(&self,
-                            pe: &<Self::Impl as SelectorImpl>::PseudoElement,
-                            context: &mut MatchingContext)
-                            -> bool;
+    fn match_pseudo_element(
+        &self,
+        pe: &<Self::Impl as SelectorImpl>::PseudoElement,
+        context: &mut MatchingContext<Self::Impl>,
+    ) -> bool;
 
     /// Whether this element is a `link`.
     fn is_link(&self) -> bool;
 
     fn has_id(&self,
               id: &<Self::Impl as SelectorImpl>::Identifier,
               case_sensitivity: CaseSensitivity)
               -> bool;
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -1829,17 +1829,17 @@ impl<'le> ::selectors::Element for Gecko
         unsafe {
             WeakNamespace::new(Gecko_Namespace(self.0))
         }
     }
 
     fn match_non_ts_pseudo_class<F>(
         &self,
         pseudo_class: &NonTSPseudoClass,
-        context: &mut MatchingContext,
+        context: &mut MatchingContext<Self::Impl>,
         relevant_link: &RelevantLinkStatus,
         flags_setter: &mut F,
     ) -> bool
     where
         F: FnMut(&Self, ElementSelectorFlags),
     {
         use selectors::matching::*;
         match *pseudo_class {
@@ -1970,17 +1970,17 @@ impl<'le> ::selectors::Element for Gecko
                 }
             }
         }