Merge mozilla-central to inbound. a=merge CLOSED TREE
authorOana Pop Rus <opoprus@mozilla.com>
Fri, 15 Mar 2019 11:46:37 +0200
changeset 522066 9daec44d681e328c4827b23f16ad587c325fe20f
parent 522065 6ea65bcee7cfbbce9dd6824f590f73313a9850b5 (current diff)
parent 522016 4d62ab0e31fd6918ca95763914a8bdf41afe757a (diff)
child 522068 9e076e178370e309828ca0b2a598058df83848af
push id10871
push usercbrindusan@mozilla.com
push dateMon, 18 Mar 2019 15:49:32 +0000
treeherdermozilla-beta@018abdd16060 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
dom/push/test/xpcshell/test_startup_error.js
--- a/browser/actors/ClickHandlerChild.jsm
+++ b/browser/actors/ClickHandlerChild.jsm
@@ -51,39 +51,48 @@ class ClickHandlerChild extends ActorChi
     }
 
     // Bug 965637, query the CSP from the doc instead of the Principal
     let csp = ownerDoc.nodePrincipal.csp;
     if (csp) {
       csp = E10SUtils.serializeCSP(csp);
     }
 
+    let ReferrerInfo = Components.Constructor("@mozilla.org/referrer-info;1",
+                                              "nsIReferrerInfo",
+                                              "init");
+    let referrerInfo = new ReferrerInfo(
+      referrerPolicy,
+      !BrowserUtils.linkHasNoReferrer(node),
+      ownerDoc.documentURIObject);
+    referrerInfo = E10SUtils.serializeReferrerInfo(referrerInfo);
     let frameOuterWindowID = WebNavigationFrames.getFrameId(ownerDoc.defaultView);
 
     let json = { button: event.button, shiftKey: event.shiftKey,
                  ctrlKey: event.ctrlKey, metaKey: event.metaKey,
                  altKey: event.altKey, href: null, title: null,
-                 frameOuterWindowID, referrerPolicy,
+                 frameOuterWindowID,
                  triggeringPrincipal: principal,
                  csp,
+                 referrerInfo,
                  originAttributes: principal ? principal.originAttributes : {},
                  isContentWindowPrivate: PrivateBrowsingUtils.isContentWindowPrivate(ownerDoc.defaultView)};
 
     if (href) {
       try {
         BrowserUtils.urlSecurityCheck(href, principal);
       } catch (e) {
         return;
       }
 
       json.href = href;
       if (node) {
         json.title = node.getAttribute("title");
       }
-      json.noReferrer = BrowserUtils.linkHasNoReferrer(node);
+
 
       // Check if the link needs to be opened with mixed content allowed.
       // Only when the owner doc has |mixedContentChannel| and the same origin
       // should we allow mixed content.
       json.allowMixedContent = false;
       let docshell = ownerDoc.defaultView.docShell;
       if (this.mm.docShell.mixedContentChannel) {
         const sm = Services.scriptSecurityManager;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -260,16 +260,21 @@ var gURLBarHandler = {
     }
   },
 };
 
 XPCOMUtils.defineLazyPreferenceGetter(gURLBarHandler, "quantumbar",
                                       "browser.urlbar.quantumbar", false,
                                       gURLBarHandler.handlePrefChange.bind(gURLBarHandler));
 
+XPCOMUtils.defineLazyGetter(this, "ReferrerInfo", () =>
+  Components.Constructor("@mozilla.org/referrer-info;1",
+                         "nsIReferrerInfo",
+                         "init"));
+
 // High priority notification bars shown at the top of the window.
 XPCOMUtils.defineLazyGetter(this, "gHighPriorityNotificationBox", () => {
   return new MozElements.NotificationBox(element => {
     element.classList.add("global-notificationbox");
     element.setAttribute("notificationside", "top");
     document.getElementById("appcontent").prepend(element);
   });
 });
@@ -1735,69 +1740,58 @@ var gBrowserInit = {
           gURLBar.removeAttribute("focused");
       });
     }
   },
 
   _handleURIToLoad() {
     this._callWithURIToLoad(uriToLoad => {
       if (!uriToLoad) {
-        // We don't check whether window.arguments[6] (userContextId) is set
+        // We don't check whether window.arguments[5] (userContextId) is set
         // because tabbrowser.js takes care of that for the initial tab.
         return;
       }
 
       // We don't check if uriToLoad is a XULElement because this case has
       // already been handled before first paint, and the argument cleared.
       if (Array.isArray(uriToLoad)) {
         // This function throws for certain malformed URIs, so use exception handling
         // so that we don't disrupt startup
         try {
           gBrowser.loadTabs(uriToLoad, {
             inBackground: false,
             replace: true,
             // See below for the semantics of window.arguments. Only the minimum is supported.
-            userContextId: window.arguments[6],
-            triggeringPrincipal: window.arguments[8] || Services.scriptSecurityManager.getSystemPrincipal(),
-            allowInheritPrincipal: window.arguments[9],
-            csp: window.arguments[10],
+            userContextId: window.arguments[5],
+            triggeringPrincipal: window.arguments[7] || Services.scriptSecurityManager.getSystemPrincipal(),
+            allowInheritPrincipal: window.arguments[8],
+            csp: window.arguments[9],
             fromExternal: true,
           });
         } catch (e) {}
       } else if (window.arguments.length >= 3) {
         // window.arguments[1]: unused (bug 871161)
-        //                 [2]: referrer (nsIURI | string)
+        //                 [2]: referrerInfo (nsIReferrerInfo)
         //                 [3]: postData (nsIInputStream)
         //                 [4]: allowThirdPartyFixup (bool)
-        //                 [5]: referrerPolicy (int)
-        //                 [6]: userContextId (int)
-        //                 [7]: originPrincipal (nsIPrincipal)
-        //                 [8]: triggeringPrincipal (nsIPrincipal)
-        //                 [9]: allowInheritPrincipal (bool)
-        //                [10]: csp (nsIContentSecurityPolicy)
-        let referrerURI = window.arguments[2];
-        if (typeof(referrerURI) == "string") {
-          try {
-            referrerURI = makeURI(referrerURI);
-          } catch (e) {
-            referrerURI = null;
-          }
-        }
-        let referrerPolicy = (window.arguments[5] != undefined ?
-            window.arguments[5] : Ci.nsIHttpChannel.REFERRER_POLICY_UNSET);
-        let userContextId = (window.arguments[6] != undefined ?
-            window.arguments[6] : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID);
-        loadURI(uriToLoad, referrerURI, window.arguments[3] || null,
-                window.arguments[4] || false, referrerPolicy, userContextId,
+        //                 [5]: userContextId (int)
+        //                 [6]: originPrincipal (nsIPrincipal)
+        //                 [7]: triggeringPrincipal (nsIPrincipal)
+        //                 [8]: allowInheritPrincipal (bool)
+        //                 [9]: csp (nsIContentSecurityPolicy)
+        let userContextId = (window.arguments[5] != undefined ?
+            window.arguments[5] : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID);
+        loadURI(uriToLoad, window.arguments[2] || null, window.arguments[3] || null,
+                window.arguments[4] || false, userContextId,
                 // pass the origin principal (if any) and force its use to create
                 // an initial about:blank viewer if present:
-                window.arguments[7], !!window.arguments[7], window.arguments[8],
+                window.arguments[6], !!window.arguments[6], window.arguments[7],
                 // TODO fix allowInheritPrincipal to default to false.
                 // Default to true unless explicitly set to false because of bug 1475201.
-                window.arguments[9] !== false, window.arguments[10]);
+                window.arguments[8] !== false, window.arguments[9]);
         window.focus();
       } else {
         // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
         // Such callers expect that window.arguments[0] is handled as a single URI.
         loadOneOrMoreURIs(uriToLoad, Services.scriptSecurityManager.getSystemPrincipal());
       }
     });
   },
@@ -2504,27 +2498,26 @@ function BrowserCloseTabOrWindow(event) 
   gBrowser.removeCurrentTab({animate: true});
 }
 
 function BrowserTryToCloseWindow() {
   if (WindowIsClosing())
     window.close(); // WindowIsClosing does all the necessary checks
 }
 
-function loadURI(uri, referrer, postData, allowThirdPartyFixup, referrerPolicy,
+function loadURI(uri, referrerInfo, postData, allowThirdPartyFixup,
                  userContextId, originPrincipal, forceAboutBlankViewerInCurrent,
                  triggeringPrincipal, allowInheritPrincipal = false, csp = null) {
   if (!triggeringPrincipal) {
     throw new Error("Must load with a triggering Principal");
   }
 
   try {
     openLinkIn(uri, "current",
-               { referrerURI: referrer,
-                 referrerPolicy,
+               { referrerInfo,
                  postData,
                  allowThirdPartyFixup,
                  userContextId,
                  originPrincipal,
                  triggeringPrincipal,
                  csp,
                  forceAboutBlankViewerInCurrent,
                  allowInheritPrincipal,
@@ -5538,17 +5531,17 @@ var TabsProgressListener = {
   },
 };
 
 function nsBrowserAccess() { }
 
 nsBrowserAccess.prototype = {
   QueryInterface: ChromeUtils.generateQI([Ci.nsIBrowserDOMWindow]),
 
-  _openURIInNewTab(aURI, aReferrer, aReferrerPolicy, aIsPrivate,
+  _openURIInNewTab(aURI, aReferrerInfo, aIsPrivate,
                    aIsExternal, aForceNotRemote = false,
                    aUserContextId = Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID,
                    aOpenerWindow = null, aOpenerBrowser = null,
                    aTriggeringPrincipal = null, aNextTabParentId = 0, aName = "", aCsp = null) {
     let win, needToFocusWin;
 
     // try the current window.  if we're in a popup, fall back on the most recent browser window
     if (window.toolbar.visible) {
@@ -5568,18 +5561,17 @@ nsBrowserAccess.prototype = {
       win.focus();
       return win.gBrowser.selectedBrowser;
     }
 
     let loadInBackground = Services.prefs.getBoolPref("browser.tabs.loadDivertedInBackground");
 
     let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", {
                                       triggeringPrincipal: aTriggeringPrincipal,
-                                      referrerURI: aReferrer,
-                                      referrerPolicy: aReferrerPolicy,
+                                      referrerInfo: aReferrerInfo,
                                       userContextId: aUserContextId,
                                       fromExternal: aIsExternal,
                                       inBackground: loadInBackground,
                                       forceNotRemote: aForceNotRemote,
                                       opener: aOpenerWindow,
                                       openerBrowser: aOpenerBrowser,
                                       nextTabParentId: aNextTabParentId,
                                       name: aName,
@@ -5633,20 +5625,20 @@ nsBrowserAccess.prototype = {
     if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
       if (isExternal &&
           Services.prefs.prefHasUserValue("browser.link.open_newwindow.override.external"))
         aWhere = Services.prefs.getIntPref("browser.link.open_newwindow.override.external");
       else
         aWhere = Services.prefs.getIntPref("browser.link.open_newwindow");
     }
 
-    let referrer = aOpener ? makeURI(aOpener.location.href) : null;
-    let referrerPolicy = Ci.nsIHttpChannel.REFERRER_POLICY_UNSET;
+    let referrerInfo = new ReferrerInfo(Ci.nsIHttpChannel.REFERRER_POLICY_UNSET, true,
+      aOpener ? makeURI(aOpener.location.href) : null);
     if (aOpener && aOpener.document) {
-      referrerPolicy = aOpener.document.referrerPolicy;
+      referrerInfo.referrerPolicy = aOpener.document.referrerPolicy;
     }
     // Bug 965637, query the CSP from the doc instead of the Principal
     let csp = aTriggeringPrincipal.csp;
     let isPrivate = aOpener
                   ? PrivateBrowsingUtils.isContentWindowPrivate(aOpener)
                   : PrivateBrowsingUtils.isWindowPrivate(window);
 
     switch (aWhere) {
@@ -5658,34 +5650,34 @@ nsBrowserAccess.prototype = {
         if (isPrivate) {
           features += ",private";
         }
         // Pass all params to openDialog to ensure that "url" isn't passed through
         // loadOneOrMoreURIs, which splits based on "|"
         try {
           newWindow = openDialog(AppConstants.BROWSER_CHROME_URL, "_blank", features,
                       // window.arguments
-                      url, null, null, null, null, null, null, null, aTriggeringPrincipal);
+                      url, null, null, null, null, null, null, aTriggeringPrincipal);
         } catch (ex) {
           Cu.reportError(ex);
         }
         break;
       case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB :
         // If we have an opener, that means that the caller is expecting access
         // to the nsIDOMWindow of the opened tab right away. For e10s windows,
         // this means forcing the newly opened browser to be non-remote so that
         // we can hand back the nsIDOMWindow. The XULBrowserWindow.shouldLoadURI
         // will do the job of shuttling off the newly opened browser to run in
         // the right process once it starts loading a URI.
         let forceNotRemote = !!aOpener;
         let userContextId = aOpener && aOpener.document
                               ? aOpener.document.nodePrincipal.originAttributes.userContextId
                               : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
         let openerWindow = (aFlags & Ci.nsIBrowserDOMWindow.OPEN_NO_OPENER) ? null : aOpener;
-        let browser = this._openURIInNewTab(aURI, referrer, referrerPolicy,
+        let browser = this._openURIInNewTab(aURI, referrerInfo,
                                             isPrivate, isExternal,
                                             forceNotRemote, userContextId,
                                             openerWindow, null, aTriggeringPrincipal,
                                             0, "", csp);
         if (browser)
           newWindow = browser.contentWindow;
         break;
       default : // OPEN_CURRENTWINDOW or an illegal value
@@ -5693,18 +5685,17 @@ nsBrowserAccess.prototype = {
         if (aURI) {
           let loadflags = isExternal ?
                             Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
                             Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
           gBrowser.loadURI(aURI.spec, {
             triggeringPrincipal: aTriggeringPrincipal,
             csp,
             flags: loadflags,
-            referrerURI: referrer,
-            referrerPolicy,
+            referrerInfo,
           });
         }
         if (!Services.prefs.getBoolPref("browser.tabs.loadDivertedInBackground"))
           window.focus();
     }
     return newWindow;
   },
 
@@ -5732,19 +5723,18 @@ nsBrowserAccess.prototype = {
 
     var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
 
     var userContextId = aParams.openerOriginAttributes &&
                         ("userContextId" in aParams.openerOriginAttributes)
                           ? aParams.openerOriginAttributes.userContextId
                           : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
 
-    let referrer = aParams.referrer ? makeURI(aParams.referrer) : null;
-    return this._openURIInNewTab(aURI, referrer,
-                                 aParams.referrerPolicy,
+    return this._openURIInNewTab(aURI,
+                                 aParams.referrerInfo,
                                  aParams.isPrivate,
                                  isExternal, false,
                                  userContextId, null, aParams.openerBrowser,
                                  aParams.triggeringPrincipal,
                                  aNextTabParentId, aName, aParams.csp);
   },
 
   isTabContentWindow(aWindow) {
@@ -6290,27 +6280,29 @@ function handleLinkClick(event, href, li
     let referrerAttrValue = Services.netUtils.parseAttributePolicyString(linkNode.
                             getAttribute("referrerpolicy"));
     if (referrerAttrValue != Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) {
       referrerPolicy = referrerAttrValue;
     }
   }
 
   let frameOuterWindowID = WebNavigationFrames.getFrameId(doc.defaultView);
+  let referrerInfo = new ReferrerInfo(
+    referrerPolicy,
+    !BrowserUtils.linkHasNoReferrer(linkNode),
+    referrerURI);
 
   // Bug 965637, query the CSP from the doc instead of the Principal
   let csp = doc.nodePrincipal.csp;
 
   urlSecurityCheck(href, doc.nodePrincipal);
   let params = {
     charset: doc.characterSet,
     allowMixedContent: persistAllowMixedContentInChildTab,
-    referrerURI,
-    referrerPolicy,
-    noReferrer: BrowserUtils.linkHasNoReferrer(linkNode),
+    referrerInfo,
     originPrincipal: doc.nodePrincipal,
     triggeringPrincipal: doc.nodePrincipal,
     csp,
     frameOuterWindowID,
   };
 
   // The new tab/window must use the same userContextId
   if (doc.nodePrincipal.originAttributes.userContextId) {
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -40,29 +40,40 @@ function openContextMenu(aMessage) {
 
   if (spellInfo) {
     spellInfo.target = aMessage.target.messageManager;
   }
 
   let documentURIObject = makeURI(data.docLocation,
                                   data.charSet,
                                   makeURI(data.baseURI));
+  let ReferrerInfo = Components.Constructor("@mozilla.org/referrer-info;1",
+                                        "nsIReferrerInfo",
+                                        "init");
+  let referrerInfo = new ReferrerInfo(
+    data.referrerPolicy,
+    !data.context.linkHasNoReferrer,
+    documentURIObject);
+  let frameReferrerInfo = new ReferrerInfo(
+    data.referrerPolicy,
+    !data.context.linkHasNoReferrer,
+    data.referrer ? makeURI(data.referrer) : null);
   gContextMenuContentData = { context: data.context,
                               isRemote: data.isRemote,
                               popupNodeSelectors: data.popupNodeSelectors,
                               browser,
                               editFlags: data.editFlags,
                               spellInfo,
                               principal: data.principal,
                               customMenuItems: data.customMenuItems,
                               documentURIObject,
                               docLocation: data.docLocation,
                               charSet: data.charSet,
-                              referrer: data.referrer,
-                              referrerPolicy: data.referrerPolicy,
+                              referrerInfo,
+                              frameReferrerInfo,
                               contentType: data.contentType,
                               contentDisposition: data.contentDisposition,
                               frameOuterWindowID: data.frameOuterWindowID,
                               selectionInfo: data.selectionInfo,
                               disableSetDesktopBackground: data.disableSetDesktopBg,
                               loginFillInfo: data.loginFillInfo,
                               parentAllowsMixedContent: data.parentAllowsMixedContent,
                               userContextId: data.userContextId,
@@ -189,17 +200,16 @@ nsContextMenu.prototype = {
     this.inFrame             = context.inFrame;
     this.inSrcdocFrame       = context.inSrcdocFrame;
     this.inSyntheticDoc      = context.inSyntheticDoc;
     this.inTabBrowser        = context.inTabBrowser;
     this.inWebExtBrowser     = context.inWebExtBrowser;
 
     this.link                = context.link;
     this.linkDownload        = context.linkDownload;
-    this.linkHasNoReferrer   = context.linkHasNoReferrer;
     this.linkProtocol        = context.linkProtocol;
     this.linkTextStr         = context.linkTextStr;
     this.linkURL             = context.linkURL;
     this.linkURI             = this.getLinkURI(); // can't send; regenerate
 
     this.onAudio             = context.onAudio;
     this.onCanvas            = context.onCanvas;
     this.onCompletedImage    = context.onCompletedImage;
@@ -776,37 +786,37 @@ nsContextMenu.prototype = {
     return DevToolsShim.inspectA11Y(gBrowser.selectedTab, this.targetSelectors);
   },
 
   _openLinkInParameters(extra) {
     let params = { charset: gContextMenuContentData.charSet,
                    originPrincipal: this.principal,
                    triggeringPrincipal: this.principal,
                    csp: this.csp,
-                   referrerURI: gContextMenuContentData.documentURIObject,
-                   referrerPolicy: gContextMenuContentData.referrerPolicy,
-                   frameOuterWindowID: gContextMenuContentData.frameOuterWindowID,
-                   noReferrer: this.linkHasNoReferrer || this.onPlainTextLink };
+                   frameOuterWindowID: gContextMenuContentData.frameOuterWindowID};
     for (let p in extra) {
       params[p] = extra[p];
     }
 
     if (!this.isRemote) {
       // Propagate the frameOuterWindowID value saved when
       // the context menu has been opened.
       params.frameOuterWindowID = this.frameOuterWindowID;
     }
 
+    let referrerInfo = gContextMenuContentData.referrerInfo;
     // If we want to change userContextId, we must be sure that we don't
     // propagate the referrer.
-    if ("userContextId" in params &&
-        params.userContextId != gContextMenuContentData.userContextId) {
-      params.noReferrer = true;
+    if (("userContextId" in params &&
+        params.userContextId != gContextMenuContentData.userContextId) ||
+      this.onPlainTextLink) {
+      referrerInfo.sendReferrer = false;
     }
 
+    params.referrerInfo = referrerInfo;
     return params;
   },
 
   // Open linked-to URL in a new window.
   openLink() {
     openLinkIn(this.linkURL, "window", this._openLinkInParameters());
   },
 
@@ -845,47 +855,44 @@ nsContextMenu.prototype = {
 
   // open URL in current tab
   openLinkInCurrent() {
     openLinkIn(this.linkURL, "current", this._openLinkInParameters());
   },
 
   // Open frame in a new tab.
   openFrameInTab() {
-    let referrer = gContextMenuContentData.referrer;
     openLinkIn(gContextMenuContentData.docLocation, "tab",
                { charset: gContextMenuContentData.charSet,
                  triggeringPrincipal: this.browser.contentPrincipal,
-                 referrerURI: referrer ? makeURI(referrer) : null });
+                 referrerInfo: gContextMenuContentData.frameReferrerInfo });
   },
 
   // Reload clicked-in frame.
   reloadFrame(aEvent) {
     let forceReload = aEvent.shiftKey;
     this.browser.messageManager.sendAsyncMessage("ContextMenu:ReloadFrame",
                                                  null, { target: this.target, forceReload });
   },
 
   // Open clicked-in frame in its own window.
   openFrame() {
-    let referrer = gContextMenuContentData.referrer;
     openLinkIn(gContextMenuContentData.docLocation, "window",
                { charset: gContextMenuContentData.charSet,
                  triggeringPrincipal: this.browser.contentPrincipal,
-                 referrerURI: referrer ? makeURI(referrer) : null });
+                 referrerInfo: gContextMenuContentData.frameReferrerInfo });
   },
 
   // Open clicked-in frame in the same window.
   showOnlyThisFrame() {
     urlSecurityCheck(gContextMenuContentData.docLocation,
                      this.browser.contentPrincipal,
                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
-    let referrer = gContextMenuContentData.referrer;
     openWebLinkIn(gContextMenuContentData.docLocation, "current", {
-      referrerURI: referrer ? makeURI(referrer) : null,
+      referrerInfo: gContextMenuContentData.frameReferrerInfo,
       triggeringPrincipal: this.browser.contentPrincipal,
     });
   },
 
   // View Partial Source
   viewPartialSource() {
     let {browser} = this;
     let openSelectionFn = function() {
@@ -928,17 +935,17 @@ nsContextMenu.prototype = {
     BrowserPageInfo(gContextMenuContentData.docLocation, "mediaTab",
                     this.imageInfo, null, this.browser);
   },
 
   viewImageDesc(e) {
     urlSecurityCheck(this.imageDescURL,
                      this.principal,
                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
-    openUILink(this.imageDescURL, e, { referrerURI: gContextMenuContentData.documentURIObject,
+    openUILink(this.imageDescURL, e, { referrerInfo: gContextMenuContentData.referrerInfo,
                                        triggeringPrincipal: this.principal,
     });
   },
 
   viewFrameInfo() {
     BrowserPageInfo(gContextMenuContentData.docLocation, null, null,
                     this.frameOuterWindowID, this.browser);
   },
@@ -962,28 +969,28 @@ nsContextMenu.prototype = {
         resolve(message.data.blobURL);
       };
       mm.addMessageListener("ContextMenu:Canvas:ToBlobURL:Result", onMessage);
     });
   },
 
   // Change current window to the URL of the image, video, or audio.
   viewMedia(e) {
-    let referrerURI = gContextMenuContentData.documentURIObject;
+    let referrerInfo = gContextMenuContentData.referrerInfo;
     let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
     if (this.onCanvas) {
       this._canvasToBlobURL(this.target).then(function(blobURL) {
-        openUILink(blobURL, e, { referrerURI,
+        openUILink(blobURL, e, { referrerInfo,
                                  triggeringPrincipal: systemPrincipal});
       }, Cu.reportError);
     } else {
       urlSecurityCheck(this.mediaURL,
                        this.principal,
                        Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
-      openUILink(this.mediaURL, e, { referrerURI,
+      openUILink(this.mediaURL, e, { referrerInfo,
                                      forceAllowDataURI: true,
                                      triggeringPrincipal: this.principal,
       });
     }
   },
 
   saveVideoFrameAsImage() {
     let mm = this.browser.messageManager;
@@ -1030,17 +1037,17 @@ nsContextMenu.prototype = {
   },
 
   // Change current window to the URL of the background image.
   viewBGImage(e) {
     urlSecurityCheck(this.bgImageURL,
                      this.principal,
                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
 
-    openUILink(this.bgImageURL, e, { referrerURI: gContextMenuContentData.documentURIObject,
+    openUILink(this.bgImageURL, e, { referrerInfo: gContextMenuContentData.referrerInfo,
                                      triggeringPrincipal: this.principal,
     });
   },
 
   setDesktopBackground() {
     let mm = this.browser.messageManager;
 
     mm.sendAsyncMessage("ContextMenu:SetAsDesktopBackground", null,
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -277,17 +277,17 @@ window._gBrowser = {
 
   get selectedBrowser() {
     return this._selectedBrowser;
   },
 
   _setupInitialBrowserAndTab() {
     // See browser.js for the meaning of window.arguments.
     // Bug 1485961 covers making this more sane.
-    let userContextId = window.arguments && window.arguments[6];
+    let userContextId = window.arguments && window.arguments[5];
 
     let tabArgument = gBrowserInit.getTabToAdopt();
 
     // We only need sameProcessAsFrameLoader in the case where we're passed a tab
     let sameProcessAsFrameLoader;
     // If we have a tab argument with browser, we use its remoteType. Otherwise,
     // if e10s is disabled or there's a parent process opener (e.g. parent
     // process about: page) for the content tab, we use a parent
@@ -1381,56 +1381,53 @@ window._gBrowser = {
 
     if (aTab.selected) {
       this.updateTitlebar();
     }
 
     return true;
   },
 
-  loadOneTab(aURI, aReferrerURI, aCharset, aPostData, aLoadInBackground, aAllowThirdPartyFixup) {
+  loadOneTab(aURI, aReferrerInfoOrParams, aCharset, aPostData, aLoadInBackground, aAllowThirdPartyFixup) {
     var aTriggeringPrincipal;
-    var aReferrerPolicy;
+    var aReferrerInfo;
     var aFromExternal;
     var aRelatedToCurrent;
     var aAllowInheritPrincipal;
     var aAllowMixedContent;
     var aSkipAnimation;
     var aForceNotRemote;
     var aPreferredRemoteType;
-    var aNoReferrer;
     var aUserContextId;
     var aSameProcessAsFrameLoader;
     var aOriginPrincipal;
     var aOpener;
     var aOpenerBrowser;
     var aCreateLazyBrowser;
     var aNextTabParentId;
     var aFocusUrlBar;
     var aName;
     var aCsp;
     if (arguments.length == 2 &&
         typeof arguments[1] == "object" &&
         !(arguments[1] instanceof Ci.nsIURI)) {
       let params = arguments[1];
       aTriggeringPrincipal = params.triggeringPrincipal;
-      aReferrerURI = params.referrerURI;
-      aReferrerPolicy = params.referrerPolicy;
+      aReferrerInfo = params.referrerInfo;
       aCharset = params.charset;
       aPostData = params.postData;
       aLoadInBackground = params.inBackground;
       aAllowThirdPartyFixup = params.allowThirdPartyFixup;
       aFromExternal = params.fromExternal;
       aRelatedToCurrent = params.relatedToCurrent;
       aAllowInheritPrincipal = !!params.allowInheritPrincipal;
       aAllowMixedContent = params.allowMixedContent;
       aSkipAnimation = params.skipAnimation;
       aForceNotRemote = params.forceNotRemote;
       aPreferredRemoteType = params.preferredRemoteType;
-      aNoReferrer = params.noReferrer;
       aUserContextId = params.userContextId;
       aSameProcessAsFrameLoader = params.sameProcessAsFrameLoader;
       aOriginPrincipal = params.originPrincipal;
       aOpener = params.opener;
       aOpenerBrowser = params.openerBrowser;
       aCreateLazyBrowser = params.createLazyBrowser;
       aNextTabParentId = params.nextTabParentId;
       aFocusUrlBar = params.focusUrlBar;
@@ -1444,31 +1441,29 @@ window._gBrowser = {
     }
 
     var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
       Services.prefs.getBoolPref("browser.tabs.loadInBackground");
     var owner = bgLoad ? null : this.selectedTab;
 
     var tab = this.addTab(aURI, {
       triggeringPrincipal: aTriggeringPrincipal,
-      referrerURI: aReferrerURI,
-      referrerPolicy: aReferrerPolicy,
+      referrerInfo: aReferrerInfo,
       charset: aCharset,
       postData: aPostData,
       ownerTab: owner,
       allowInheritPrincipal: aAllowInheritPrincipal,
       allowThirdPartyFixup: aAllowThirdPartyFixup,
       fromExternal: aFromExternal,
       relatedToCurrent: aRelatedToCurrent,
       skipAnimation: aSkipAnimation,
       allowMixedContent: aAllowMixedContent,
       forceNotRemote: aForceNotRemote,
       createLazyBrowser: aCreateLazyBrowser,
       preferredRemoteType: aPreferredRemoteType,
-      noReferrer: aNoReferrer,
       userContextId: aUserContextId,
       originPrincipal: aOriginPrincipal,
       sameProcessAsFrameLoader: aSameProcessAsFrameLoader,
       opener: aOpener,
       openerBrowser: aOpenerBrowser,
       nextTabParentId: aNextTabParentId,
       focusUrlBar: aFocusUrlBar,
       name: aName,
@@ -2339,26 +2334,24 @@ window._gBrowser = {
     focusUrlBar,
     forceNotRemote,
     fromExternal,
     index,
     lazyTabTitle,
     name,
     nextTabParentId,
     noInitialLabel,
-    noReferrer,
     opener,
     openerBrowser,
     originPrincipal,
     ownerTab,
     pinned,
     postData,
     preferredRemoteType,
-    referrerPolicy,
-    referrerURI,
+    referrerInfo,
     relatedToCurrent,
     sameProcessAsFrameLoader,
     skipAnimation,
     skipBackgroundNotify,
     triggeringPrincipal,
     userContextId,
     recordExecution,
     replayExecution,
@@ -2380,23 +2373,23 @@ window._gBrowser = {
     // determining positioning, and inherited attributes such as the
     // user context ID.
     //
     // If we have a browser opener (which is usually the browser
     // element from a remote window.open() call), use that.
     //
     // Otherwise, if the tab is related to the current tab (e.g.,
     // because it was opened by a link click), use the selected tab as
-    // the owner. If referrerURI is set, and we don't have an
+    // the owner. If referrerInfo is set, and we don't have an
     // explicit relatedToCurrent arg, we assume that the tab is
     // related to the current tab, since referrerURI is null or
     // undefined if the tab is opened from an external application or
     // bookmark (i.e. somewhere other than an existing tab).
     if (relatedToCurrent == null) {
-      relatedToCurrent = !!referrerURI;
+      relatedToCurrent = !!(referrerInfo && referrerInfo.originalReferrer);
     }
     let openerTab = ((openerBrowser && this.getTabForBrowser(openerBrowser)) ||
       (relatedToCurrent && this.selectedTab));
 
     var t = document.createXULElement("tab");
 
     t.openerTab = openerTab;
 
@@ -2532,19 +2525,21 @@ window._gBrowser = {
       // opener, use the opener's remote type.
       if (!preferredRemoteType && openerBrowser) {
         preferredRemoteType = openerBrowser.remoteType;
       }
 
       // If URI is about:blank and we don't have a preferred remote type,
       // then we need to use the referrer, if we have one, to get the
       // correct remote type for the new tab.
-      if (uriIsAboutBlank && !preferredRemoteType && referrerURI) {
+      if (uriIsAboutBlank && !preferredRemoteType && referrerInfo &&
+          referrerInfo.originalReferrer) {
         preferredRemoteType =
-          E10SUtils.getRemoteTypeForURI(referrerURI.spec, gMultiProcessBrowser);
+          E10SUtils.getRemoteTypeForURI(referrerInfo.originalReferrer.spec,
+                                        gMultiProcessBrowser);
       }
 
       let remoteType =
         forceNotRemote ? E10SUtils.NOT_REMOTE :
         E10SUtils.getRemoteTypeForURI(aURI, gMultiProcessBrowser,
           preferredRemoteType);
 
       // If we open a new tab with the newtab URL in the default
@@ -2662,26 +2657,21 @@ window._gBrowser = {
         flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
       }
       if (allowMixedContent) {
         flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT;
       }
       if (!allowInheritPrincipal) {
         flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
       }
-
-      let ReferrerInfo = Components.Constructor("@mozilla.org/referrer-info;1",
-                                                "nsIReferrerInfo",
-                                                "init");
       try {
         b.loadURI(aURI, {
           flags,
           triggeringPrincipal,
-          referrerInfo: new ReferrerInfo(
-            referrerPolicy, !noReferrer, referrerURI),
+          referrerInfo,
           charset,
           postData,
           csp,
         });
       } catch (ex) {
         Cu.reportError(ex);
       }
     }
--- a/browser/base/content/test/general/browser_relatedTabs.js
+++ b/browser/base/content/test/general/browser_relatedTabs.js
@@ -5,18 +5,24 @@
 add_task(async function() {
   is(gBrowser.tabs.length, 1, "one tab is open initially");
 
   // Add several new tabs in sequence, interrupted by selecting a
   // different tab, moving a tab around and closing a tab,
   // returning a list of opened tabs for verifying the expected order.
   // The new tab behaviour is documented in bug 465673
   let tabs = [];
+  let ReferrerInfo = Components.Constructor("@mozilla.org/referrer-info;1",
+                                            "nsIReferrerInfo",
+                                            "init");
+
   function addTab(aURL, aReferrer) {
-    let tab = BrowserTestUtils.addTab(gBrowser, aURL, {referrerURI: aReferrer});
+    let referrerInfo = new ReferrerInfo(
+      Ci.nsIHttpChannel.REFERRER_POLICY_UNSET, true, aReferrer);
+    let tab = BrowserTestUtils.addTab(gBrowser, aURL, { referrerInfo });
     tabs.push(tab);
     return BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   }
 
   await addTab("http://mochi.test:8888/#0");
   gBrowser.selectedTab = tabs[0];
   await addTab("http://mochi.test:8888/#1");
   await addTab("http://mochi.test:8888/#2", gBrowser.currentURI);
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -97,37 +97,36 @@ function doGetProtocolFlags(aURI) {
  *
  * @param url {string}
  * @param event {Event | Object} Event or JSON object representing an Event
  * @param {Boolean | Object} aIgnoreButton
  * @param {Boolean} aIgnoreButton
  * @param {Boolean} aIgnoreAlt
  * @param {Boolean} aAllowThirdPartyFixup
  * @param {Object} aPostData
- * @param {nsIURI} aReferrerURI
+ * @param {Object} aReferrerInfo
  */
 function openUILink(url, event, aIgnoreButton, aIgnoreAlt, aAllowThirdPartyFixup,
-                    aPostData, aReferrerURI) {
+                    aPostData, aReferrerInfo) {
   event = getRootEvent(event);
   let params;
 
   if (aIgnoreButton && typeof aIgnoreButton == "object") {
     params = aIgnoreButton;
 
     // don't forward "ignoreButton" and "ignoreAlt" to openUILinkIn
     aIgnoreButton = params.ignoreButton;
     aIgnoreAlt = params.ignoreAlt;
     delete params.ignoreButton;
     delete params.ignoreAlt;
   } else {
     params = {
       allowThirdPartyFixup: aAllowThirdPartyFixup,
       postData: aPostData,
-      referrerURI: aReferrerURI,
-      referrerPolicy: Ci.nsIHttpChannel.REFERRER_POLICY_UNSET,
+      referrerInfo: aReferrerInfo,
       initiatingDoc: event ? event.target.ownerDocument : null,
     };
   }
 
   if (!params.triggeringPrincipal) {
     throw new Error("Required argument triggeringPrincipal missing within openUILink");
   }
 
@@ -275,25 +274,25 @@ function openWebLinkIn(url, where, param
  * allowThirdPartyFixup controls whether third party services such as Google's
  * I Feel Lucky are allowed to interpret this URL. This parameter may be
  * undefined, which is treated as false.
  *
  * Instead of aAllowThirdPartyFixup, you may also pass an object with any of
  * these properties:
  *   allowThirdPartyFixup (boolean)
  *   postData             (nsIInputStream)
- *   referrerURI          (nsIURI)
+ *   referrerInfo         (nsIReferrerInfo)
  *   relatedToCurrent     (boolean)
  *   skipTabAnimation     (boolean)
  *   allowPinnedTabHostChange (boolean)
  *   allowPopups          (boolean)
  *   userContextId        (unsigned int)
  *   targetBrowser        (XUL browser)
  */
-function openUILinkIn(url, where, aAllowThirdPartyFixup, aPostData, aReferrerURI) {
+function openUILinkIn(url, where, aAllowThirdPartyFixup, aPostData, aReferrerInfo) {
   var params;
 
   if (arguments.length == 3 && typeof arguments[2] == "object") {
     params = aAllowThirdPartyFixup;
   }
   if (!params || !params.triggeringPrincipal) {
     throw new Error("Required argument triggeringPrincipal missing within openUILinkIn");
   }
@@ -302,62 +301,63 @@ function openUILinkIn(url, where, aAllow
 
   openLinkIn(url, where, params);
 }
 
 /* eslint-disable complexity */
 function openLinkIn(url, where, params) {
   if (!where || !url)
     return;
+  let ReferrerInfo = Components.Constructor("@mozilla.org/referrer-info;1",
+                                            "nsIReferrerInfo",
+                                            "init");
 
   var aFromChrome           = params.fromChrome;
   var aAllowThirdPartyFixup = params.allowThirdPartyFixup;
   var aPostData             = params.postData;
   var aCharset              = params.charset;
-  var aReferrerURI          = params.referrerURI;
-  var aReferrerPolicy       = ("referrerPolicy" in params ?
-      params.referrerPolicy : Ci.nsIHttpChannel.REFERRER_POLICY_UNSET);
+  var aReferrerInfo       = ("referrerInfo" in params)
+    ? params.referrerInfo
+    : new ReferrerInfo(Ci.nsIHttpChannel.REFERRER_POLICY_UNSET, true, null);
   var aRelatedToCurrent     = params.relatedToCurrent;
   var aAllowInheritPrincipal = !!params.allowInheritPrincipal;
   var aAllowMixedContent    = params.allowMixedContent;
   var aForceAllowDataURI    = params.forceAllowDataURI;
   var aInBackground         = params.inBackground;
   var aInitiatingDoc        = params.initiatingDoc;
   var aIsPrivate            = params.private;
   var aSkipTabAnimation     = params.skipTabAnimation;
   var aAllowPinnedTabHostChange = !!params.allowPinnedTabHostChange;
-  var aNoReferrer           = params.noReferrer;
   var aAllowPopups          = !!params.allowPopups;
   var aUserContextId        = params.userContextId;
   var aIndicateErrorPageLoad = params.indicateErrorPageLoad;
   var aPrincipal            = params.originPrincipal;
   var aTriggeringPrincipal  = params.triggeringPrincipal;
   var aCsp                  = params.csp;
   var aForceAboutBlankViewerInCurrent =
       params.forceAboutBlankViewerInCurrent;
   var aResolveOnNewTabCreated = params.resolveOnNewTabCreated;
 
   if (!aTriggeringPrincipal) {
     throw new Error("Must load with a triggering Principal");
   }
 
   if (where == "save") {
     // TODO(1073187): propagate referrerPolicy.
-
     // ContentClick.jsm passes isContentWindowPrivate for saveURL instead of passing a CPOW initiatingDoc
     if ("isContentWindowPrivate" in params) {
-      saveURL(url, null, null, true, true, aNoReferrer ? null : aReferrerURI,
+      saveURL(url, null, null, true, true, aReferrerInfo.sendReferrer ? aReferrerInfo.originalReferrer : null,
               null, params.isContentWindowPrivate, aPrincipal);
     } else {
       if (!aInitiatingDoc) {
         Cu.reportError("openUILink/openLinkIn was called with " +
           "where == 'save' but without initiatingDoc.  See bug 814264.");
         return;
       }
-      saveURL(url, null, null, true, true, aNoReferrer ? null : aReferrerURI, aInitiatingDoc);
+      saveURL(url, null, null, true, true, aReferrerInfo.sendReferrer ? aReferrerInfo.originalReferrer : null, aInitiatingDoc);
     }
     return;
   }
 
   // Establish which window we'll load the link in.
   let w;
   if (where == "current" && params.targetBrowser) {
     w = params.targetBrowser.ownerGlobal;
@@ -391,17 +391,17 @@ function openLinkIn(url, where, params) 
   aTriggeringPrincipal = useOAForPrincipal(aTriggeringPrincipal);
 
   if (!w || where == "window") {
     let features = "chrome,dialog=no,all";
     if (aIsPrivate) {
       features += ",private";
       // To prevent regular browsing data from leaking to private browsing sites,
       // strip the referrer when opening a new private window. (See Bug: 1409226)
-      aNoReferrer = true;
+      aReferrerInfo.sendReferrer = false;
     }
 
     // This propagates to window.arguments.
     var sa = Cc["@mozilla.org/array;1"].
              createInstance(Ci.nsIMutableArray);
 
     var wuri = Cc["@mozilla.org/supports-string;1"].
                createInstance(Ci.nsISupportsString);
@@ -413,37 +413,25 @@ function openLinkIn(url, where, params) 
                   .createInstance(Ci.nsISupportsString);
       charset.data = "charset=" + aCharset;
     }
 
     var allowThirdPartyFixupSupports = Cc["@mozilla.org/supports-PRBool;1"].
                                        createInstance(Ci.nsISupportsPRBool);
     allowThirdPartyFixupSupports.data = aAllowThirdPartyFixup;
 
-    var referrerURISupports = null;
-    if (aReferrerURI && !aNoReferrer) {
-      referrerURISupports = Cc["@mozilla.org/supports-string;1"].
-                            createInstance(Ci.nsISupportsString);
-      referrerURISupports.data = aReferrerURI.spec;
-    }
-
-    var referrerPolicySupports = Cc["@mozilla.org/supports-PRUint32;1"].
-                                 createInstance(Ci.nsISupportsPRUint32);
-    referrerPolicySupports.data = aReferrerPolicy;
-
     var userContextIdSupports = Cc["@mozilla.org/supports-PRUint32;1"].
                                  createInstance(Ci.nsISupportsPRUint32);
     userContextIdSupports.data = aUserContextId;
 
     sa.appendElement(wuri);
     sa.appendElement(charset);
-    sa.appendElement(referrerURISupports);
+    sa.appendElement(aReferrerInfo);
     sa.appendElement(aPostData);
     sa.appendElement(allowThirdPartyFixupSupports);
-    sa.appendElement(referrerPolicySupports);
     sa.appendElement(userContextIdSupports);
     sa.appendElement(aPrincipal);
     sa.appendElement(aTriggeringPrincipal);
     sa.appendElement(null); // allowInheritPrincipal
     sa.appendElement(aCsp);
 
     const sourceWindow = (w || window);
     let win;
@@ -553,25 +541,21 @@ function openLinkIn(url, where, params) 
     // start a new recording.
     if (targetBrowser.hasAttribute("recordExecution") &&
         targetBrowser.currentURI.spec != "about:blank") {
       w.gBrowser.updateBrowserRemoteness(targetBrowser,
                                          { recordExecution: "*", newFrameloader: true,
                                            remoteType: E10SUtils.DEFAULT_REMOTE_TYPE });
     }
 
-    let ReferrerInfo = Components.Constructor("@mozilla.org/referrer-info;1",
-                                              "nsIReferrerInfo",
-                                              "init");
     targetBrowser.loadURI(url, {
       triggeringPrincipal: aTriggeringPrincipal,
       csp: aCsp,
-      referrerInfo: new ReferrerInfo(
-        aReferrerPolicy, !aNoReferrer, aReferrerURI),
       flags,
+      referrerInfo: aReferrerInfo,
       postData: aPostData,
       userContextId: aUserContextId,
     });
 
     // Don't focus the content area if focus is in the address bar and we're
     // loading the New Tab page.
     focusUrlBar = w.document.activeElement == w.gURLBar.inputField &&
                   w.isBlankPageURL(url);
@@ -579,26 +563,24 @@ function openLinkIn(url, where, params) 
   case "tabshifted":
     loadInBackground = !loadInBackground;
     // fall through
   case "tab":
     focusUrlBar = !loadInBackground && w.isBlankPageURL(url)
       && !aboutNewTabService.willNotifyUser;
 
     let tabUsedForLoad = w.gBrowser.loadOneTab(url, {
-      referrerURI: aReferrerURI,
-      referrerPolicy: aReferrerPolicy,
+      referrerInfo: aReferrerInfo,
       charset: aCharset,
       postData: aPostData,
       inBackground: loadInBackground,
       allowThirdPartyFixup: aAllowThirdPartyFixup,
       relatedToCurrent: aRelatedToCurrent,
       skipAnimation: aSkipTabAnimation,
       allowMixedContent: aAllowMixedContent,
-      noReferrer: aNoReferrer,
       userContextId: aUserContextId,
       originPrincipal: aPrincipal,
       triggeringPrincipal: aTriggeringPrincipal,
       allowInheritPrincipal: aAllowInheritPrincipal,
       csp: aCsp,
       focusUrlBar,
     });
     targetBrowser = tabUsedForLoad.linkedBrowser;
--- a/browser/components/BrowserContentHandler.jsm
+++ b/browser/components/BrowserContentHandler.jsm
@@ -240,21 +240,20 @@ function openBrowserWindow(cmdLine, trig
       });
       args = [uriArray];
     } else {
       // Always pass at least 3 arguments to avoid the "|"-splitting behavior,
       // ie. avoid the loadOneOrMoreURIs function.
       // Also, we need to pass the triggering principal.
       args = [urlOrUrlList,
               null, // charset
-              null, // referer
+              null, // refererInfo
               postData,
               undefined, // allowThirdPartyFixup; this would be `false` but that
                          // needs a conversion. Hopefully bug 1485961 will fix.
-              undefined, // referrer policy
               undefined, // user context id
               null, // origin principal
               triggeringPrincipal];
     }
   }
 
   if (cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
     let win = Services.wm.getMostRecentWindow("navigator:blank");
--- a/browser/components/contextualidentity/test/browser/browser_relatedTab.js
+++ b/browser/components/contextualidentity/test/browser/browser_relatedTab.js
@@ -1,31 +1,38 @@
 "use strict";
 
 /*
  * Bug 1325014 - Adding tab related to current tab inherits current tab's container usercontextid unless otherwise specified
  */
 
 add_task(async function() {
   let tab = BrowserTestUtils.addTab(gBrowser, "about:blank", {userContextId: 1});
+  let ReferrerInfo = Components.Constructor("@mozilla.org/referrer-info;1",
+                                            "nsIReferrerInfo",
+                                            "init");
 
   gBrowser.selectedTab = tab;
   let relatedTab = BrowserTestUtils.addTab(gBrowser, "about:blank", {relatedToCurrent: true});
   is(relatedTab.getAttribute("usercontextid"), 1, "Related tab (relatedToCurrent) inherits current tab's usercontextid");
   BrowserTestUtils.removeTab(relatedTab);
 
   gBrowser.selectedTab = tab;
   relatedTab = BrowserTestUtils.addTab(gBrowser, "about:blank", {relatedToCurrent: true, userContextId: 2});
   is(relatedTab.getAttribute("usercontextid"), 2, "Related tab (relatedToCurrent) with overridden usercontextid");
   BrowserTestUtils.removeTab(relatedTab);
 
   gBrowser.selectedTab = tab;
-  relatedTab = BrowserTestUtils.addTab(gBrowser, "about:blank", {referrerURI: gBrowser.currentURI});
+  let referrerInfo = new ReferrerInfo(
+    Ci.nsIHttpChannel.REFERRER_POLICY_UNSET, true, gBrowser.currentURI);
+  relatedTab = BrowserTestUtils.addTab(gBrowser, "about:blank", { referrerInfo });
   is(relatedTab.getAttribute("usercontextid"), 1, "Related tab (referrer) inherits current tab's usercontextid");
   BrowserTestUtils.removeTab(relatedTab);
 
   gBrowser.selectedTab = tab;
-  relatedTab = BrowserTestUtils.addTab(gBrowser, "about:blank", {referrerURI: gBrowser.currentURI, userContextId: 2});
+  referrerInfo = new ReferrerInfo(
+    Ci.nsIHttpChannel.REFERRER_POLICY_UNSET, true, gBrowser.currentURI);
+  relatedTab = BrowserTestUtils.addTab(gBrowser, "about:blank", { referrerInfo, userContextId: 2});
   is(relatedTab.getAttribute("usercontextid"), 2, "Related tab (referrer) with overridden usercontextid");
   BrowserTestUtils.removeTab(relatedTab);
 
   BrowserTestUtils.removeTab(tab);
 });
--- a/browser/components/contextualidentity/test/browser/browser_usercontextid_new_window.js
+++ b/browser/components/contextualidentity/test/browser/browser_usercontextid_new_window.js
@@ -19,17 +19,16 @@ function openWindowWithUserContextId(use
   let urlSupports = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
   urlSupports.data = TEST_URI;
   args.appendElement(urlSupports);
 
   args.appendElement(null);
   args.appendElement(null);
   args.appendElement(null);
   args.appendElement(null);
-  args.appendElement(null);
 
   let userContextIdSupports = Cc["@mozilla.org/supports-PRUint32;1"].createInstance(Ci.nsISupportsPRUint32);
   userContextIdSupports.data = userContextId;
   args.appendElement(userContextIdSupports);
 
   args.appendElement(Services.scriptSecurityManager.getSystemPrincipal());
   args.appendElement(Services.scriptSecurityManager.getSystemPrincipal());
 
--- a/browser/components/extensions/parent/ext-browser.js
+++ b/browser/components/extensions/parent/ext-browser.js
@@ -239,18 +239,18 @@ global.TabContext = class extends EventE
     windowTracker.removeListener("progress", this);
     windowTracker.removeListener("TabSelect", this);
     tabTracker.off("tab-adopted", this.tabAdopted);
   }
 };
 
 // This promise is used to wait for the search service to be initialized.
 // None of the code in the WebExtension modules requests that initialization.
-// It is assumed that it is started at some point. If tests start to fail
-// because this promise never resolves, that's likely the cause.
+// It is assumed that it is started at some point. That might never happen,
+// e.g. if the application shuts down before the search service initializes.
 XPCOMUtils.defineLazyGetter(global, "searchInitialized", () => {
   if (Services.search.isInitialized) {
     return Promise.resolve();
   }
   return ExtensionUtils.promiseObserved("browser-search-service", (_, data) => data == "init-complete");
 });
 
 class WindowTracker extends WindowTrackerBase {
--- a/browser/components/extensions/parent/ext-chrome-settings-overrides.js
+++ b/browser/components/extensions/parent/ext-chrome-settings-overrides.js
@@ -74,16 +74,23 @@ async function handleInitialHomepagePopu
     if (currentUrl == homepageUrl && gBrowser.selectedTab == tab) {
       homepagePopup.open();
       return;
     }
   }
   homepagePopup.addObserver(extensionId);
 }
 
+// When an extension starts up, a search engine may asynchronously be
+// registered, without blocking the startup. When an extension is
+// uninstalled, we need to wait for this registration to finish
+// before running the uninstallation handler.
+// Map[extension id -> Promise]
+var pendingSearchSetupTasks = new Map();
+
 this.chrome_settings_overrides = class extends ExtensionAPI {
   static async processDefaultSearchSetting(action, id) {
     await ExtensionSettingsStore.initialize();
     let item = ExtensionSettingsStore.getSetting(DEFAULT_SEARCH_STORE_TYPE, DEFAULT_SEARCH_SETTING_NAME);
     if (!item) {
       return;
     }
     if (Services.search.defaultEngine.name != item.value &&
@@ -126,17 +133,21 @@ this.chrome_settings_overrides = class e
 
   static removeSearchSettings(id) {
     return Promise.all([
       this.processDefaultSearchSetting("removeSetting", id),
       this.removeEngine(id),
     ]);
   }
 
-  static onUninstall(id) {
+  static async onUninstall(id) {
+    let searchStartupPromise = pendingSearchSetupTasks.get(id);
+    if (searchStartupPromise) {
+      await searchStartupPromise;
+    }
     // Note: We do not have to deal with homepage here as it is managed by
     // the ExtensionPreferencesManager.
     return Promise.all([
       this.removeSearchSettings(id),
       homepagePopup.clearConfirmation(id),
     ]);
   }
 
@@ -185,78 +196,100 @@ this.chrome_settings_overrides = class e
         close: () => {
           if (extension.shutdownReason == "ADDON_DISABLE") {
             homepagePopup.clearConfirmation(extension.id);
           }
         },
       });
     }
     if (manifest.chrome_settings_overrides.search_provider) {
-      await searchInitialized;
-      extension.callOnClose({
-        close: () => {
-          if (extension.shutdownReason == "ADDON_DISABLE") {
-            chrome_settings_overrides.processDefaultSearchSetting("disable", extension.id);
-            chrome_settings_overrides.removeEngine(extension.id);
+      // Registering a search engine can potentially take a long while,
+      // or not complete at all (when searchInitialized is never resolved),
+      // so we are deliberately not awaiting the returned promise here.
+      let searchStartupPromise =
+        this.processSearchProviderManifestEntry().finally(() => {
+          if (pendingSearchSetupTasks.get(extension.id) === searchStartupPromise) {
+            pendingSearchSetupTasks.delete(extension.id);
           }
-        },
-      });
+        });
+
+      // Save the promise so we can await at onUninstall.
+      pendingSearchSetupTasks.set(extension.id, searchStartupPromise);
+    }
+  }
+
+  async processSearchProviderManifestEntry() {
+    await searchInitialized;
 
-      let searchProvider = manifest.chrome_settings_overrides.search_provider;
-      let engineName = searchProvider.name.trim();
-      if (searchProvider.is_default) {
-        let engine = Services.search.getEngineByName(engineName);
-        let defaultEngines = await Services.search.getDefaultEngines();
-        if (engine && defaultEngines.some(defaultEngine => defaultEngine.name == engineName)) {
-          // Needs to be called every time to handle reenabling, but
-          // only sets default for install or enable.
-          await this.setDefault(engineName);
-          // For built in search engines, we don't do anything further
-          return;
+    let {extension} = this;
+    if (!extension) {
+      Cu.reportError(`Extension shut down before search provider was registered`);
+      return;
+    }
+    extension.callOnClose({
+      close: () => {
+        if (extension.shutdownReason == "ADDON_DISABLE") {
+          chrome_settings_overrides.processDefaultSearchSetting("disable", extension.id);
+          chrome_settings_overrides.removeEngine(extension.id);
         }
+      },
+    });
+
+    let {manifest} = extension;
+    let searchProvider = manifest.chrome_settings_overrides.search_provider;
+    let engineName = searchProvider.name.trim();
+    if (searchProvider.is_default) {
+      let engine = Services.search.getEngineByName(engineName);
+      let defaultEngines = await Services.search.getDefaultEngines();
+      if (engine && defaultEngines.some(defaultEngine => defaultEngine.name == engineName)) {
+        // Needs to be called every time to handle reenabling, but
+        // only sets default for install or enable.
+        await this.setDefault(engineName);
+        // For built in search engines, we don't do anything further
+        return;
       }
-      await this.addSearchEngine();
-      if (searchProvider.is_default) {
-        if (extension.startupReason === "ADDON_INSTALL") {
-          // Don't ask if it already the current engine
-          let engine = Services.search.getEngineByName(engineName);
-          let defaultEngine = await Services.search.getDefault();
-          if (defaultEngine.name != engine.name) {
-            let subject = {
-              wrappedJSObject: {
-                // This is a hack because we don't have the browser of
-                // the actual install. This means the popup might show
-                // in a different window. Will be addressed in a followup bug.
-                browser: windowTracker.topWindow.gBrowser.selectedBrowser,
-                name: this.extension.name,
-                icon: this.extension.iconURL,
-                currentEngine: defaultEngine.name,
-                newEngine: engineName,
-                respond(allow) {
-                  if (allow) {
-                    ExtensionSettingsStore.addSetting(
-                      extension.id, DEFAULT_SEARCH_STORE_TYPE, DEFAULT_SEARCH_SETTING_NAME, engineName, () => defaultEngine.name);
-                    Services.search.defaultEngine = Services.search.getEngineByName(engineName);
-                  }
-                },
+    }
+    await this.addSearchEngine();
+    if (searchProvider.is_default) {
+      if (extension.startupReason === "ADDON_INSTALL") {
+        // Don't ask if it already the current engine
+        let engine = Services.search.getEngineByName(engineName);
+        let defaultEngine = await Services.search.getDefault();
+        if (defaultEngine.name != engine.name) {
+          let subject = {
+            wrappedJSObject: {
+              // This is a hack because we don't have the browser of
+              // the actual install. This means the popup might show
+              // in a different window. Will be addressed in a followup bug.
+              browser: windowTracker.topWindow.gBrowser.selectedBrowser,
+              name: this.extension.name,
+              icon: this.extension.iconURL,
+              currentEngine: defaultEngine.name,
+              newEngine: engineName,
+              respond(allow) {
+                if (allow) {
+                  ExtensionSettingsStore.addSetting(
+                    extension.id, DEFAULT_SEARCH_STORE_TYPE, DEFAULT_SEARCH_SETTING_NAME, engineName, () => defaultEngine.name);
+                  Services.search.defaultEngine = Services.search.getEngineByName(engineName);
+                }
               },
-            };
-            Services.obs.notifyObservers(subject, "webextension-defaultsearch-prompt");
-          }
-        } else {
-          // Needs to be called every time to handle reenabling, but
-          // only sets default for install or enable.
-          this.setDefault(engineName);
+            },
+          };
+          Services.obs.notifyObservers(subject, "webextension-defaultsearch-prompt");
         }
-      } else if (ExtensionSettingsStore.hasSetting(extension.id,
-                                                   DEFAULT_SEARCH_STORE_TYPE,
-                                                   DEFAULT_SEARCH_SETTING_NAME)) {
-        // is_default has been removed, but we still have a setting. Remove it.
-        chrome_settings_overrides.processDefaultSearchSetting("removeSetting", extension.id);
+      } else {
+        // Needs to be called every time to handle reenabling, but
+        // only sets default for install or enable.
+        this.setDefault(engineName);
       }
+    } else if (ExtensionSettingsStore.hasSetting(extension.id,
+                                                 DEFAULT_SEARCH_STORE_TYPE,
+                                                 DEFAULT_SEARCH_SETTING_NAME)) {
+      // is_default has been removed, but we still have a setting. Remove it.
+      chrome_settings_overrides.processDefaultSearchSetting("removeSetting", extension.id);
     }
   }
 
   async setDefault(engineName) {
     let {extension} = this;
     if (extension.startupReason === "ADDON_INSTALL") {
       let defaultEngine = await Services.search.getDefault();
       let item = await ExtensionSettingsStore.addSetting(
--- a/browser/components/extensions/parent/ext-windows.js
+++ b/browser/components/extensions/parent/ext-windows.js
@@ -199,20 +199,19 @@ this.windows = class extends ExtensionAP
                 !context.checkLoadURL(url, {dontReportErrors: true})) {
               // The extension principal cannot directly load about:-URLs,
               // except for about:blank. So use the system principal instead.
               principal = Services.scriptSecurityManager.getSystemPrincipal();
             }
           }
 
           args.appendElement(null); // unused
-          args.appendElement(null); // referrer
+          args.appendElement(null); // referrerInfo
           args.appendElement(null); // postData
           args.appendElement(null); // allowThirdPartyFixup
-          args.appendElement(null); // referrerPolicy
 
           if (createData.cookieStoreId) {
             let userContextIdSupports = Cc["@mozilla.org/supports-PRUint32;1"].createInstance(Ci.nsISupportsPRUint32);
             // May throw if validation fails.
             userContextIdSupports.data = getUserContextIdForCookieStoreId(extension, createData.cookieStoreId, createData.incognito);
             args.appendElement(userContextIdSupports); // userContextId
           } else {
             args.appendElement(null);
--- a/browser/components/extensions/test/browser/browser_ext_search.js
+++ b/browser/components/extensions/test/browser/browser_ext_search.js
@@ -1,15 +1,19 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+const {AddonTestUtils} = ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm");
+
 const SEARCH_TERM = "test";
 const SEARCH_URL = "https://localhost/?q={searchTerms}";
 
+AddonTestUtils.initMochitest(this);
+
 add_task(async function test_search() {
   async function background(SEARCH_TERM) {
     function awaitSearchResult() {
       return new Promise(resolve => {
         async function listener(tabId, info, changedTab) {
           if (changedTab.url == "about:blank") {
             // Ignore events related to the initial tab open.
             return;
@@ -52,16 +56,17 @@ add_task(async function test_search() {
           "search_url": SEARCH_URL,
         },
       },
     },
     background: `(${background})("${SEARCH_TERM}")`,
     useAddonManager: "temporary",
   });
   await extension.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(extension);
 
   let addonEngines = await extension.awaitMessage("engines");
   let engines = (await Services.search.getEngines()).filter(engine => !engine.hidden);
   is(addonEngines.length, engines.length, "Engine lengths are the same.");
   let defaultEngine = addonEngines.filter(engine => engine.isDefault === true);
   is(defaultEngine.length, 1, "One default engine");
   is(defaultEngine[0].name, (await Services.search.getDefault()).name, "Default engine is correct");
 
--- a/browser/components/extensions/test/browser/browser_ext_settings_overrides_default_search.js
+++ b/browser/components/extensions/test/browser/browser_ext_settings_overrides_default_search.js
@@ -1,19 +1,23 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 
 "use strict";
 
 ChromeUtils.defineModuleGetter(this, "AddonManager",
                                "resource://gre/modules/AddonManager.jsm");
 
+const {AddonTestUtils} = ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm");
+
 const EXTENSION1_ID = "extension1@mozilla.com";
 const EXTENSION2_ID = "extension2@mozilla.com";
 
+AddonTestUtils.initMochitest(this);
+
 var defaultEngineName;
 
 async function restoreDefaultEngine() {
   let engine = Services.search.getEngineByName(defaultEngineName);
   await Services.search.setDefault(engine);
 }
 
 add_task(async function setup() {
@@ -32,16 +36,17 @@ add_task(async function test_extension_s
           "is_default": true,
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(ext1);
 
   is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   await ext1.unload();
 
   is((await Services.search.getDefault()).name, defaultEngineName, `Default engine is ${defaultEngineName}`);
 });
 
@@ -145,20 +150,22 @@ add_task(async function test_extension_s
           "is_default": true,
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(ext1);
 
   is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   await ext2.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(ext2);
 
   is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
 
   await ext2.unload();
 
   is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   await ext1.unload();
@@ -191,20 +198,22 @@ add_task(async function test_extension_s
           "is_default": true,
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(ext1);
 
   is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   await ext2.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(ext2);
 
   is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
 
   await ext1.unload();
 
   is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
 
   await ext2.unload();
@@ -224,16 +233,17 @@ add_task(async function test_user_changi
           "is_default": true,
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(ext1);
 
   is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   let engine = Services.search.getEngineByName("Twitter");
   await Services.search.setDefault(engine);
 
   await ext1.unload();
 
@@ -258,16 +268,17 @@ add_task(async function test_user_change
           "is_default": true,
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(ext1);
 
   is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   let engine = Services.search.getEngineByName("Twitter");
   await Services.search.setDefault(engine);
 
   is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
 
@@ -323,27 +334,29 @@ add_task(async function test_two_addons_
           "is_default": true,
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(ext1);
 
   is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   let disabledPromise = awaitEvent("shutdown", EXTENSION1_ID);
   let addon1 = await AddonManager.getAddonByID(EXTENSION1_ID);
   await addon1.disable();
   await disabledPromise;
 
   is((await Services.search.getDefault()).name, defaultEngineName, `Default engine is ${defaultEngineName}`);
 
   await ext2.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(ext2);
 
   is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
 
   let enabledPromise = awaitEvent("ready", EXTENSION1_ID);
   await addon1.enable();
   await enabledPromise;
 
   is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
@@ -391,20 +404,22 @@ add_task(async function test_two_addons_
           "is_default": true,
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(ext1);
 
   is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   await ext2.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(ext2);
 
   is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
 
   let disabledPromise = awaitEvent("shutdown", EXTENSION1_ID);
   let addon1 = await AddonManager.getAddonByID(EXTENSION1_ID);
   await addon1.disable();
   await disabledPromise;
 
@@ -459,20 +474,22 @@ add_task(async function test_two_addons_
           "is_default": true,
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(ext1);
 
   is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   await ext2.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(ext2);
 
   is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
 
   let disabledPromise = awaitEvent("shutdown", EXTENSION2_ID);
   let addon2 = await AddonManager.getAddonByID(EXTENSION2_ID);
   await addon2.disable();
   await disabledPromise;
 
--- a/browser/components/extensions/test/xpcshell/test_ext_chrome_settings_overrides_update.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_chrome_settings_overrides_update.js
@@ -60,16 +60,17 @@ add_task(async function test_overrides_u
   };
   let extension = ExtensionTestUtils.loadExtension(extensionInfo);
 
   let defaultHomepageURL = HomePage.get();
   let defaultEngineName = (await Services.search.getDefault()).name;
 
   let prefPromise = promisePrefChanged(HOMEPAGE_URI);
   await extension.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(extension);
   await prefPromise;
 
   equal(extension.version, "1.0", "The installed addon has the expected version.");
   ok(HomePage.get().endsWith(HOMEPAGE_URI),
      "Home page url is overridden by the extension.");
   equal((await Services.search.getDefault()).name,
         "DuckDuckGo",
         "Default engine is overridden by the extension");
--- a/browser/components/extensions/test/xpcshell/test_ext_settings_overrides_search.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_settings_overrides_search.js
@@ -37,16 +37,17 @@ add_task(async function test_extension_a
           "suggest_url": kSearchSuggestURL,
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(ext1);
 
   let engine = Services.search.getEngineByName("MozSearch");
   ok(engine, "Engine should exist.");
 
   let {baseURI} = ext1.extension;
   equal(engine.iconURI.spec, baseURI.resolve("foo.ico"), "icon path matches");
   let icons = engine.getIcons();
   equal(icons.length, 2, "both icons avialable");
@@ -78,16 +79,17 @@ add_task(async function test_extension_a
           "search_url": "https://example.com/?q={searchTerms}",
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(ext1);
 
   let engine = Services.search.getEngineByName("MozSearch");
   ok(engine, "Engine should exist.");
 
   await ext1.unload();
   await delay();
 
   engine = Services.search.getEngineByName("MozSearch");
@@ -111,16 +113,17 @@ add_task(async function test_upgrade_def
         },
       },
       "version": "0.1",
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(ext1);
 
   let engine = Services.search.getEngineByName("MozSearch");
   await Services.search.setDefault(engine);
   await Services.search.moveEngine(engine, 1);
 
   await ext1.upgrade({
     manifest: {
       "chrome_settings_overrides": {
@@ -134,16 +137,17 @@ add_task(async function test_upgrade_def
         "gecko": {
           "id": "testengine@mozilla.com",
         },
       },
       "version": "0.2",
     },
     useAddonManager: "temporary",
   });
+  await AddonTestUtils.waitForSearchProviderStartup(ext1);
 
   engine = Services.search.getEngineByName("MozSearch");
   equal(Services.search.defaultEngine, engine, "Default engine should still be MozSearch");
   equal((await Services.search.getEngines()).map(e => e.name).indexOf(engine.name),
         1, "Engine is in position 1");
 
   await ext1.unload();
   await delay();
@@ -165,16 +169,17 @@ add_task(async function test_extension_p
           "suggest_url_post_params": "foo=bar&bar=foo",
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(ext1);
 
   let engine = Services.search.getEngineByName("MozSearch");
   ok(engine, "Engine should exist.");
 
   let url = engine.wrappedJSObject._getURLOfType("text/html");
   equal(url.method, "POST", "Search URLs method is POST");
 
   let expectedURL = kSearchEngineURL.replace("{searchTerms}", kSearchTerm);
--- a/browser/components/extensions/test/xpcshell/test_ext_settings_overrides_search_mozParam.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_settings_overrides_search_mozParam.js
@@ -55,16 +55,17 @@ add_task(async function test_extension_s
           "search_url": "https://example.com/?q={searchTerms}",
           "params": [...mozParams, ...params],
         },
       },
     },
     useAddonManager: "permanent",
   });
   await extension.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(extension);
   equal(extension.extension.isPrivileged, true, "extension is priviledged");
 
   let engine = Services.search.getEngineByName("MozParamsTest");
 
   let extraParams = [];
   for (let p of params) {
     if (p.condition == "pref") {
       extraParams.push(`${p.name}=good`);
@@ -108,14 +109,15 @@ add_task(async function test_extension_s
             {name: "q", value: "{searchTerms}"},
           ],
         },
       },
     },
     useAddonManager: "permanent",
   });
   await extension.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(extension);
   equal(extension.extension.isPrivileged, false, "extension is not priviledged");
   let engine = Services.search.getEngineByName("MozParamsTest");
   let expectedURL = engine.getSubmission("test", null, "contextmenu").uri.spec;
   equal(expectedURL, "https://example.com/?q=test", "engine cannot have conditional or pref params");
   await extension.unload();
 });
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/xpcshell/test_ext_settings_overrides_shutdown.js
@@ -0,0 +1,95 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+
+"use strict";
+
+const {AddonTestUtils} = ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm");
+// Lazily import ExtensionParent to allow AddonTestUtils.createAppInfo to
+// override Services.appinfo.
+ChromeUtils.defineModuleGetter(this, "ExtensionParent",
+                               "resource://gre/modules/ExtensionParent.jsm");
+
+AddonTestUtils.init(this);
+AddonTestUtils.overrideCertDB();
+AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42", "42");
+
+add_task(async function shutdown_during_search_provider_startup() {
+  await AddonTestUtils.promiseStartupManager();
+
+  let extension = ExtensionTestUtils.loadExtension({
+    useAddonManager: "permanent",
+    manifest: {
+      chrome_settings_overrides: {
+        search_provider: {
+          name: "dummy name",
+          search_url: "https://example.com/",
+        },
+      },
+    },
+  });
+
+  info("Starting up search extension");
+  await extension.startup();
+  let extStartPromise = AddonTestUtils.waitForSearchProviderStartup(extension, {
+    // Search provider registration is expected to be pending because the search
+    // service has not been initialized yet.
+    expectPending: true,
+  });
+
+  let initialized = false;
+  ExtensionParent.apiManager.global.searchInitialized.then(() => {
+    initialized = true;
+  });
+
+  await extension.addon.disable();
+
+  info("Extension managed to shut down despite the uninitialized search");
+  // Initialize search after extension shutdown to check that it does not cause
+  // any problems, and that the test can continue to test uninstall behavior.
+  Assert.ok(!initialized, "Search service should not have been initialized");
+
+  extension.addon.enable();
+  await extension.awaitStartup();
+
+  // Check that uninstall is blocked until the search registration at startup
+  // has finished. This registration only finished once the search service is
+  // initialized.
+  let uninstallingPromise = new Promise(resolve => {
+    let Management = ExtensionParent.apiManager;
+    Management.on("uninstall", function listener(eventName, {id}) {
+      Management.off("uninstall", listener);
+      Assert.equal(id, extension.id, "Expected extension");
+      resolve();
+    });
+  });
+
+  let extRestartPromise = AddonTestUtils.waitForSearchProviderStartup(extension, {
+    // Search provider registration is expected to be pending again,
+    // because the search service has still not been initialized yet.
+    expectPending: true,
+  });
+
+  let uninstalledPromise = extension.addon.uninstall();
+  let uninstalled = false;
+  uninstalledPromise.then(() => { uninstalled = true; });
+
+  await uninstallingPromise;
+  Assert.ok(!uninstalled, "Uninstall should not be finished yet");
+  Assert.ok(!initialized, "Search service should still be uninitialized");
+  await Services.search.init();
+  Assert.ok(initialized, "Search service should be initialized");
+
+  // After initializing the search service, the search provider registration
+  // promises should settle eventually.
+
+  // Despite the interrupted startup, the promise should still resolve without
+  // an error.
+  await extStartPromise;
+  // The extension that is still active. The promise should just resolve.
+  await extRestartPromise;
+
+  // After initializing the search service, uninstall should eventually finish.
+  await uninstalledPromise;
+
+  await AddonTestUtils.promiseShutdownManager();
+});
--- a/browser/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/browser/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -5,11 +5,12 @@
 [test_ext_browsingData_passwords.js]
 [test_ext_browsingData_settings.js]
 [test_ext_chrome_settings_overrides_update.js]
 [test_ext_distribution_popup.js]
 [test_ext_geckoProfiler_control.js]
 [test_ext_history.js]
 [test_ext_settings_overrides_search.js]
 [test_ext_settings_overrides_search_mozParam.js]
+[test_ext_settings_overrides_shutdown.js]
 [test_ext_url_overrides_newtab.js]
 [test_ext_url_overrides_newtab_update.js]
 
--- a/browser/components/newtab/common/Reducers.jsm
+++ b/browser/components/newtab/common/Reducers.jsm
@@ -479,16 +479,44 @@ function DiscoveryStream(prevState = INI
     case at.DISCOVERY_STREAM_SPOCS_ENDPOINT:
       return {
         ...prevState,
         spocs: {
           ...INITIAL_STATE.DiscoveryStream.spocs,
           spocs_endpoint: action.data || INITIAL_STATE.DiscoveryStream.spocs.spocs_endpoint,
         },
       };
+    case at.PLACES_LINK_BLOCKED:
+      // Return if action data is empty, or spocs or feeds data is not loaded
+      if (!action.data || !prevState.spocs.loaded || !prevState.feeds.loaded) {
+        return prevState;
+      }
+      // Filter spocs and recommendations data inside feeds by removing action.data.url
+      // received on PLACES_LINK_BLOCKED triggered by dismiss link menu option
+      return {
+        ...prevState,
+        spocs: {
+          ...prevState.spocs,
+          data: prevState.spocs.data.spocs ? {
+            spocs: prevState.spocs.data.spocs.filter(s => s.url !== action.data.url),
+          } : {},
+        },
+        feeds: {
+          ...prevState.feeds,
+          data: Object.keys(prevState.feeds.data).reduce((accumulator, feed_url) => {
+            accumulator[feed_url] = {
+              data: {
+                ...prevState.feeds.data[feed_url].data,
+                recommendations: prevState.feeds.data[feed_url].data.recommendations.filter(r => r.url !== action.data.url),
+              },
+            };
+            return accumulator;
+          }, {}),
+        },
+      };
     case at.DISCOVERY_STREAM_SPOCS_UPDATE:
       if (action.data) {
         return {
           ...prevState,
           spocs: {
             ...prevState.spocs,
             lastUpdated: action.data.lastUpdated,
             data: action.data.spocs,
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
@@ -55,17 +55,18 @@ export class DSCard extends React.PureCo
           </div>
           <ImpressionStats
             campaignId={this.props.campaignId}
             rows={[{id: this.props.id, pos: this.props.pos}]}
             dispatch={this.props.dispatch}
             source={this.props.type} />
         </SafeAnchor>
         <DSLinkMenu
-          index={this.props.index}
+          id={this.props.id}
+          index={this.props.pos}
           dispatch={this.props.dispatch}
           intl={this.props.intl}
           url={this.props.url}
           title={this.props.title}
           source={this.props.source}
           type={this.props.type} />
       </div>
     );
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss
@@ -16,16 +16,20 @@
       }
 
       color: $blue-60;
     }
   }
 
   &:active {
     header {
+      @include dark-theme-only {
+        color: $blue-50;
+      }
+
       color: $blue-70;
     }
   }
 
   .img-wrapper {
     width: 100%;
   }
 
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSLinkMenu/DSLinkMenu.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSLinkMenu/DSLinkMenu.jsx
@@ -37,41 +37,43 @@ export class _DSLinkMenu extends React.P
       dsLinkMenuHostDiv.parentElement.classList.add("last-item");
     }
     dsLinkMenuHostDiv.parentElement.classList.add("active");
   }
 
   render() {
     const {index, dispatch} = this.props;
     const isContextMenuOpen = this.state.showContextMenu && this.state.activeCard === index;
-    const TOP_STORIES_SOURCE = "TOP_STORIES";
-    const TOP_STORIES_CONTEXT_MENU_OPTIONS = ["OpenInNewWindow", "OpenInPrivateWindow"];
+    const TOP_STORIES_CONTEXT_MENU_OPTIONS = ["OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"];
     const title = this.props.title || this.props.source;
+    const type = this.props.type || "DISCOVERY_STREAM";
 
     return (<div>
       <button ref={this.contextMenuButtonRef}
               className="context-menu-button icon"
               title={this.props.intl.formatMessage({id: "context_menu_title"})}
               onClick={this.onMenuButtonClick}>
         <span className="sr-only">
           <FormattedMessage id="context_menu_button_sr" values={{title}} />
         </span>
       </button>
       {isContextMenuOpen &&
         <LinkMenu
           dispatch={dispatch}
           index={index}
-          source={TOP_STORIES_SOURCE}
+          source={type.toUpperCase()}
           onUpdate={this.onMenuUpdate}
           onShow={this.onMenuShow}
           options={TOP_STORIES_CONTEXT_MENU_OPTIONS}
+          shouldSendImpressionStats={true}
           site={{
             referrer: "https://getpocket.com/recommendations",
             title: this.props.title,
             type: this.props.type,
             url: this.props.url,
+            guid: this.props.id,
           }} />
       }
     </div>);
   }
 }
 
 export const DSLinkMenu = injectIntl(_DSLinkMenu);
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Hero/Hero.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Hero/Hero.jsx
@@ -27,17 +27,17 @@ export class Hero extends React.PureComp
       }));
     }
   }
 
   render() {
     const {data} = this.props;
 
     // Handle a render before feed has been fetched by displaying nothing
-    if (!data || !data.recommendations) {
+    if (!data || !data.recommendations || !data.recommendations.length) {
       return (
         <div />
       );
     }
 
     let [heroRec, ...otherRecs] = data.recommendations.slice(0, this.props.items);
     this.heroRec = heroRec;
 
@@ -92,17 +92,18 @@ export class Hero extends React.PureComp
               </div>
               <ImpressionStats
                 campaignId={heroRec.campaignId}
                 rows={[{id: heroRec.id, pos: heroRec.pos}]}
                 dispatch={this.props.dispatch}
                 source={this.props.type} />
             </SafeAnchor>
             <DSLinkMenu
-              index={this.props.index}
+              id={heroRec.id}
+              index={heroRec.pos}
               dispatch={this.props.dispatch}
               intl={this.props.intl}
               url={heroRec.url}
               title={heroRec.title}
               source={heroRec.domain}
               type={this.props.type} />
           </div>
           <div className={`${this.props.subComponentType}`}>
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Hero/_Hero.scss
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Hero/_Hero.scss
@@ -167,16 +167,17 @@
         padding-top: 100%; // 1:1 aspect ratio
       }
     }
 
     .cards {
       display: grid;
       grid-template-columns: repeat(2, 1fr);
       grid-column-gap: 24px;
+      grid-auto-rows: min-content;
     }
   }
 
   // "Full width layout"
   .ds-column-9 &,
   .ds-column-10 &,
   .ds-column-11 &,
   .ds-column-12 & {
@@ -226,16 +227,17 @@
         }
       }
     }
 
     .cards {
       display: grid;
       grid-template-columns: repeat(2, 1fr);
       grid-column-gap: 24px;
+      grid-auto-rows: min-content;
 
       .ds-card {
         &:hover {
           @include dark-theme-only {
             background: none;
 
             .title {
               color: $blue-40;
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/List/List.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/List/List.jsx
@@ -58,17 +58,18 @@ export class ListItem extends React.Pure
           <div className="ds-list-image" style={{backgroundImage: `url(${this.props.image_src})`}} />
           <ImpressionStats
             campaignId={this.props.campaignId}
             rows={[{id: this.props.id, pos: this.props.pos}]}
             dispatch={this.props.dispatch}
             source={this.props.type} />
         </SafeAnchor>
         <DSLinkMenu
-          index={this.props.index}
+          id={this.props.id}
+          index={this.props.pos}
           dispatch={this.props.dispatch}
           intl={this.props.intl}
           url={this.props.url}
           title={this.props.title}
           source={this.props.source}
           type={this.props.type} />
       </li>
     );
--- a/browser/components/newtab/css/activity-stream-linux.css
+++ b/browser/components/newtab/css/activity-stream-linux.css
@@ -2060,17 +2060,18 @@ main {
       height: 0;
       padding-top: 100%; }
   .ds-column-5 .ds-hero .cards,
   .ds-column-6 .ds-hero .cards,
   .ds-column-7 .ds-hero .cards,
   .ds-column-8 .ds-hero .cards {
     display: grid;
     grid-template-columns: repeat(2, 1fr);
-    grid-column-gap: 24px; }
+    grid-column-gap: 24px;
+    grid-auto-rows: min-content; }
   .ds-column-9 .ds-hero,
   .ds-column-10 .ds-hero,
   .ds-column-11 .ds-hero,
   .ds-column-12 .ds-hero {
     display: grid;
     grid-template-columns: repeat(2, 1fr);
     grid-column-gap: 24px; }
     .ds-column-9 .ds-hero.ds-hero-border,
@@ -2138,17 +2139,18 @@ main {
         .ds-column-12 .ds-hero .wrapper .meta .source {
           margin-bottom: 0; }
     .ds-column-9 .ds-hero .cards,
     .ds-column-10 .ds-hero .cards,
     .ds-column-11 .ds-hero .cards,
     .ds-column-12 .ds-hero .cards {
       display: grid;
       grid-template-columns: repeat(2, 1fr);
-      grid-column-gap: 24px; }
+      grid-column-gap: 24px;
+      grid-auto-rows: min-content; }
       [lwt-newtab-brighttext]:not(.force-light-theme) .ds-column-9 .ds-hero .cards .ds-card:hover, [lwt-newtab-brighttext]:not(.force-light-theme)
       .ds-column-10 .ds-hero .cards .ds-card:hover, [lwt-newtab-brighttext]:not(.force-light-theme)
       .ds-column-11 .ds-hero .cards .ds-card:hover, [lwt-newtab-brighttext]:not(.force-light-theme)
       .ds-column-12 .ds-hero .cards .ds-card:hover {
         background: none; }
         [lwt-newtab-brighttext]:not(.force-light-theme) .ds-column-9 .ds-hero .cards .ds-card:hover .title, [lwt-newtab-brighttext]:not(.force-light-theme)
         .ds-column-10 .ds-hero .cards .ds-card:hover .title, [lwt-newtab-brighttext]:not(.force-light-theme)
         .ds-column-11 .ds-hero .cards .ds-card:hover .title, [lwt-newtab-brighttext]:not(.force-light-theme)
@@ -2555,16 +2557,18 @@ main {
   flex-direction: column;
   position: relative; }
   .ds-card:hover header {
     color: #0060DF; }
     [lwt-newtab-brighttext]:not(.force-light-theme) .ds-card:hover header {
       color: #45A1FF; }
   .ds-card:active header {
     color: #003EAA; }
+    [lwt-newtab-brighttext]:not(.force-light-theme) .ds-card:active header {
+      color: #0A84FF; }
   .ds-card .img-wrapper {
     width: 100%; }
   .ds-card .img {
     background-color: var(--newtab-card-placeholder-color);
     background-position: center;
     background-repeat: no-repeat;
     background-size: cover;
     border-radius: 4px;
--- a/browser/components/newtab/css/activity-stream-mac.css
+++ b/browser/components/newtab/css/activity-stream-mac.css
@@ -2063,17 +2063,18 @@ main {
       height: 0;
       padding-top: 100%; }
   .ds-column-5 .ds-hero .cards,
   .ds-column-6 .ds-hero .cards,
   .ds-column-7 .ds-hero .cards,
   .ds-column-8 .ds-hero .cards {
     display: grid;
     grid-template-columns: repeat(2, 1fr);
-    grid-column-gap: 24px; }
+    grid-column-gap: 24px;
+    grid-auto-rows: min-content; }
   .ds-column-9 .ds-hero,
   .ds-column-10 .ds-hero,
   .ds-column-11 .ds-hero,
   .ds-column-12 .ds-hero {
     display: grid;
     grid-template-columns: repeat(2, 1fr);
     grid-column-gap: 24px; }
     .ds-column-9 .ds-hero.ds-hero-border,
@@ -2141,17 +2142,18 @@ main {
         .ds-column-12 .ds-hero .wrapper .meta .source {
           margin-bottom: 0; }
     .ds-column-9 .ds-hero .cards,
     .ds-column-10 .ds-hero .cards,
     .ds-column-11 .ds-hero .cards,
     .ds-column-12 .ds-hero .cards {
       display: grid;
       grid-template-columns: repeat(2, 1fr);
-      grid-column-gap: 24px; }
+      grid-column-gap: 24px;
+      grid-auto-rows: min-content; }
       [lwt-newtab-brighttext]:not(.force-light-theme) .ds-column-9 .ds-hero .cards .ds-card:hover, [lwt-newtab-brighttext]:not(.force-light-theme)
       .ds-column-10 .ds-hero .cards .ds-card:hover, [lwt-newtab-brighttext]:not(.force-light-theme)
       .ds-column-11 .ds-hero .cards .ds-card:hover, [lwt-newtab-brighttext]:not(.force-light-theme)
       .ds-column-12 .ds-hero .cards .ds-card:hover {
         background: none; }
         [lwt-newtab-brighttext]:not(.force-light-theme) .ds-column-9 .ds-hero .cards .ds-card:hover .title, [lwt-newtab-brighttext]:not(.force-light-theme)
         .ds-column-10 .ds-hero .cards .ds-card:hover .title, [lwt-newtab-brighttext]:not(.force-light-theme)
         .ds-column-11 .ds-hero .cards .ds-card:hover .title, [lwt-newtab-brighttext]:not(.force-light-theme)
@@ -2558,16 +2560,18 @@ main {
   flex-direction: column;
   position: relative; }
   .ds-card:hover header {
     color: #0060DF; }
     [lwt-newtab-brighttext]:not(.force-light-theme) .ds-card:hover header {
       color: #45A1FF; }
   .ds-card:active header {
     color: #003EAA; }
+    [lwt-newtab-brighttext]:not(.force-light-theme) .ds-card:active header {
+      color: #0A84FF; }
   .ds-card .img-wrapper {
     width: 100%; }
   .ds-card .img {
     background-color: var(--newtab-card-placeholder-color);
     background-position: center;
     background-repeat: no-repeat;
     background-size: cover;
     border-radius: 4px;
--- a/browser/components/newtab/css/activity-stream-windows.css
+++ b/browser/components/newtab/css/activity-stream-windows.css
@@ -2060,17 +2060,18 @@ main {
       height: 0;
       padding-top: 100%; }
   .ds-column-5 .ds-hero .cards,
   .ds-column-6 .ds-hero .cards,
   .ds-column-7 .ds-hero .cards,
   .ds-column-8 .ds-hero .cards {
     display: grid;
     grid-template-columns: repeat(2, 1fr);
-    grid-column-gap: 24px; }
+    grid-column-gap: 24px;
+    grid-auto-rows: min-content; }
   .ds-column-9 .ds-hero,
   .ds-column-10 .ds-hero,
   .ds-column-11 .ds-hero,
   .ds-column-12 .ds-hero {
     display: grid;
     grid-template-columns: repeat(2, 1fr);
     grid-column-gap: 24px; }
     .ds-column-9 .ds-hero.ds-hero-border,
@@ -2138,17 +2139,18 @@ main {
         .ds-column-12 .ds-hero .wrapper .meta .source {
           margin-bottom: 0; }
     .ds-column-9 .ds-hero .cards,
     .ds-column-10 .ds-hero .cards,
     .ds-column-11 .ds-hero .cards,
     .ds-column-12 .ds-hero .cards {
       display: grid;
       grid-template-columns: repeat(2, 1fr);
-      grid-column-gap: 24px; }
+      grid-column-gap: 24px;
+      grid-auto-rows: min-content; }
       [lwt-newtab-brighttext]:not(.force-light-theme) .ds-column-9 .ds-hero .cards .ds-card:hover, [lwt-newtab-brighttext]:not(.force-light-theme)
       .ds-column-10 .ds-hero .cards .ds-card:hover, [lwt-newtab-brighttext]:not(.force-light-theme)
       .ds-column-11 .ds-hero .cards .ds-card:hover, [lwt-newtab-brighttext]:not(.force-light-theme)
       .ds-column-12 .ds-hero .cards .ds-card:hover {
         background: none; }
         [lwt-newtab-brighttext]:not(.force-light-theme) .ds-column-9 .ds-hero .cards .ds-card:hover .title, [lwt-newtab-brighttext]:not(.force-light-theme)
         .ds-column-10 .ds-hero .cards .ds-card:hover .title, [lwt-newtab-brighttext]:not(.force-light-theme)
         .ds-column-11 .ds-hero .cards .ds-card:hover .title, [lwt-newtab-brighttext]:not(.force-light-theme)
@@ -2555,16 +2557,18 @@ main {
   flex-direction: column;
   position: relative; }
   .ds-card:hover header {
     color: #0060DF; }
     [lwt-newtab-brighttext]:not(.force-light-theme) .ds-card:hover header {
       color: #45A1FF; }
   .ds-card:active header {
     color: #003EAA; }
+    [lwt-newtab-brighttext]:not(.force-light-theme) .ds-card:active header {
+      color: #0A84FF; }
   .ds-card .img-wrapper {
     width: 100%; }
   .ds-card .img {
     background-color: var(--newtab-card-placeholder-color);
     background-position: center;
     background-repeat: no-repeat;
     background-size: cover;
     border-radius: 4px;
--- a/browser/components/newtab/data/content/activity-stream.bundle.js
+++ b/browser/components/newtab/data/content/activity-stream.bundle.js
@@ -7291,19 +7291,19 @@ class DSLinkMenu_DSLinkMenu extends exte
       dsLinkMenuHostDiv.parentElement.classList.add("last-item");
     }
     dsLinkMenuHostDiv.parentElement.classList.add("active");
   }
 
   render() {
     const { index, dispatch } = this.props;
     const isContextMenuOpen = this.state.showContextMenu && this.state.activeCard === index;
-    const TOP_STORIES_SOURCE = "TOP_STORIES";
-    const TOP_STORIES_CONTEXT_MENU_OPTIONS = ["OpenInNewWindow", "OpenInPrivateWindow"];
+    const TOP_STORIES_CONTEXT_MENU_OPTIONS = ["OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"];
     const title = this.props.title || this.props.source;
+    const type = this.props.type || "DISCOVERY_STREAM";
 
     return external_React_default.a.createElement(
       "div",
       null,
       external_React_default.a.createElement(
         "button",
         { ref: this.contextMenuButtonRef,
           className: "context-menu-button icon",
@@ -7313,25 +7313,27 @@ class DSLinkMenu_DSLinkMenu extends exte
           "span",
           { className: "sr-only" },
           external_React_default.a.createElement(external_ReactIntl_["FormattedMessage"], { id: "context_menu_button_sr", values: { title } })
         )
       ),
       isContextMenuOpen && external_React_default.a.createElement(LinkMenu["LinkMenu"], {
         dispatch: dispatch,
         index: index,
-        source: TOP_STORIES_SOURCE,
+        source: type.toUpperCase(),
         onUpdate: this.onMenuUpdate,
         onShow: this.onMenuShow,
         options: TOP_STORIES_CONTEXT_MENU_OPTIONS,
+        shouldSendImpressionStats: true,
         site: {
           referrer: "https://getpocket.com/recommendations",
           title: this.props.title,
           type: this.props.type,
-          url: this.props.url
+          url: this.props.url,
+          guid: this.props.id
         } })
     );
   }
 }
 
 const DSLinkMenu = Object(external_ReactIntl_["injectIntl"])(DSLinkMenu_DSLinkMenu);
 // EXTERNAL MODULE: ./content-src/components/DiscoveryStreamImpressionStats/ImpressionStats.jsx
 var ImpressionStats = __webpack_require__(33);
@@ -7478,17 +7480,18 @@ class DSCard_DSCard extends external_Rea
         ),
         external_React_default.a.createElement(ImpressionStats["ImpressionStats"], {
           campaignId: this.props.campaignId,
           rows: [{ id: this.props.id, pos: this.props.pos }],
           dispatch: this.props.dispatch,
           source: this.props.type })
       ),
       external_React_default.a.createElement(DSLinkMenu, {
-        index: this.props.index,
+        id: this.props.id,
+        index: this.props.pos,
         dispatch: this.props.dispatch,
         intl: this.props.intl,
         url: this.props.url,
         title: this.props.title,
         source: this.props.source,
         type: this.props.type })
     );
   }
@@ -7665,17 +7668,18 @@ class List_ListItem extends external_Rea
         external_React_default.a.createElement("div", { className: "ds-list-image", style: { backgroundImage: `url(${this.props.image_src})` } }),
         external_React_default.a.createElement(ImpressionStats["ImpressionStats"], {
           campaignId: this.props.campaignId,
           rows: [{ id: this.props.id, pos: this.props.pos }],
           dispatch: this.props.dispatch,
           source: this.props.type })
       ),
       external_React_default.a.createElement(DSLinkMenu, {
-        index: this.props.index,
+        id: this.props.id,
+        index: this.props.pos,
         dispatch: this.props.dispatch,
         intl: this.props.intl,
         url: this.props.url,
         title: this.props.title,
         source: this.props.source,
         type: this.props.type })
     );
   }
@@ -7759,17 +7763,17 @@ class Hero_Hero extends external_React_d
       }));
     }
   }
 
   render() {
     const { data } = this.props;
 
     // Handle a render before feed has been fetched by displaying nothing
-    if (!data || !data.recommendations) {
+    if (!data || !data.recommendations || !data.recommendations.length) {
       return external_React_default.a.createElement("div", null);
     }
 
     let [heroRec, ...otherRecs] = data.recommendations.slice(0, this.props.items);
     this.heroRec = heroRec;
 
     let cards = otherRecs.map((rec, index) => external_React_default.a.createElement(DSCard_DSCard, {
       campaignId: rec.campaign_id,
@@ -7847,17 +7851,18 @@ class Hero_Hero extends external_React_d
             ),
             external_React_default.a.createElement(ImpressionStats["ImpressionStats"], {
               campaignId: heroRec.campaignId,
               rows: [{ id: heroRec.id, pos: heroRec.pos }],
               dispatch: this.props.dispatch,
               source: this.props.type })
           ),
           external_React_default.a.createElement(DSLinkMenu, {
-            index: this.props.index,
+            id: heroRec.id,
+            index: heroRec.pos,
             dispatch: this.props.dispatch,
             intl: this.props.intl,
             url: heroRec.url,
             title: heroRec.title,
             source: heroRec.domain,
             type: this.props.type })
         ),
         external_React_default.a.createElement(
@@ -12352,16 +12357,40 @@ function DiscoveryStream(prevState = INI
         })
       });
     case Actions["actionTypes"].DISCOVERY_STREAM_SPOCS_ENDPOINT:
       return Object.assign({}, prevState, {
         spocs: Object.assign({}, INITIAL_STATE.DiscoveryStream.spocs, {
           spocs_endpoint: action.data || INITIAL_STATE.DiscoveryStream.spocs.spocs_endpoint
         })
       });
+    case Actions["actionTypes"].PLACES_LINK_BLOCKED:
+      // Return if action data is empty, or spocs or feeds data is not loaded
+      if (!action.data || !prevState.spocs.loaded || !prevState.feeds.loaded) {
+        return prevState;
+      }
+      // Filter spocs and recommendations data inside feeds by removing action.data.url
+      // received on PLACES_LINK_BLOCKED triggered by dismiss link menu option
+      return Object.assign({}, prevState, {
+        spocs: Object.assign({}, prevState.spocs, {
+          data: prevState.spocs.data.spocs ? {
+            spocs: prevState.spocs.data.spocs.filter(s => s.url !== action.data.url)
+          } : {}
+        }),
+        feeds: Object.assign({}, prevState.feeds, {
+          data: Object.keys(prevState.feeds.data).reduce((accumulator, feed_url) => {
+            accumulator[feed_url] = {
+              data: Object.assign({}, prevState.feeds.data[feed_url].data, {
+                recommendations: prevState.feeds.data[feed_url].data.recommendations.filter(r => r.url !== action.data.url)
+              })
+            };
+            return accumulator;
+          }, {})
+        })
+      });
     case Actions["actionTypes"].DISCOVERY_STREAM_SPOCS_UPDATE:
       if (action.data) {
         return Object.assign({}, prevState, {
           spocs: Object.assign({}, prevState.spocs, {
             lastUpdated: action.data.lastUpdated,
             data: action.data.spocs,
             loaded: true
           })
--- a/browser/components/newtab/lib/ActivityStream.jsm
+++ b/browser/components/newtab/lib/ActivityStream.jsm
@@ -238,16 +238,20 @@ const PREFS_CONFIG = new Map([
         api_key_pref: "extensions.pocket.oAuthConsumerKey",
         enabled: isEnabled,
         show_spocs: geo === "US",
         // This is currently an exmple layout used for dev purposes.
         layout_endpoint: "https://getpocket.cdn.mozilla.net/v3/newtab/layout?version=1&consumer_key=$apiKey&layout_variant=basic",
       });
     },
   }],
+  ["discoverystream.endpoints", {
+    title: "Endpoint prefixes (comma-separated) that are allowed to be requested",
+    value: "https://getpocket.cdn.mozilla.net/",
+  }],
   ["discoverystream.optOut.0", {
     title: "Opt out of new layout v0",
     value: false,
   }],
   ["discoverystream.spoc.impressions", {
     title: "Track spoc impressions",
     skipBroadcast: true,
     value: "{}",
--- a/browser/components/newtab/lib/DiscoveryStreamFeed.jsm
+++ b/browser/components/newtab/lib/DiscoveryStreamFeed.jsm
@@ -1,29 +1,31 @@
 /* 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 {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+const {NewTabUtils} = ChromeUtils.import("resource://gre/modules/NewTabUtils.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
 ChromeUtils.defineModuleGetter(this, "perfService", "resource://activity-stream/common/PerfService.jsm");
 
 const {actionTypes: at, actionCreators: ac} = ChromeUtils.import("resource://activity-stream/common/Actions.jsm");
 const {PersistentCache} = ChromeUtils.import("resource://activity-stream/lib/PersistentCache.jsm");
 
 const CACHE_KEY = "discovery_stream";
 const LAYOUT_UPDATE_TIME = 30 * 60 * 1000; // 30 minutes
 const STARTUP_CACHE_EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000; // 1 week
 const COMPONENT_FEEDS_UPDATE_TIME = 30 * 60 * 1000; // 30 minutes
 const SPOCS_FEEDS_UPDATE_TIME = 30 * 60 * 1000; // 30 minutes
 const DEFAULT_RECS_EXPIRE_TIME = 60 * 60 * 1000; // 1 hour
 const MAX_LIFETIME_CAP = 500; // Guard against misconfiguration on the server
 const PREF_CONFIG = "discoverystream.config";
+const PREF_ENDPOINTS = "discoverystream.endpoints";
 const PREF_OPT_OUT = "discoverystream.optOut.0";
 const PREF_SHOW_SPONSORED = "showSponsored";
 const PREF_SPOC_IMPRESSIONS = "discoverystream.spoc.impressions";
 const PREF_REC_IMPRESSIONS = "discoverystream.rec.impressions";
 
 this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
   constructor() {
     // Internal state for checking if we've intialized all our data
@@ -83,27 +85,30 @@ this.DiscoveryStreamFeed = class Discove
   }
 
   async fetchFromEndpoint(endpoint) {
     if (!endpoint) {
       Cu.reportError("Tried to fetch endpoint but none was configured.");
       return null;
     }
     try {
+      // Make sure the requested endpoint is allowed
+      const allowed = this.store.getState().Prefs.values[PREF_ENDPOINTS].split(",");
+      if (!allowed.some(prefix => endpoint.startsWith(prefix))) {
+        throw new Error(`Not one of allowed prefixes (${allowed})`);
+      }
+
       const response = await fetch(endpoint, {credentials: "omit"});
       if (!response.ok) {
-        // istanbul ignore next
-        throw new Error(`${endpoint} returned unexpected status: ${response.status}`);
+        throw new Error(`Unexpected status (${response.status})`);
       }
       return response.json();
     } catch (error) {
-      // istanbul ignore next
       Cu.reportError(`Failed to fetch ${endpoint}: ${error.message}`);
     }
-    // istanbul ignore next
     return null;
   }
 
   /**
    * Returns true if data in the cache for a particular key has expired or is missing.
    * @param {object} cachedData data returned from cache.get()
    * @param {string} key a cache key
    * @param {string?} url for "feed" only, the URL of the feed.
@@ -195,30 +200,35 @@ this.DiscoveryStreamFeed = class Discove
   buildFeedPromise({newFeedsPromises, newFeeds}, isStartup) {
     return component => {
       const {url} = component.feed;
 
       if (!newFeeds[url]) {
         // We initially stub this out so we don't fetch dupes,
         // we then fill in with the proper object inside the promise.
         newFeeds[url] = {};
-
         const feedPromise = this.getComponentFeed(url, isStartup);
-
-        feedPromise.then(data => {
-          newFeeds[url] = data;
+        feedPromise.then(feed => {
+          newFeeds[url] = this.filterRecommendations(feed);
         }).catch(/* istanbul ignore next */ error => {
           Cu.reportError(`Error trying to load component feed ${url}: ${error}`);
         });
 
         newFeedsPromises.push(feedPromise);
       }
     };
   }
 
+  filterRecommendations(feed) {
+    if (feed && feed.data && feed.data.recommendations && feed.data.recommendations.length) {
+      return {data: this.filterBlocked(feed.data, "recommendations")};
+    }
+    return feed;
+  }
+
   /**
    * reduceFeedComponents - Filters out components with no feeds, and combines
    *                        all feeds on this component with the feeds from other components.
    * @param {Boolean} isStartup We have different cache handling for startup.
    * @returns {Function} We return a function so we can contain the scope for isStartup.
    *                     Reduces feeds into promises and feed data.
    */
   reduceFeedComponents(isStartup) {
@@ -304,22 +314,34 @@ this.DiscoveryStreamFeed = class Discove
       lastUpdated: Date.now(),
       data: {},
     };
 
     sendUpdate({
       type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
       data: {
         lastUpdated: spocs.lastUpdated,
-        spocs: this.transform(this.filterSpocs(spocs.data)),
+        spocs: this.transform(this.frequencyCapSpocs(spocs.data)),
       },
     });
   }
 
-  transform(data) {
+  filterBlocked(data, type) {
+    if (data && data[type] && data[type].length) {
+      const filteredItems = data[type].filter(item => !NewTabUtils.blockedLinks.isBlocked({"url": item.url}));
+      return {
+        ...data,
+        [type]: filteredItems,
+      };
+    }
+    return data;
+  }
+
+  transform(spocs) {
+    const data = this.filterBlocked(spocs, "spocs");
     if (data && data.spocs && data.spocs.length) {
       const spocsPerDomain = this.store.getState().DiscoveryStream.spocs.spocs_per_domain || 1;
       const campaignMap = {};
       return {
         ...data,
         spocs: data.spocs
           .map(s => ({...s, score: s.item_score}))
           .filter(s => s.score >= s.min_score)
@@ -335,17 +357,17 @@ this.DiscoveryStreamFeed = class Discove
             return false;
           }),
       };
     }
     return data;
   }
 
   // Filter spocs based on frequency caps
-  filterSpocs(data) {
+  frequencyCapSpocs(data) {
     if (data && data.spocs && data.spocs.length) {
       const {spocs} = data;
       const impressions = this.readImpressionsPref(PREF_SPOC_IMPRESSIONS);
       return {
         ...data,
         spocs: spocs.filter(s => this.isBelowFrequencyCap(impressions, s)),
       };
     }
@@ -706,17 +728,17 @@ this.DiscoveryStreamFeed = class Discove
 
           const cachedData = await this.cache.get() || {};
           const {spocs} = cachedData;
 
           this.store.dispatch(ac.AlsoToPreloaded({
             type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
             data: {
               lastUpdated: spocs.lastUpdated,
-              spocs: this.transform(this.filterSpocs(spocs.data)),
+              spocs: this.transform(this.frequencyCapSpocs(spocs.data)),
             },
           }));
         }
         break;
       case at.UNINIT:
         // When this feed is shutting down:
         this.uninitPrefs();
         break;
--- a/browser/components/newtab/locales-src/id/strings.properties
+++ b/browser/components/newtab/locales-src/id/strings.properties
@@ -88,16 +88,18 @@ section_disclaimer_topstories_buttontext
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
 prefs_home_header=Konten Beranda Firefox
 prefs_home_description=Pilih konten yang ingin Anda tampilkan dalam Beranda Firefox.
 
 prefs_content_discovery_header=Beranda Firefox
+prefs_content_discovery_description=Penemuan Konten dalam Firefox Home memungkinkan Anda untuk menemukan artikel bermutu tinggi dan relevan dari seluruh web.
+prefs_content_discovery_button=Matikan Penemuan Konten
 
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} baris
 prefs_search_header=Pencarian Web
 prefs_topsites_description=Situs yang sering Anda kunjungi
 prefs_topstories_description2=Konten bermutu dari seluruh web, khusus untuk Anda
--- a/browser/components/newtab/locales-src/lij/strings.properties
+++ b/browser/components/newtab/locales-src/lij/strings.properties
@@ -1,9 +1,9 @@
-newtab_page_title=Neuvo Feuggio
+newtab_page_title=Neuvo feuggio
 
 header_top_sites=I megio sciti
 header_highlights=In evidensa
 # LOCALIZATION NOTE(header_recommended_by): This is followed by the name
 # of the corresponding content provider.
 header_recommended_by=Consegiou da {provider}
 
 # LOCALIZATION NOTE(context_menu_button_sr): This is for screen readers when
@@ -47,18 +47,18 @@ menu_action_archive_pocket=Archivia in P
 
 # LOCALIZATION NOTE (menu_action_show_file_*): These are platform specific strings
 # found in the context menu of an item that has been downloaded. The intention behind
 # "this action" is that it will show where the downloaded file exists on the file system
 # for each operating system.
 menu_action_show_file_mac_os=Fanni vedde in Finder
 menu_action_show_file_windows=Arvi cartella
 menu_action_show_file_linux=Arvi cartella
-menu_action_show_file_default=Fanni vedde file
-menu_action_open_file=Arvi file
+menu_action_show_file_default=Mostra o schedaio
+menu_action_open_file=Arvi schedaio
 
 # LOCALIZATION NOTE (menu_action_copy_download_link, menu_action_go_to_download_page):
 # "Download" here, in both cases, is not a verb, it is a noun. As in, "Copy the
 # link that belongs to this downloaded item"
 menu_action_copy_download_link=Còpia indirisso òrigine
 menu_action_go_to_download_page=Vanni a-a pagina de descaregamento
 menu_action_remove_download=Scancella da-a stöia
 
@@ -88,16 +88,17 @@ section_disclaimer_topstories_buttontext=Va ben, ò capio
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
 prefs_home_header=Pagina iniçiâ de Firefox
 prefs_home_description=Çerni i contegnui che ti veu vedde inta pagina iniçiâ de Firefox.
 
 prefs_content_discovery_header=Pagina iniçiâ de Firefox
+prefs_content_discovery_button=Dizabilita a descoverta de neuvi contegnui
 
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} riga;{num} righe
 prefs_search_header=Çerca into Web
 prefs_topsites_description=I sciti che ti vixiti de ciù
 prefs_topstories_description2=I megio contegnui pigiæ in gio pe-a ræ, personalizæ pe ti
--- a/browser/components/newtab/locales-src/pt-BR/strings.properties
+++ b/browser/components/newtab/locales-src/pt-BR/strings.properties
@@ -127,24 +127,24 @@ edit_topsites_edit_button=Editar este si
 
 # LOCALIZATION NOTE (topsites_form_*): This is shown in the New/Edit Topsite modal.
 topsites_form_add_header=Novo site popular
 topsites_form_edit_header=Editar site popular
 topsites_form_title_label=Título
 topsites_form_title_placeholder=Digite um título
 topsites_form_url_label=URL
 topsites_form_image_url_label=URL de imagem personalizada
-topsites_form_url_placeholder=Digite ou cole um URL
+topsites_form_url_placeholder=Digite ou cole uma URL
 topsites_form_use_image_link=Usar uma imagem personalizada…
 # LOCALIZATION NOTE (topsites_form_*_button): These are verbs/actions.
 topsites_form_preview_button=Visualizar
 topsites_form_add_button=Adicionar
 topsites_form_save_button=Salvar
 topsites_form_cancel_button=Cancelar
-topsites_form_url_validation=É necessário um URL válido
+topsites_form_url_validation=É necessário uma URL válida
 topsites_form_image_validation=Não foi possível carregar a imagem. Tente uma URL diferente.
 
 # LOCALIZATION NOTE (pocket_read_more): This is shown at the bottom of the
 # trending stories section and precedes a list of links to popular topics.
 pocket_read_more=Tópicos populares:
 # LOCALIZATION NOTE (pocket_read_even_more): This is shown as a link at the
 # end of the list of popular topic links.
 pocket_read_even_more=Ver mais histórias
--- a/browser/components/newtab/prerendered/locales/id/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/id/activity-stream-strings.js
@@ -36,18 +36,18 @@ window.gActivityStreamStrings = {
   "search_header": "Pencarian {search_engine_name}",
   "search_web_placeholder": "Cari di Web",
   "section_disclaimer_topstories": "Kisah paling menarik di web, dipilih berdasarkan yang Anda baca. Dari Pocket, kini bagian dari Mozilla.",
   "section_disclaimer_topstories_linktext": "Pelajari cara kerjanya.",
   "section_disclaimer_topstories_buttontext": "Oke, paham",
   "prefs_home_header": "Konten Beranda Firefox",
   "prefs_home_description": "Pilih konten yang ingin Anda tampilkan dalam Beranda Firefox.",
   "prefs_content_discovery_header": "Beranda Firefox",
-  "prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
-  "prefs_content_discovery_button": "Turn Off Content Discovery",
+  "prefs_content_discovery_description": "Penemuan Konten dalam Firefox Home memungkinkan Anda untuk menemukan artikel bermutu tinggi dan relevan dari seluruh web.",
+  "prefs_content_discovery_button": "Matikan Penemuan Konten",
   "prefs_section_rows_option": "{num} baris",
   "prefs_search_header": "Pencarian Web",
   "prefs_topsites_description": "Situs yang sering Anda kunjungi",
   "prefs_topstories_description2": "Konten bermutu dari seluruh web, khusus untuk Anda",
   "prefs_topstories_options_sponsored_label": "Konten Sponsor",
   "prefs_topstories_sponsored_learn_more": "Pelajari lebih lanjut",
   "prefs_highlights_description": "Sejumlah situs yang Anda simpan atau kunjungi",
   "prefs_highlights_options_visited_label": "Laman yang Dikunjungi",
--- a/browser/components/newtab/prerendered/locales/lij/activity-stream-noscripts.html
+++ b/browser/components/newtab/prerendered/locales/lij/activity-stream-noscripts.html
@@ -1,14 +1,14 @@
 <!doctype html>
 <html lang="lij" dir="ltr">
   <head>
     <meta charset="utf-8">
     <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline' resource: chrome:; connect-src https:; img-src https: data: blob:; style-src 'unsafe-inline';">
-    <title>Neuvo Feuggio</title>
+    <title>Neuvo feuggio</title>
     <link rel="icon" type="image/png" href="chrome://branding/content/icon32.png"/>
     <link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
     <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
   </head>
   <body class="activity-stream">
     <div id="root"><!-- Regular React Rendering --></div>
     <div id="snippets-container">
       <div id="snippets"></div>
--- a/browser/components/newtab/prerendered/locales/lij/activity-stream-prerendered-noscripts.html
+++ b/browser/components/newtab/prerendered/locales/lij/activity-stream-prerendered-noscripts.html
@@ -1,14 +1,14 @@
 <!doctype html>
 <html lang="lij" dir="ltr">
   <head>
     <meta charset="utf-8">
     <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline' resource: chrome:; connect-src https:; img-src https: data: blob:; style-src 'unsafe-inline';">
-    <title>Neuvo Feuggio</title>
+    <title>Neuvo feuggio</title>
     <link rel="icon" type="image/png" href="chrome://branding/content/icon32.png"/>
     <link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
     <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
   </head>
   <body class="activity-stream">
     <div id="root"><div data-reactroot=""><div class="outer-wrapper fixed-to-top"><main><div class="non-collapsible-section"><div class="search-wrapper"><div class="search-inner-wrapper"><label for="newtab-search-text" class="search-label"><span class="sr-only"><span>Çerca inta Ræ</span></span></label><input type="search" id="newtab-search-text" maxLength="256" placeholder="Çerca inta Ræ" title="Çerca inta Ræ"/><button id="searchSubmit" class="search-button" title="Çerca"><span class="sr-only"><span>Çerca</span></span></button></div></div></div><div class="body-wrapper"><div class="sections-list"><section class="collapsible-section top-sites animation-enabled" data-section-id="topsites"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-topsites"></span><span>I megio sciti</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="Arvi menû"><span class="sr-only"><span>Arvi into menû contesto pe-a seçion</span></span></button></div></div><div class="section-body"><ul class="top-sites-list"><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Cangia sto scito"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Cangia sto scito"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Cangia sto scito"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Cangia sto scito"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Cangia sto scito"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Cangia sto scito"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Cangia sto scito"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Cangia sto scito"></button></div></li></ul><div class="edit-topsites-wrapper"></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="topstories"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-pocket"></span><span>Consegiou da Pocket</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="Arvi menû"><span class="sr-only"><span>Arvi into menû contesto pe-a seçion</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul><div class="top-stories-bottom-container"><div class="wrapper-more-recommendations"></div></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="highlights"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-highlights"></span><span>In evidensa</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="Arvi menû"><span class="sr-only"><span>Arvi into menû contesto pe-a seçion</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul></div></section></div><div class="prefs-button"><button class="icon icon-settings" title="Personalizza a teu pagina Neuvo feuggio"></button></div></div></main></div></div></div>
     <div id="snippets-container">
       <div id="snippets"></div>
--- a/browser/components/newtab/prerendered/locales/lij/activity-stream-prerendered.html
+++ b/browser/components/newtab/prerendered/locales/lij/activity-stream-prerendered.html
@@ -1,14 +1,14 @@
 <!doctype html>
 <html lang="lij" dir="ltr">
   <head>
     <meta charset="utf-8">
     <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline' resource: chrome:; connect-src https:; img-src https: data: blob:; style-src 'unsafe-inline';">
-    <title>Neuvo Feuggio</title>
+    <title>Neuvo feuggio</title>
     <link rel="icon" type="image/png" href="chrome://branding/content/icon32.png"/>
     <link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
     <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
   </head>
   <body class="activity-stream">
     <div id="root"><div data-reactroot=""><div class="outer-wrapper fixed-to-top"><main><div class="non-collapsible-section"><div class="search-wrapper"><div class="search-inner-wrapper"><label for="newtab-search-text" class="search-label"><span class="sr-only"><span>Çerca inta Ræ</span></span></label><input type="search" id="newtab-search-text" maxLength="256" placeholder="Çerca inta Ræ" title="Çerca inta Ræ"/><button id="searchSubmit" class="search-button" title="Çerca"><span class="sr-only"><span>Çerca</span></span></button></div></div></div><div class="body-wrapper"><div class="sections-list"><section class="collapsible-section top-sites animation-enabled" data-section-id="topsites"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-topsites"></span><span>I megio sciti</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="Arvi menû"><span class="sr-only"><span>Arvi into menû contesto pe-a seçion</span></span></button></div></div><div class="section-body"><ul class="top-sites-list"><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Cangia sto scito"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Cangia sto scito"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Cangia sto scito"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Cangia sto scito"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Cangia sto scito"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Cangia sto scito"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Cangia sto scito"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Cangia sto scito"></button></div></li></ul><div class="edit-topsites-wrapper"></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="topstories"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-pocket"></span><span>Consegiou da Pocket</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="Arvi menû"><span class="sr-only"><span>Arvi into menû contesto pe-a seçion</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul><div class="top-stories-bottom-container"><div class="wrapper-more-recommendations"></div></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="highlights"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-highlights"></span><span>In evidensa</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="Arvi menû"><span class="sr-only"><span>Arvi into menû contesto pe-a seçion</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul></div></section></div><div class="prefs-button"><button class="icon icon-settings" title="Personalizza a teu pagina Neuvo feuggio"></button></div></div></main></div></div></div>
     <div id="snippets-container">
       <div id="snippets"></div>
--- a/browser/components/newtab/prerendered/locales/lij/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/lij/activity-stream-strings.js
@@ -1,11 +1,11 @@
 // Note - this is a generated lij file.
 window.gActivityStreamStrings = {
-  "newtab_page_title": "Neuvo Feuggio",
+  "newtab_page_title": "Neuvo feuggio",
   "header_top_sites": "I megio sciti",
   "header_highlights": "In evidensa",
   "header_recommended_by": "Consegiou da {provider}",
   "context_menu_button_sr": "Arvi into menû contesto pe {title}",
   "section_context_menu_button_sr": "Arvi into menû contesto pe-a seçion",
   "type_label_visited": "Vixitou",
   "type_label_bookmarked": "Azonto a-i segnalibbri",
   "type_label_recommended": "De tentensa",
@@ -22,32 +22,32 @@ window.gActivityStreamStrings = {
   "confirm_history_delete_p1": "Te seguo de scancelâ tutte e ripetiçioin de sta pagina da stöia?",
   "confirm_history_delete_notice_p2": "Sta açion a no se peu anulâ.",
   "menu_action_save_to_pocket": "Sarva in Pocket",
   "menu_action_delete_pocket": "Scancella da Pocket",
   "menu_action_archive_pocket": "Archivia in Pocket",
   "menu_action_show_file_mac_os": "Fanni vedde in Finder",
   "menu_action_show_file_windows": "Arvi cartella",
   "menu_action_show_file_linux": "Arvi cartella",
-  "menu_action_show_file_default": "Fanni vedde file",
-  "menu_action_open_file": "Arvi file",
+  "menu_action_show_file_default": "Mostra o schedaio",
+  "menu_action_open_file": "Arvi schedaio",
   "menu_action_copy_download_link": "Còpia indirisso òrigine",
   "menu_action_go_to_download_page": "Vanni a-a pagina de descaregamento",
   "menu_action_remove_download": "Scancella da-a stöia",
   "search_button": "Çerca",
   "search_header": "Riçerca {search_engine_name}",
   "search_web_placeholder": "Çerca inta Ræ",
   "section_disclaimer_topstories": "E stöie ciù interesanti do Web, seleçionæ in baze a quello che ti lezi. Pigiæ da Pocket, che oua o l'é parte de Mozilla.",
   "section_disclaimer_topstories_linktext": "Descòvri comme fonçionn-a.",
   "section_disclaimer_topstories_buttontext": "Va ben, ò capio",
   "prefs_home_header": "Pagina iniçiâ de Firefox",
   "prefs_home_description": "Çerni i contegnui che ti veu vedde inta pagina iniçiâ de Firefox.",
   "prefs_content_discovery_header": "Pagina iniçiâ de Firefox",
   "prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
-  "prefs_content_discovery_button": "Turn Off Content Discovery",
+  "prefs_content_discovery_button": "Dizabilita a descoverta de neuvi contegnui",
   "prefs_section_rows_option": "{num} riga;{num} righe",
   "prefs_search_header": "Çerca into Web",
   "prefs_topsites_description": "I sciti che ti vixiti de ciù",
   "prefs_topstories_description2": "I megio contegnui pigiæ in gio pe-a ræ, personalizæ pe ti",
   "prefs_topstories_options_sponsored_label": "Stöie sponsorizæ",
   "prefs_topstories_sponsored_learn_more": "Atre informaçioin",
   "prefs_highlights_description": "'Na seleçion di sciti che t'ê sarvou ò vixitou",
   "prefs_highlights_options_visited_label": "Pagine vixitæ",
--- a/browser/components/newtab/prerendered/locales/lij/activity-stream.html
+++ b/browser/components/newtab/prerendered/locales/lij/activity-stream.html
@@ -1,14 +1,14 @@
 <!doctype html>
 <html lang="lij" dir="ltr">
   <head>
     <meta charset="utf-8">
     <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline' resource: chrome:; connect-src https:; img-src https: data: blob:; style-src 'unsafe-inline';">
-    <title>Neuvo Feuggio</title>
+    <title>Neuvo feuggio</title>
     <link rel="icon" type="image/png" href="chrome://branding/content/icon32.png"/>
     <link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
     <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
   </head>
   <body class="activity-stream">
     <div id="root"><!-- Regular React Rendering --></div>
     <div id="snippets-container">
       <div id="snippets"></div>
--- a/browser/components/newtab/prerendered/locales/pt-BR/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/pt-BR/activity-stream-strings.js
@@ -62,23 +62,23 @@ window.gActivityStreamStrings = {
   "edit_topsites_button_text": "Editar",
   "edit_topsites_edit_button": "Editar este site",
   "topsites_form_add_header": "Novo site popular",
   "topsites_form_edit_header": "Editar site popular",
   "topsites_form_title_label": "Título",
   "topsites_form_title_placeholder": "Digite um título",
   "topsites_form_url_label": "URL",
   "topsites_form_image_url_label": "URL de imagem personalizada",
-  "topsites_form_url_placeholder": "Digite ou cole um URL",
+  "topsites_form_url_placeholder": "Digite ou cole uma URL",
   "topsites_form_use_image_link": "Usar uma imagem personalizada…",
   "topsites_form_preview_button": "Visualizar",
   "topsites_form_add_button": "Adicionar",
   "topsites_form_save_button": "Salvar",
   "topsites_form_cancel_button": "Cancelar",
-  "topsites_form_url_validation": "É necessário um URL válido",
+  "topsites_form_url_validation": "É necessário uma URL válida",
   "topsites_form_image_validation": "Não foi possível carregar a imagem. Tente uma URL diferente.",
   "pocket_read_more": "Tópicos populares:",
   "pocket_read_even_more": "Ver mais histórias",
   "pocket_more_reccommendations": "Mais recomendações",
   "pocket_how_it_works": "Como funciona",
   "pocket_cta_button": "Adicionar o Pocket",
   "pocket_cta_text": "Salve as histórias que você gosta no Pocket e abasteça sua mente com leituras fascinantes.",
   "highlights_empty_state": "Comece a navegar e mostraremos aqui alguns ótimos artigos, vídeos e outras páginas que você favoritou ou visitou recentemente.",
--- a/browser/components/newtab/test/browser/browser.ini
+++ b/browser/components/newtab/test/browser/browser.ini
@@ -1,15 +1,16 @@
 [DEFAULT]
 support-files =
   blue_page.html
   red_page.html
   head.js
 prefs =
   browser.newtabpage.activity-stream.debug=false
+  browser.newtabpage.activity-stream.discoverystream.endpoints=data:
 
 [browser_activity_stream_strings.js]
 [browser_as_load_location.js]
 [browser_as_render.js]
 [browser_asrouter_targeting.js]
 [browser_asrouter_trigger_listeners.js]
 [browser_discovery_styles.js]
 [browser_enabled_newtabpage.js]
--- a/browser/components/newtab/test/unit/asrouter/CFRPageActions.test.js
+++ b/browser/components/newtab/test/unit/asrouter/CFRPageActions.test.js
@@ -26,16 +26,17 @@ describe("CFRPageActions", () => {
     "cfr-notification-footer-text",
     "cfr-notification-footer-filled-stars",
     "cfr-notification-footer-empty-stars",
     "cfr-notification-footer-users",
     "cfr-notification-footer-spacer",
     "cfr-notification-footer-learn-more-link",
     "cfr-notification-footer-pintab-animation-container",
     "cfr-notification-footer-animation-button",
+    "cfr-notification-footer-animation-label",
   ];
   const elementClassNames = [
     "popup-notification-body-container",
   ];
 
   beforeEach(() => {
     sandbox = sinon.createSandbox();
     clock = sandbox.useFakeTimers();
--- a/browser/components/newtab/test/unit/common/Reducers.test.js
+++ b/browser/components/newtab/test/unit/common/Reducers.test.js
@@ -697,16 +697,114 @@ describe("Reducers", () => {
         loaded: true,
       });
     });
     it("should handle no data from DISCOVERY_STREAM_SPOCS_UPDATE", () => {
       const data = null;
       const state = DiscoveryStream(undefined, {type: at.DISCOVERY_STREAM_SPOCS_UPDATE, data});
       assert.deepEqual(state.spocs, INITIAL_STATE.DiscoveryStream.spocs);
     });
+    it("should not update state for empty action.data on PLACES_LINK_BLOCKED", () => {
+      const newState = DiscoveryStream(undefined, {type: at.PLACES_LINK_BLOCKED});
+      assert.equal(newState, INITIAL_STATE.DiscoveryStream);
+    });
+    it("should not update state if feeds are not loaded", () => {
+      const deleteAction = {type: at.PLACES_LINK_BLOCKED, data: {url: "foo.com"}};
+      const newState = DiscoveryStream(undefined, deleteAction);
+      assert.equal(newState, INITIAL_STATE.DiscoveryStream);
+    });
+    it("should not update state if spocs and feeds data is undefined", () => {
+      const deleteAction = {type: at.PLACES_LINK_BLOCKED, data: {url: "foo.com"}};
+      const oldState = {
+        spocs: {
+          data: {},
+          loaded: true,
+        },
+        feeds: {
+          data: {},
+          loaded: true,
+        },
+      };
+      const newState = DiscoveryStream(oldState, deleteAction);
+      assert.deepEqual(newState, oldState);
+    });
+    it("should remove the site on PLACES_LINK_BLOCKED from spocs if feeds data is empty", () => {
+      const deleteAction = {type: at.PLACES_LINK_BLOCKED, data: {url: "https://foo.com"}};
+      const oldState = {
+        spocs: {
+          data: {
+            spocs: [
+              {url: "https://foo.com"},
+              {url: "test-spoc.com"},
+            ],
+          },
+          loaded: true,
+        },
+        feeds: {
+          data: {},
+          loaded: true,
+        },
+      };
+      const newState = DiscoveryStream(oldState, deleteAction);
+      assert.deepEqual(newState.spocs.data.spocs, [{url: "test-spoc.com"}]);
+    });
+    it("should remove the site on PLACES_LINK_BLOCKED from feeds if spocs data is empty", () => {
+      const deleteAction = {type: at.PLACES_LINK_BLOCKED, data: {url: "https://foo.com"}};
+      const oldState = {
+        spocs: {
+          data: {},
+          loaded: true,
+        },
+        feeds: {
+          data: {
+            "https://foo.com/feed1": {
+              data: {
+                recommendations: [
+                  {url: "https://foo.com"},
+                  {url: "test.com"},
+                ],
+              },
+            },
+          },
+          loaded: true,
+        },
+      };
+      const newState = DiscoveryStream(oldState, deleteAction);
+      assert.deepEqual(newState.feeds.data["https://foo.com/feed1"].data.recommendations, [{url: "test.com"}]);
+    });
+    it("should remove the site on PLACES_LINK_BLOCKED from both feeds and spocs", () => {
+      const oldState = {
+        feeds: {
+          data: {
+            "https://foo.com/feed1": {
+              data: {
+                recommendations: [
+                  {url: "https://foo.com"},
+                  {url: "test.com"},
+                ],
+              },
+            },
+          },
+          loaded: true,
+        },
+        spocs: {
+          data: {
+            spocs: [
+              {url: "https://foo.com"},
+              {url: "test-spoc.com"},
+            ],
+          },
+          loaded: true,
+        },
+      };
+      const deleteAction = {type: at.PLACES_LINK_BLOCKED, data: {url: "https://foo.com"}};
+      const newState = DiscoveryStream(oldState, deleteAction);
+      assert.deepEqual(newState.spocs.data.spocs, [{url: "test-spoc.com"}]);
+      assert.deepEqual(newState.feeds.data["https://foo.com/feed1"].data.recommendations, [{url: "test.com"}]);
+    });
   });
   describe("Search", () => {
     it("should return INITIAL_STATE by default", () => {
       assert.equal(Search(undefined, {type: "some_action"}), INITIAL_STATE.Search);
     });
     it("should set hide to true on HIDE_SEARCH", () => {
       const nextState = Search(undefined, {type: "HIDE_SEARCH"});
       assert.propertyVal(nextState, "hide", true);
--- a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSLinkMenu.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSLinkMenu.test.jsx
@@ -19,21 +19,21 @@ describe("<DSLinkMenu>", () => {
   });
 
   it("should render LinkMenu when context menu button is clicked", () => {
     let button = wrapper.find(".context-menu-button");
     button.simulate("click", {preventDefault: () => {}});
     assert.equal(wrapper.find(LinkMenu).length, 1);
   });
 
-  it("should pass dispatch, onUpdate, onShow, site, options, source and index to LinkMenu", () => {
+  it("should pass dispatch, onUpdate, onShow, site, options, shouldSendImpressionStats, source and index to LinkMenu", () => {
     wrapper.find(".context-menu-button").simulate("click", {preventDefault: () => {}});
     const linkMenuProps = wrapper.find(LinkMenu).props();
-    ["dispatch", "onUpdate", "onShow", "site", "index", "options", "source"].forEach(prop => assert.property(linkMenuProps, prop));
+    ["dispatch", "onUpdate", "onShow", "site", "index", "options", "source", "shouldSendImpressionStats"].forEach(prop => assert.property(linkMenuProps, prop));
   });
 
   it("should pass through the correct menu options to LinkMenu", () => {
     wrapper.find(".context-menu-button").simulate("click", {preventDefault: () => {}});
     const linkMenuProps = wrapper.find(LinkMenu).props();
     assert.deepEqual(linkMenuProps.options,
-      ["OpenInNewWindow", "OpenInPrivateWindow"]);
+      ["OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"]);
   });
 });
--- a/browser/components/newtab/test/unit/lib/DiscoveryStreamFeed.test.js
+++ b/browser/components/newtab/test/unit/lib/DiscoveryStreamFeed.test.js
@@ -1,24 +1,30 @@
 import {actionCreators as ac, actionTypes as at, actionUtils as au} from "common/Actions.jsm";
 import {combineReducers, createStore} from "redux";
 import {DiscoveryStreamFeed} from "lib/DiscoveryStreamFeed.jsm";
+import {GlobalOverrider} from "test/unit/utils";
 import {reducers} from "common/Reducers.jsm";
 
 const CONFIG_PREF_NAME = "discoverystream.config";
+const DUMMY_ENDPOINT = "https://getpocket.cdn.mozilla.net/dummy";
+const ENDPOINTS_PREF_NAME = "discoverystream.endpoints";
 const SPOC_IMPRESSION_TRACKING_PREF = "discoverystream.spoc.impressions";
 const REC_IMPRESSION_TRACKING_PREF = "discoverystream.rec.impressions";
 const THIRTY_MINUTES = 30 * 60 * 1000;
 const ONE_WEEK = 7 * 24 * 60 * 60 * 1000; // 1 week
 
 describe("DiscoveryStreamFeed", () => {
   let feed;
   let sandbox;
   let fetchStub;
   let clock;
+  let fakeNewTabUtils;
+  let globals;
+
   const setPref = (name, value) => {
     const action = {
       type: at.PREF_CHANGED,
       data: {
         name,
         value: typeof value === "object" ? JSON.stringify(value) : value,
       },
     };
@@ -35,27 +41,82 @@ describe("DiscoveryStreamFeed", () => {
     // Time
     clock = sinon.useFakeTimers();
 
     // Feed
     feed = new DiscoveryStreamFeed();
     feed.store = createStore(combineReducers(reducers), {
       Prefs: {
         values: {
-          [CONFIG_PREF_NAME]: JSON.stringify({enabled: false, show_spocs: false, layout_endpoint: "foo"}),
+          [CONFIG_PREF_NAME]: JSON.stringify({enabled: false, show_spocs: false, layout_endpoint: DUMMY_ENDPOINT}),
+          [ENDPOINTS_PREF_NAME]: DUMMY_ENDPOINT,
         },
       },
     });
 
     sandbox.stub(feed, "_maybeUpdateCachedData").resolves();
+
+    globals = new GlobalOverrider();
+    fakeNewTabUtils = {
+      blockedLinks: {
+        links: [],
+        isBlocked: () => false,
+      },
+    };
+    globals.set("NewTabUtils", fakeNewTabUtils);
   });
 
   afterEach(() => {
     clock.restore();
     sandbox.restore();
+    globals.restore();
+  });
+
+  describe("#fetchFromEndpoint", () => {
+    beforeEach(() => {
+      fetchStub.resolves({
+        json: () => Promise.resolve("hi"),
+        ok: true,
+      });
+    });
+    it("should get a response", async () => {
+      const response = await feed.fetchFromEndpoint(DUMMY_ENDPOINT);
+
+      assert.equal(response, "hi");
+    });
+    it("should not send cookies", async () => {
+      await feed.fetchFromEndpoint(DUMMY_ENDPOINT);
+
+      assert.propertyVal(fetchStub.firstCall.args[1], "credentials", "omit");
+    });
+    it("should allow unexpected response", async () => {
+      fetchStub.resolves({ok: false});
+
+      const response = await feed.fetchFromEndpoint(DUMMY_ENDPOINT);
+
+      assert.equal(response, null);
+    });
+    it("should disallow unexpected endpoints", async () => {
+      feed.store.getState = () => ({
+        Prefs: {values: {[ENDPOINTS_PREF_NAME]: "https://other.site"}},
+      });
+
+      const response = await feed.fetchFromEndpoint(DUMMY_ENDPOINT);
+
+      assert.equal(response, null);
+    });
+    it("should allow multiple endpoints", async () => {
+      feed.store.getState = () => ({
+        Prefs: {values: {[ENDPOINTS_PREF_NAME]: `https://other.site,${DUMMY_ENDPOINT}`}},
+      });
+
+      const response = await feed.fetchFromEndpoint(DUMMY_ENDPOINT);
+
+      assert.equal(response, "hi");
+    });
   });
 
   describe("#loadLayout", () => {
     it("should fetch data and populate the cache if it is empty", async () => {
       const resp = {layout: ["foo", "bar"]};
       const fakeCache = {};
       sandbox.stub(feed.cache, "get").returns(Promise.resolve(fakeCache));
       sandbox.stub(feed.cache, "set").returns(Promise.resolve());
@@ -529,17 +590,90 @@ describe("DiscoveryStreamFeed", () => {
         {campaign_id: 1, item_score: 0.8, score: 0.8, min_score: 0.1},
         {campaign_id: 3, item_score: 0.7, score: 0.7, min_score: 0.1},
         {campaign_id: 3, item_score: 0.7, score: 0.7, min_score: 0.1},
         {campaign_id: 2, item_score: 0.6, score: 0.6, min_score: 0.1},
       ]);
     });
   });
 
-  describe("#filterSpocs", () => {
+  describe("#filterBlocked", () => {
+    it("should return initial data if spocs are empty", () => {
+      const result = feed.filterBlocked({spocs: []});
+
+      assert.equal(result.spocs.length, 0);
+    });
+    it("should return initial spocs data if links are not blocked", () => {
+      const result = feed.filterBlocked({
+        spocs: [
+          {url: "https://foo.com"},
+          {url: "test.com"},
+        ],
+      }, "spocs");
+      assert.equal(result.spocs.length, 2);
+    });
+    it("should return filtered out spocs based on blockedlist", () => {
+      fakeNewTabUtils.blockedLinks.links = [{url: "https://foo.com"}];
+      fakeNewTabUtils.blockedLinks.isBlocked = site => (fakeNewTabUtils.blockedLinks.links[0].url === site.url);
+
+      const result = feed.filterBlocked({
+        spocs: [
+          {url: "https://foo.com"},
+          {url: "test.com"},
+        ],
+      }, "spocs");
+
+      assert.lengthOf(result.spocs, 1);
+      assert.equal(result.spocs[0].url, "test.com");
+      assert.notInclude(result.spocs, fakeNewTabUtils.blockedLinks.links[0]);
+    });
+    it("should return initial recommendations data if links are not blocked", () => {
+      const result = feed.filterBlocked({
+        recommendations: [
+          {url: "https://foo.com"},
+          {url: "test.com"},
+        ],
+      }, "recommendations");
+      assert.equal(result.recommendations.length, 2);
+    });
+    it("should return filtered out recommendations based on blockedlist", () => {
+      fakeNewTabUtils.blockedLinks.links = [{url: "https://foo.com"}];
+      fakeNewTabUtils.blockedLinks.isBlocked = site => (fakeNewTabUtils.blockedLinks.links[0].url === site.url);
+
+      const result = feed.filterBlocked({
+        recommendations: [
+          {url: "https://foo.com"},
+          {url: "test.com"},
+        ],
+      }, "recommendations");
+
+      assert.lengthOf(result.recommendations, 1);
+      assert.equal(result.recommendations[0].url, "test.com");
+      assert.notInclude(result.recommendations, fakeNewTabUtils.blockedLinks.links[0]);
+    });
+    it("filterRecommendations based on blockedlist by passing feed data", () => {
+      fakeNewTabUtils.blockedLinks.links = [{url: "https://foo.com"}];
+      fakeNewTabUtils.blockedLinks.isBlocked = site => (fakeNewTabUtils.blockedLinks.links[0].url === site.url);
+
+      const result = feed.filterRecommendations({
+        data: {
+          recommendations: [
+            {url: "https://foo.com"},
+            {url: "test.com"},
+          ],
+        },
+      });
+
+      assert.lengthOf(result.data.recommendations, 1);
+      assert.equal(result.data.recommendations[0].url, "test.com");
+      assert.notInclude(result.data.recommendations, fakeNewTabUtils.blockedLinks.links[0]);
+    });
+  });
+
+  describe("#frequencyCapSpocs", () => {
     it("should return filtered out spocs based on frequency caps", () => {
       const fakeSpocs = {
         spocs: [
           {
             campaign_id: "seen",
             caps: {
               lifetime: 3,
               campaign: {
@@ -560,17 +694,17 @@ describe("DiscoveryStreamFeed", () => {
           },
         ],
       };
       const fakeImpressions = {
         "seen": [Date.now() - 1],
       };
       sandbox.stub(feed, "readImpressionsPref").returns(fakeImpressions);
 
-      const result = feed.filterSpocs(fakeSpocs);
+      const result = feed.frequencyCapSpocs(fakeSpocs);
 
       assert.equal(result.spocs.length, 1);
       assert.equal(result.spocs[0].campaign_id, "not-seen");
     });
   });
 
   describe("#isBelowFrequencyCap", () => {
     it("should return true if there are no campaign impressions", () => {
--- a/browser/components/preferences/in-content/tests/browser_extension_controlled.js
+++ b/browser/components/preferences/in-content/tests/browser_extension_controlled.js
@@ -4,16 +4,19 @@ const PROXY_PREF = "network.proxy.type";
 
 ChromeUtils.defineModuleGetter(this, "ExtensionSettingsStore",
                                "resource://gre/modules/ExtensionSettingsStore.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 XPCOMUtils.defineLazyPreferenceGetter(this, "proxyType", PROXY_PREF);
 
+const {AddonTestUtils} = ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm");
+AddonTestUtils.initMochitest(this);
+
 const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 const CHROME_URL_ROOT = TEST_DIR + "/";
 const PERMISSIONS_URL = "chrome://browser/content/preferences/sitePermissions.xul";
 let sitePermissionsDialog;
 
 function getSupportsFile(path) {
   let cr = Cc["@mozilla.org/chrome/chrome-registry;1"]
     .getService(Ci.nsIChromeRegistry);
@@ -463,16 +466,17 @@ add_task(async function testExtensionCon
   // Install an extension that will set the default search engine.
   let originalExtension = ExtensionTestUtils.loadExtension({
     useAddonManager: "permanent",
     manifest: Object.assign({}, manifest, {version: "1.0"}),
   });
 
   let messageShown = waitForMessageShown("browserDefaultSearchExtensionContent");
   await originalExtension.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(originalExtension);
   await messageShown;
 
   let addon = await AddonManager.getAddonByID(extensionId);
   is(addon.version, "1.0", "The addon has the expected version.");
 
   // The default search engine has been set by the extension and the user is notified.
   let controlledLabel = controlledContent.querySelector("description");
   let extensionEngine = Services.search.defaultEngine;
@@ -507,16 +511,17 @@ add_task(async function testExtensionCon
   await waitForMessageHidden(controlledContent.id);
 
   // Update the extension and wait for "ready".
   let updatedExtension = ExtensionTestUtils.loadExtension({
     useAddonManager: "permanent",
     manifest: Object.assign({}, manifest, {version: "2.0"}),
   });
   await updatedExtension.startup();
+  await AddonTestUtils.waitForSearchProviderStartup(updatedExtension);
   addon = await AddonManager.getAddonByID(extensionId);
 
   // Verify the extension is updated and search engine didn't change.
   is(addon.version, "2.0", "The updated addon has the expected version");
   is(controlledContent.hidden, true, "The extension controlled row is hidden after update");
   is(initialEngine, Services.search.defaultEngine,
      "default search engine is still the initial engine after update");
 
--- a/browser/modules/ContentClick.jsm
+++ b/browser/modules/ContentClick.jsm
@@ -66,19 +66,17 @@ var ContentClick = {
     var where = window.whereToOpenLink(json);
     if (where == "current")
       return;
 
     // Todo(903022): code for where == save
 
     let params = {
       charset: browser.characterSet,
-      referrerURI: browser.documentURI,
-      referrerPolicy: json.referrerPolicy,
-      noReferrer: json.noReferrer,
+      referrerInfo: E10SUtils.deserializeReferrerInfo(json.referrerInfo),
       allowMixedContent: json.allowMixedContent,
       isContentWindowPrivate: json.isContentWindowPrivate,
       originPrincipal: json.originPrincipal,
       triggeringPrincipal: json.triggeringPrincipal,
       csp: json.csp ? E10SUtils.deserializeCSP(json.csp) : null,
       frameOuterWindowID: json.frameOuterWindowID,
     };
 
--- a/build/autoconf/clang-plugin.m4
+++ b/build/autoconf/clang-plugin.m4
@@ -31,17 +31,21 @@ if test -n "$ENABLE_CLANG_PLUGIN"; then
     AC_MSG_RESULT([$LLVMCONFIG])
 
     if test -z "$LLVMCONFIG"; then
         AC_MSG_ERROR([Cannot find an llvm-config binary for building a clang plugin])
     fi
     dnl For some reason the llvm-config downloaded from clang.llvm.org for clang3_8
     dnl produces a -isysroot flag for a sysroot which might not ship when passed
     dnl --cxxflags. We use sed to remove this argument so that builds work on OSX
-    LLVM_CXXFLAGS=`$LLVMCONFIG --cxxflags | sed -e 's/-isysroot [[^ ]]*//'`
+    dnl
+    dnl For a similar reason, we remove any -gcc-toolchain arguments, since the
+    dnl directories specified by such arguments might not exist on the current
+    dnl machine.
+    LLVM_CXXFLAGS=`$LLVMCONFIG --cxxflags | sed -e 's/-isysroot [[^ ]]*//' -e 's/-gcc-toolchain [[^ ]]*//'`
 
     LLVM_LDFLAGS=`$LLVMCONFIG --ldflags | tr '\n' ' '`
 
     if test "${HOST_OS_ARCH}" = "Darwin"; then
         dnl We need to make sure that we use the symbols coming from the clang
         dnl binary. In order to do this, we need to pass -flat_namespace and
         dnl -undefined suppress to the linker. This makes sure that we link the
         dnl symbols into the flat namespace provided by clang, and thus get
--- a/build/build-clang/build-clang.py
+++ b/build/build-clang/build-clang.py
@@ -115,18 +115,33 @@ def delete(path):
         shutil.rmtree(path)
     else:
         try:
             os.unlink(path)
         except Exception:
             pass
 
 
-def install_libgcc(gcc_dir, clang_dir):
-    out = subprocess.check_output([os.path.join(gcc_dir, "bin", "gcc"),
+def install_libgcc(gcc_dir, clang_dir, is_final_stage):
+    gcc_bin_dir = os.path.join(gcc_dir, 'bin')
+
+    # Copy over gcc toolchain bits that clang looks for, to ensure that
+    # clang is using a consistent version of ld, since the system ld may
+    # be incompatible with the output clang produces.  But copy it to a
+    # target-specific directory so a cross-compiler to Mac doesn't pick
+    # up the (Linux-specific) ld with disastrous results.
+    #
+    # Only install this for the bootstrap process; we expect any consumers of
+    # the newly-built toolchain to provide an appropriate ld themselves.
+    if not is_final_stage:
+        x64_bin_dir = os.path.join(clang_dir, 'x86_64-unknown-linux-gnu', 'bin')
+        mkdir_p(x64_bin_dir)
+        shutil.copy2(os.path.join(gcc_bin_dir, 'ld'), x64_bin_dir)
+
+    out = subprocess.check_output([os.path.join(gcc_bin_dir, "gcc"),
                                    '-print-libgcc-file-name'])
 
     libgcc_dir = os.path.dirname(out.rstrip())
     clang_lib_dir = os.path.join(clang_dir, "lib", "gcc",
                                  "x86_64-unknown-linux-gnu",
                                  os.path.basename(libgcc_dir))
     mkdir_p(clang_lib_dir)
     copy_tree(libgcc_dir, clang_lib_dir)
@@ -304,17 +319,17 @@ def build_one_stage(cc, cxx, asm, ld, ar
     cmake_args += cmake_base_args(
         cc, cxx, asm, ld, ar, ranlib, libtool, inst_dir)
     cmake_args += [
         src_dir
     ]
     build_package(build_dir, cmake_args)
 
     if is_linux():
-        install_libgcc(gcc_dir, inst_dir)
+        install_libgcc(gcc_dir, inst_dir, is_final_stage)
     # For some reasons the import library clang.lib of clang.exe is not
     # installed, so we copy it by ourselves.
     if is_windows():
         # The compiler-rt cmake scripts don't allow to build it for multiple
         # targets at once on Windows, so manually build the 32-bits compiler-rt
         # during the final stage.
         build_32_bit = False
         if is_final_stage:
@@ -400,33 +415,39 @@ def get_tool(config, key):
 #           * (nothing will be deleted here)
 #   share/
 #     clang/
 #       clang-format-diff.py
 #       clang-tidy-diff.py
 #       run-clang-tidy.py
 def prune_final_dir_for_clang_tidy(final_dir, osx_cross_compile):
     # Make sure we only have what we expect.
-    dirs = ("bin", "include", "lib", "lib32", "libexec", "msbuild-bin", "share", "tools")
+    dirs = ["bin", "include", "lib", "lib32", "libexec", "msbuild-bin", "share", "tools"]
+    if is_linux():
+        dirs.append("x86_64-unknown-linux-gnu")
     for f in glob.glob("%s/*" % final_dir):
         if os.path.basename(f) not in dirs:
             raise Exception("Found unknown file %s in the final directory" % f)
         if not os.path.isdir(f):
             raise Exception("Expected %s to be a directory" % f)
 
     # In bin/, only keep clang-tidy and clang-apply-replacements. The last one
     # is used to auto-fix some of the issues detected by clang-tidy.
     re_clang_tidy = re.compile(
         r"^clang-(apply-replacements|format|tidy)(\.exe)?$", re.I)
     for f in glob.glob("%s/bin/*" % final_dir):
         if re_clang_tidy.search(os.path.basename(f)) is None:
             delete(f)
 
     # Keep include/ intact.
 
+    # Remove the target-specific files.
+    if is_linux():
+        shutil.rmtree(os.path.join(final_dir, "x86_64-unknown-linux-gnu"))
+
     # In lib/, only keep lib/clang/N.M.O/include and the LLVM shared library.
     re_ver_num = re.compile(r"^\d+\.\d+\.\d+$", re.I)
     for f in glob.glob("%s/lib/*" % final_dir):
         name = os.path.basename(f)
         if name == "clang":
             continue
         if osx_cross_compile and name == 'libLLVM.dylib':
             continue
@@ -671,19 +692,23 @@ if __name__ == "__main__":
         extra_cxxflags = ["-stdlib=libc++"]
         extra_cflags2 = []
         extra_cxxflags2 = ["-stdlib=libc++"]
         extra_asmflags = []
         extra_ldflags = []
     elif is_linux():
         extra_cflags = []
         extra_cxxflags = []
-        extra_cflags2 = ["-fPIC"]
+        # When building stage2 and stage3, we want the newly-built clang to pick
+        # up whatever headers were installed from the gcc we used to build stage1,
+        # always, rather than the system headers.  Providing -gcc-toolchain
+        # encourages clang to do that.
+        extra_cflags2 = ["-fPIC", '-gcc-toolchain', stage1_inst_dir]
         # Silence clang's warnings about arguments not being used in compilation.
-        extra_cxxflags2 = ["-fPIC", '-Qunused-arguments']
+        extra_cxxflags2 = ["-fPIC", '-Qunused-arguments', '-gcc-toolchain', stage1_inst_dir]
         extra_asmflags = []
         # Avoid libLLVM internal function calls going through the PLT.
         extra_ldflags = ['-Wl,-Bsymbolic-functions']
 
         if 'LD_LIBRARY_PATH' in os.environ:
             os.environ['LD_LIBRARY_PATH'] = ('%s/lib64/:%s' %
                                              (gcc_dir, os.environ['LD_LIBRARY_PATH']))
         else:
--- a/build/unix/build-gcc/build-gcc.sh
+++ b/build/unix/build-gcc/build-gcc.sh
@@ -34,32 +34,39 @@ build_binutils() {
   # if binutils_configure_flags is not set at all, give it the default value
   if [ -z "${binutils_configure_flags+xxx}" ];
   then
     # gold is disabled because we don't use it on automation, and also we ran into
     # some issues with it using this script in build-clang.py.
     #
     # --enable-targets builds extra target support in ld.
     # Enabling aarch64 support brings in arm support, so we don't need to specify that too.
-    binutils_configure_flags="--enable-targets=aarch64-unknown-linux-gnu --disable-gold --enable-plugins --disable-nls --with-sysroot=/"
+    #
+    # It is important to have the binutils --target and the gcc --target match,
+    # so binutils will install binaries in a place that gcc will look for them.
+    binutils_configure_flags="--enable-targets=aarch64-unknown-linux-gnu --build=x86_64-unknown-linux-gnu --target=x86_64-unknown-linux-gnu --disable-gold --enable-plugins --disable-nls --with-sysroot=/"
   fi
 
   mkdir $root_dir/binutils-objdir
   pushd $root_dir/binutils-objdir
   ../binutils-$binutils_version/configure --prefix=${prefix-/tools/gcc}/ $binutils_configure_flags
   make $make_flags
   make install $make_flags DESTDIR=$root_dir
   export PATH=$root_dir/${prefix-/tools/gcc}/bin:$PATH
   popd
 }
 
 build_gcc() {
+  # Be explicit about --build and --target so header and library install
+  # directories are consistent.
+  local target="${1:-x86_64-unknown-linux-gnu}"
+
   mkdir $root_dir/gcc-objdir
   pushd $root_dir/gcc-objdir
-  ../gcc-$gcc_version/configure --prefix=${prefix-/tools/gcc} --enable-languages=c,c++  --disable-nls --disable-gnu-unique-object --enable-__cxa_atexit --with-arch-32=pentiumpro --with-sysroot=/
+  ../gcc-$gcc_version/configure --prefix=${prefix-/tools/gcc} --build=x86_64-unknown-linux-gnu --target="${target}" --enable-languages=c,c++  --disable-nls --disable-gnu-unique-object --enable-__cxa_atexit --with-arch-32=pentiumpro --with-sysroot=/
   make $make_flags
   make $make_flags install DESTDIR=$root_dir
 
   cd $root_dir/tools
   ln -s gcc gcc/bin/cc
 
   tar caf $root_dir/gcc.tar.xz gcc/
   popd
--- a/devtools/client/responsive.html/components/App.js
+++ b/devtools/client/responsive.html/components/App.js
@@ -54,16 +54,17 @@ class App extends PureComponent {
       viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.onAddCustomDevice = this.onAddCustomDevice.bind(this);
+    this.onBrowserContextMenu = this.onBrowserContextMenu.bind(this);
     this.onBrowserMounted = this.onBrowserMounted.bind(this);
     this.onChangeDevice = this.onChangeDevice.bind(this);
     this.onChangeNetworkThrottling = this.onChangeNetworkThrottling.bind(this);
     this.onChangePixelRatio = this.onChangePixelRatio.bind(this);
     this.onChangeTouchSimulation = this.onChangeTouchSimulation.bind(this);
     this.onChangeUserAgent = this.onChangeUserAgent.bind(this);
     this.onContentResize = this.onContentResize.bind(this);
     this.onDeviceListUpdate = this.onDeviceListUpdate.bind(this);
@@ -77,21 +78,34 @@ class App extends PureComponent {
     this.onToggleReloadOnTouchSimulation =
       this.onToggleReloadOnTouchSimulation.bind(this);
     this.onToggleReloadOnUserAgent = this.onToggleReloadOnUserAgent.bind(this);
     this.onToggleUserAgentInput = this.onToggleUserAgentInput.bind(this);
     this.onUpdateDeviceDisplayed = this.onUpdateDeviceDisplayed.bind(this);
     this.onUpdateDeviceModal = this.onUpdateDeviceModal.bind(this);
   }
 
+  componentWillUnmount() {
+    this.browser.removeEventListener("contextmenu", this.onContextMenu);
+    this.browser = null;
+  }
+
   onAddCustomDevice(device) {
     this.props.dispatch(addCustomDevice(device));
   }
 
-  onBrowserMounted() {
+  onBrowserContextMenu() {
+    // Update the position of remote browser so that makes the context menu to show at
+    // proper position before showing.
+    this.browser.frameLoader.requestUpdatePosition();
+  }
+
+  onBrowserMounted(browser) {
+    this.browser = browser;
+    this.browser.addEventListener("contextmenu", this.onBrowserContextMenu);
     window.postMessage({ type: "browser-mounted" }, "*");
   }
 
   onChangeDevice(id, device, deviceType) {
     // TODO: Bug 1332754: Move messaging and logic into the action creator so that the
     // message is sent from the action creator and device property changes are sent from
     // there instead of this function.
     window.postMessage({
--- a/devtools/client/responsive.html/components/Browser.js
+++ b/devtools/client/responsive.html/components/Browser.js
@@ -67,17 +67,17 @@ class Browser extends PureComponent {
     // script now.
     if (!this.props.swapAfterMount) {
       await this.startFrameScript();
     }
 
     // Notify manager.js that this browser has mounted, so that it can trigger
     // a swap if needed and continue with the rest of its startup.
     await this.browserShown;
-    this.props.onBrowserMounted();
+    this.props.onBrowserMounted(this.browser);
 
     // If we are swapping browsers after mount, wait for the swap to complete
     // and start the frame script after that.
     if (this.props.swapAfterMount) {
       await message.wait(window, "start-frame-script");
       await this.startFrameScript();
       message.post(window, "start-frame-script:done");
     }
--- a/docshell/base/nsDocShellLoadState.cpp
+++ b/docshell/base/nsDocShellLoadState.cpp
@@ -49,19 +49,17 @@ nsDocShellLoadState::nsDocShellLoadState
   mLoadType = aLoadState.LoadType();
   mTarget = aLoadState.Target();
   mLoadFlags = aLoadState.LoadFlags();
   mFirstParty = aLoadState.FirstParty();
   mTypeHint = aLoadState.TypeHint();
   mFileName = aLoadState.FileName();
   mIsFromProcessingFrameAttributes =
       aLoadState.IsFromProcessingFrameAttributes();
-  mReferrerInfo =
-      new ReferrerInfo(aLoadState.Referrer(), aLoadState.ReferrerPolicy(),
-                       aLoadState.SendReferrer());
+  mReferrerInfo = aLoadState.ReferrerInfo();
   mURI = aLoadState.URI();
   mOriginalURI = aLoadState.OriginalURI();
   mBaseURI = aLoadState.BaseURI();
   mTriggeringPrincipal = aLoadState.TriggeringPrincipal();
   mPrincipalToInherit = aLoadState.PrincipalToInherit();
   mCsp = aLoadState.Csp();
 }
 
@@ -462,13 +460,11 @@ DocShellLoadStateInit nsDocShellLoadStat
   loadState.IsFromProcessingFrameAttributes() =
       mIsFromProcessingFrameAttributes;
   loadState.URI() = mURI;
   loadState.OriginalURI() = mOriginalURI;
   loadState.BaseURI() = mBaseURI;
   loadState.TriggeringPrincipal() = mTriggeringPrincipal;
   loadState.PrincipalToInherit() = mPrincipalToInherit;
   loadState.Csp() = mCsp;
-  loadState.Referrer() = mReferrerInfo->GetOriginalReferrer();
-  loadState.SendReferrer() = mReferrerInfo->GetSendReferrer();
-  loadState.ReferrerPolicy() = mReferrerInfo->GetReferrerPolicy();
+  loadState.ReferrerInfo() = mReferrerInfo;
   return loadState;
 }
--- a/dom/base/nsOpenURIInFrameParams.cpp
+++ b/dom/base/nsOpenURIInFrameParams.cpp
@@ -16,43 +16,29 @@ NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION(nsOpenURIInFrameParams, mOpenerBrowser)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsOpenURIInFrameParams)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsOpenURIInFrameParams)
 
 nsOpenURIInFrameParams::nsOpenURIInFrameParams(
     const mozilla::OriginAttributes& aOriginAttributes, Element* aOpener)
-    : mOpenerOriginAttributes(aOriginAttributes),
-      mOpenerBrowser(aOpener),
-      mReferrerPolicy(mozilla::net::RP_Unset) {}
+    : mOpenerOriginAttributes(aOriginAttributes), mOpenerBrowser(aOpener) {}
 
 nsOpenURIInFrameParams::~nsOpenURIInFrameParams() {}
 
 NS_IMETHODIMP
-nsOpenURIInFrameParams::GetReferrer(nsAString& aReferrer) {
-  aReferrer = mReferrer;
+nsOpenURIInFrameParams::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
+  NS_IF_ADDREF(*aReferrerInfo = mReferrerInfo);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsOpenURIInFrameParams::SetReferrer(const nsAString& aReferrer) {
-  mReferrer = aReferrer;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsOpenURIInFrameParams::GetReferrerPolicy(uint32_t* aReferrerPolicy) {
-  *aReferrerPolicy = mReferrerPolicy;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsOpenURIInFrameParams::SetReferrerPolicy(uint32_t aReferrerPolicy) {
-  mReferrerPolicy = aReferrerPolicy;
+nsOpenURIInFrameParams::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+  mReferrerInfo = aReferrerInfo;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsOpenURIInFrameParams::GetIsPrivate(bool* aIsPrivate) {
   NS_ENSURE_ARG_POINTER(aIsPrivate);
   *aIsPrivate = mOpenerOriginAttributes.mPrivateBrowsingId > 0;
   return NS_OK;
--- a/dom/base/nsOpenURIInFrameParams.h
+++ b/dom/base/nsOpenURIInFrameParams.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/BasePrincipal.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIBrowserDOMWindow.h"
 #include "nsFrameLoaderOwner.h"
+#include "nsIReferrerInfo.h"
 #include "nsString.h"
 
 namespace mozilla {
 class OriginAttributes;
 }
 
 class nsOpenURIInFrameParams final : public nsIOpenURIInFrameParams {
  public:
@@ -23,13 +24,12 @@ class nsOpenURIInFrameParams final : pub
   explicit nsOpenURIInFrameParams(
       const mozilla::OriginAttributes& aOriginAttributes, Element* aOpener);
 
  private:
   ~nsOpenURIInFrameParams();
 
   mozilla::OriginAttributes mOpenerOriginAttributes;
   RefPtr<Element> mOpenerBrowser;
-  nsString mReferrer;
-  uint32_t mReferrerPolicy;
+  nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
   nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
   nsCOMPtr<nsIContentSecurityPolicy> mCsp;
 };
--- a/dom/html/TextTrackManager.cpp
+++ b/dom/html/TextTrackManager.cpp
@@ -16,20 +16,22 @@
 #include "mozilla/dom/TextTrackCue.h"
 #include "nsComponentManagerUtils.h"
 #include "nsGlobalWindow.h"
 #include "nsIFrame.h"
 #include "nsIWebVTTParserWrapper.h"
 #include "nsVariant.h"
 #include "nsVideoFrame.h"
 
-static mozilla::LazyLogModule gTextTrackLog("TextTrackManager");
-#define WEBVTT_LOG(...) MOZ_LOG(gTextTrackLog, LogLevel::Debug, (__VA_ARGS__))
-#define WEBVTT_LOGV(...) \
-  MOZ_LOG(gTextTrackLog, LogLevel::Verbose, (__VA_ARGS__))
+mozilla::LazyLogModule gTextTrackLog("WebVTT");
+
+#define WEBVTT_LOG(msg, ...) \
+  MOZ_LOG(gTextTrackLog, LogLevel::Debug, ("TextTrackManager=%p, " msg, this, ##__VA_ARGS__))
+#define WEBVTT_LOGV(msg, ...) \
+  MOZ_LOG(gTextTrackLog, LogLevel::Verbose, ("TextTrackManager=%p, " msg, this, ##__VA_ARGS__))
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_ISUPPORTS(TextTrackManager::ShutdownObserverProxy, nsIObserver);
 
 void TextTrackManager::ShutdownObserverProxy::Unregister() {
   nsContentUtils::UnregisterShutdownObserver(this);
@@ -113,17 +115,17 @@ TextTrackManager::TextTrackManager(HTMLM
       mTimeMarchesOnDispatched(false),
       mUpdateCueDisplayDispatched(false),
       performedTrackSelection(false),
       mCueTelemetryReported(false),
       mShutdown(false) {
   nsISupports* parentObject = mMediaElement->OwnerDoc()->GetParentObject();
 
   NS_ENSURE_TRUE_VOID(parentObject);
-  WEBVTT_LOG("%p Create TextTrackManager", this);
+  WEBVTT_LOG("Create TextTrackManager");
   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject);
   mNewCues = new TextTrackCueList(window);
   mLastActiveCues = new TextTrackCueList(window);
   mTextTracks = new TextTrackList(window, this);
   mPendingTextTracks = new TextTrackList(window, this);
 
   if (!sParserWrapper) {
     nsCOMPtr<nsIWebVTTParserWrapper> parserWrapper =
@@ -131,36 +133,35 @@ TextTrackManager::TextTrackManager(HTMLM
     MOZ_ASSERT(parserWrapper, "Can't create nsIWebVTTParserWrapper");
     sParserWrapper = parserWrapper;
     ClearOnShutdown(&sParserWrapper);
   }
   mShutdownProxy = new ShutdownObserverProxy(this);
 }
 
 TextTrackManager::~TextTrackManager() {
-  WEBVTT_LOG("%p ~TextTrackManager", this);
+  WEBVTT_LOG("~TextTrackManager");
   mShutdownProxy->Unregister();
 }
 
 TextTrackList* TextTrackManager::GetTextTracks() const { return mTextTracks; }
 
 already_AddRefed<TextTrack> TextTrackManager::AddTextTrack(
     TextTrackKind aKind, const nsAString& aLabel, const nsAString& aLanguage,
     TextTrackMode aMode, TextTrackReadyState aReadyState,
     TextTrackSource aTextTrackSource) {
   if (!mMediaElement || !mTextTracks) {
     return nullptr;
   }
-  WEBVTT_LOG("%p AddTextTrack", this);
-  WEBVTT_LOGV("AddTextTrack kind %" PRIu32 " Label %s Language %s",
-              static_cast<uint32_t>(aKind), NS_ConvertUTF16toUTF8(aLabel).get(),
-              NS_ConvertUTF16toUTF8(aLanguage).get());
   RefPtr<TextTrack> track = mTextTracks->AddTextTrack(
       aKind, aLabel, aLanguage, aMode, aReadyState, aTextTrackSource,
       CompareTextTracks(mMediaElement));
+  WEBVTT_LOG("AddTextTrack %p kind %" PRIu32 " Label %s Language %s",
+             track.get(), static_cast<uint32_t>(aKind), NS_ConvertUTF16toUTF8(aLabel).get(),
+             NS_ConvertUTF16toUTF8(aLanguage).get());
   AddCues(track);
   ReportTelemetryForTrack(track);
 
   if (aTextTrackSource == TextTrackSource::Track) {
     RefPtr<nsIRunnable> task = NewRunnableMethod(
         "dom::TextTrackManager::HonorUserPreferencesForTrackSelection", this,
         &TextTrackManager::HonorUserPreferencesForTrackSelection);
     nsContentUtils::RunInStableState(task.forget());
@@ -168,17 +169,17 @@ already_AddRefed<TextTrack> TextTrackMan
 
   return track.forget();
 }
 
 void TextTrackManager::AddTextTrack(TextTrack* aTextTrack) {
   if (!mMediaElement || !mTextTracks) {
     return;
   }
-  WEBVTT_LOG("%p AddTextTrack TextTrack %p", this, aTextTrack);
+  WEBVTT_LOG("AddTextTrack TextTrack %p", aTextTrack);
   mTextTracks->AddTextTrack(aTextTrack, CompareTextTracks(mMediaElement));
   AddCues(aTextTrack);
   ReportTelemetryForTrack(aTextTrack);
 
   if (aTextTrack->GetTextTrackSource() == TextTrackSource::Track) {
     RefPtr<nsIRunnable> task = NewRunnableMethod(
         "dom::TextTrackManager::HonorUserPreferencesForTrackSelection", this,
         &TextTrackManager::HonorUserPreferencesForTrackSelection);
@@ -190,51 +191,51 @@ void TextTrackManager::AddCues(TextTrack
   if (!mNewCues) {
     WEBVTT_LOG("AddCues mNewCues is null");
     return;
   }
 
   TextTrackCueList* cueList = aTextTrack->GetCues();
   if (cueList) {
     bool dummy;
-    WEBVTT_LOGV("AddCues cueList->Length() %d", cueList->Length());
+    WEBVTT_LOGV("AddCues, CuesNum=%d", cueList->Length());
     for (uint32_t i = 0; i < cueList->Length(); ++i) {
       mNewCues->AddCue(*cueList->IndexedGetter(i, dummy));
     }
     TimeMarchesOn();
   }
 }
 
 void TextTrackManager::RemoveTextTrack(TextTrack* aTextTrack,
                                        bool aPendingListOnly) {
   if (!mPendingTextTracks || !mTextTracks) {
     return;
   }
 
-  WEBVTT_LOG("%p RemoveTextTrack TextTrack %p", this, aTextTrack);
+  WEBVTT_LOG("RemoveTextTrack TextTrack %p", aTextTrack);
   mPendingTextTracks->RemoveTextTrack(aTextTrack);
   if (aPendingListOnly) {
     return;
   }
 
   mTextTracks->RemoveTextTrack(aTextTrack);
   // Remove the cues in mNewCues belong to aTextTrack.
   TextTrackCueList* removeCueList = aTextTrack->GetCues();
   if (removeCueList) {
-    WEBVTT_LOGV("RemoveTextTrack removeCueList->Length() %d",
+    WEBVTT_LOGV("RemoveTextTrack removeCuesNum=%d",
                 removeCueList->Length());
     for (uint32_t i = 0; i < removeCueList->Length(); ++i) {
       mNewCues->RemoveCue(*((*removeCueList)[i]));
     }
     TimeMarchesOn();
   }
 }
 
 void TextTrackManager::DidSeek() {
-  WEBVTT_LOG("%p DidSeek", this);
+  WEBVTT_LOG("DidSeek");
   if (mMediaElement) {
     mLastTimeMarchesOnCalled = mMediaElement->CurrentTime();
     WEBVTT_LOGV("DidSeek set mLastTimeMarchesOnCalled %lf",
                 mLastTimeMarchesOnCalled);
   }
   mHasSeeked = true;
 }
 
@@ -257,45 +258,44 @@ void TextTrackManager::UpdateCueDisplay(
   if (!overlay) {
     return;
   }
 
   nsTArray<RefPtr<TextTrackCue>> showingCues;
   mTextTracks->GetShowingCues(showingCues);
 
   if (showingCues.Length() > 0) {
-    WEBVTT_LOG("UpdateCueDisplay ProcessCues");
-    WEBVTT_LOGV("UpdateCueDisplay showingCues.Length() %zu",
-                showingCues.Length());
+    WEBVTT_LOG("UpdateCueDisplay, processCues, showingCuesNum=%zu",
+               showingCues.Length());
     RefPtr<nsVariantCC> jsCues = new nsVariantCC();
 
     jsCues->SetAsArray(nsIDataType::VTYPE_INTERFACE, &NS_GET_IID(EventTarget),
                        showingCues.Length(),
                        static_cast<void*>(showingCues.Elements()));
     nsPIDOMWindowInner* window = mMediaElement->OwnerDoc()->GetInnerWindow();
     if (window) {
       sParserWrapper->ProcessCues(window, jsCues, overlay, controls);
     }
   } else if (overlay->Length() > 0) {
     WEBVTT_LOG("UpdateCueDisplay EmptyString");
     nsContentUtils::SetNodeTextContent(overlay, EmptyString(), true);
   }
 }
 
 void TextTrackManager::NotifyCueAdded(TextTrackCue& aCue) {
-  WEBVTT_LOG("NotifyCueAdded");
+  WEBVTT_LOG("NotifyCueAdded, cue=%p", &aCue);
   if (mNewCues) {
     mNewCues->AddCue(aCue);
   }
   TimeMarchesOn();
   ReportTelemetryForCue();
 }
 
 void TextTrackManager::NotifyCueRemoved(TextTrackCue& aCue) {
-  WEBVTT_LOG("NotifyCueRemoved");
+  WEBVTT_LOG("NotifyCueRemoved, cue=%p", &aCue);
   if (mNewCues) {
     mNewCues->RemoveCue(aCue);
   }
   TimeMarchesOn();
   DispatchUpdateCueDisplay();
 }
 
 void TextTrackManager::PopulatePendingList() {
@@ -815,17 +815,17 @@ void TextTrackManager::TimeMarchesOn() {
   mLastActiveCues = currentCues;
 
   // Step 18.
   UpdateCueDisplay();
 }
 
 void TextTrackManager::NotifyCueUpdated(TextTrackCue* aCue) {
   // TODO: Add/Reorder the cue to mNewCues if we have some optimization?
-  WEBVTT_LOG("NotifyCueUpdated");
+  WEBVTT_LOG("NotifyCueUpdated, cue=%p", aCue);
   TimeMarchesOn();
   // For the case "Texttrack.mode = hidden/showing", if the mode
   // changing between showing and hidden, TimeMarchesOn
   // doesn't render the cue. Call DispatchUpdateCueDisplay() explicitly.
   DispatchUpdateCueDisplay();
 }
 
 void TextTrackManager::NotifyReset() {
--- a/dom/interfaces/base/nsIBrowserDOMWindow.idl
+++ b/dom/interfaces/base/nsIBrowserDOMWindow.idl
@@ -5,23 +5,23 @@
 
 #include "nsISupports.idl"
 
 interface mozIDOMWindowProxy;
 interface nsIDOMWindow;
 interface nsIURI;
 interface nsIPrincipal;
 interface nsIContentSecurityPolicy;
+interface nsIReferrerInfo;
 webidl Element;
 
 [scriptable, uuid(e774db14-79ac-4156-a7a3-aa3fd0a22c10)]
 interface nsIOpenURIInFrameParams : nsISupports
 {
-  attribute AString referrer;
-  attribute unsigned long referrerPolicy;
+  attribute nsIReferrerInfo referrerInfo;
   readonly attribute boolean isPrivate;
   attribute nsIPrincipal triggeringPrincipal;
   attribute nsIContentSecurityPolicy csp;
 
   // The browser or frame element in the parent process which holds the
   // opener window in the content process. May be null.
   readonly attribute Element openerBrowser;
 
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -162,17 +162,17 @@
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsContentPermissionHelper.h"
 #include "nsPluginHost.h"
 #ifdef NS_PRINTING
 #  include "nsPrintingProxy.h"
 #endif
 #include "nsWindowMemoryReporter.h"
-#include "nsIReferrerInfo.h"
+#include "ReferrerInfo.h"
 
 #include "IHistory.h"
 #include "nsNetUtil.h"
 
 #include "base/message_loop.h"
 #include "base/process_util.h"
 #include "base/task.h"
 
@@ -753,29 +753,48 @@ ContentChild::ProvideWindow(mozIDOMWindo
                             nsDocShellLoadState* aLoadState, bool* aWindowIsNew,
                             mozIDOMWindowProxy** aReturn) {
   return ProvideWindowCommon(nullptr, aParent, false, aChromeFlags,
                              aCalledFromJS, aPositionSpecified, aSizeSpecified,
                              aURI, aName, aFeatures, aForceNoOpener, aLoadState,
                              aWindowIsNew, aReturn);
 }
 
-static nsresult GetCreateWindowParams(
-    mozIDOMWindowProxy* aParent, nsDocShellLoadState* aLoadState,
-    nsACString& aBaseURIString, float* aFullZoom, uint32_t* aReferrerPolicy,
-    nsIPrincipal** aTriggeringPrincipal, nsIContentSecurityPolicy** aCsp) {
+static nsresult GetCreateWindowParams(mozIDOMWindowProxy* aParent,
+                                      nsDocShellLoadState* aLoadState,
+                                      float* aFullZoom,
+                                      nsIReferrerInfo** aReferrerInfo,
+                                      nsIPrincipal** aTriggeringPrincipal,
+                                      nsIContentSecurityPolicy** aCsp) {
   *aFullZoom = 1.0f;
   if (!aTriggeringPrincipal || !aCsp) {
     NS_ERROR("aTriggeringPrincipal || aCsp is null");
     return NS_ERROR_FAILURE;
   }
+
+  if (!aReferrerInfo) {
+    NS_ERROR("aReferrerInfo is null");
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIReferrerInfo> referrerInfo;
+  if (aLoadState) {
+    referrerInfo = aLoadState->GetReferrerInfo();
+  }
+
   auto* opener = nsPIDOMWindowOuter::From(aParent);
   if (!opener) {
     nsCOMPtr<nsIPrincipal> nullPrincipal =
         NullPrincipal::CreateWithoutOriginAttributes();
+    if (!referrerInfo) {
+      referrerInfo =
+          new ReferrerInfo(nullptr, mozilla::net::ReferrerPolicy::RP_Unset);
+    }
+
+    referrerInfo.swap(*aReferrerInfo);
     NS_ADDREF(*aTriggeringPrincipal = nullPrincipal);
     return NS_OK;
   }
 
   nsCOMPtr<Document> doc = opener->GetDoc();
   NS_ADDREF(*aTriggeringPrincipal = doc->NodePrincipal());
 
   // Currently we query the CSP from the doc->NodePrincipal(). After
@@ -787,26 +806,23 @@ static nsresult GetCreateWindowParams(
   }
 
   nsCOMPtr<nsIURI> baseURI = doc->GetDocBaseURI();
   if (!baseURI) {
     NS_ERROR("Document didn't return a base URI");
     return NS_ERROR_FAILURE;
   }
 
-  baseURI->GetSpec(aBaseURIString);
-  if (aLoadState) {
-    nsCOMPtr<nsIReferrerInfo> referrerInfo = aLoadState->GetReferrerInfo();
-    if (referrerInfo && referrerInfo->GetSendReferrer()) {
-      referrerInfo->GetReferrerPolicy(aReferrerPolicy);
-    } else {
-      *aReferrerPolicy = mozilla::net::RP_No_Referrer;
-    }
+  if (!referrerInfo) {
+    referrerInfo =
+        new ReferrerInfo(doc->GetDocBaseURI(), doc->GetReferrerPolicy());
   }
 
+  referrerInfo.swap(*aReferrerInfo);
+
   RefPtr<nsDocShell> openerDocShell =
       static_cast<nsDocShell*>(opener->GetDocShell());
   if (!openerDocShell) {
     return NS_OK;
   }
 
   nsCOMPtr<nsIContentViewer> cv;
   nsresult rv = openerDocShell->GetContentViewer(getter_AddRefs(cv));
@@ -858,34 +874,33 @@ nsresult ContentChild::ProvideWindowComm
       rv = browserChrome3->ShouldLoadURIInThisProcess(aURI, &shouldLoad);
       loadInDifferentProcess = NS_SUCCEEDED(rv) && !shouldLoad;
     }
   }
 
   // If we're in a content process and we have noopener set, there's no reason
   // to load in our process, so let's load it elsewhere!
   if (loadInDifferentProcess) {
-    nsAutoCString baseURIString;
     float fullZoom;
     nsCOMPtr<nsIPrincipal> triggeringPrincipal;
     nsCOMPtr<nsIContentSecurityPolicy> csp;
-    uint32_t referrerPolicy = mozilla::net::RP_Unset;
+    nsCOMPtr<nsIReferrerInfo> referrerInfo;
     rv = GetCreateWindowParams(
-        aParent, aLoadState, baseURIString, &fullZoom, &referrerPolicy,
+        aParent, aLoadState, &fullZoom, getter_AddRefs(referrerInfo),
         getter_AddRefs(triggeringPrincipal), getter_AddRefs(csp));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     Maybe<URIParams> uriToLoad;
     SerializeURI(aURI, uriToLoad);
     Unused << SendCreateWindowInDifferentProcess(
         aTabOpener, aChromeFlags, aCalledFromJS, aPositionSpecified,
-        aSizeSpecified, uriToLoad, features, baseURIString, fullZoom, name,
-        Principal(triggeringPrincipal), csp, referrerPolicy);
+        aSizeSpecified, uriToLoad, features, fullZoom, name,
+        Principal(triggeringPrincipal), csp, referrerInfo);
 
     // We return NS_ERROR_ABORT, so that the caller knows that we've abandoned
     // the window open as far as it is concerned.
     return NS_ERROR_ABORT;
   }
 
   if (aTabOpener) {
     PopupIPCTabContext context;
@@ -1064,38 +1079,36 @@ nsresult ContentChild::ProvideWindowComm
     }
 
     // NOTE: BrowserFrameOpenWindowPromise is the same type as
     // CreateWindowPromise, and this code depends on that fact.
     newChild->SendBrowserFrameOpenWindow(aTabOpener, NS_ConvertUTF8toUTF16(url),
                                          name, NS_ConvertUTF8toUTF16(features),
                                          std::move(resolve), std::move(reject));
   } else {
-    nsAutoCString baseURIString;
     float fullZoom;
     nsCOMPtr<nsIPrincipal> triggeringPrincipal;
     nsCOMPtr<nsIContentSecurityPolicy> csp;
-    uint32_t referrerPolicy = mozilla::net::RP_Unset;
+    nsCOMPtr<nsIReferrerInfo> referrerInfo;
     rv = GetCreateWindowParams(
-        aParent, aLoadState, baseURIString, &fullZoom, &referrerPolicy,
+        aParent, aLoadState, &fullZoom, getter_AddRefs(referrerInfo),
         getter_AddRefs(triggeringPrincipal), getter_AddRefs(csp));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     Maybe<URIParams> uriToLoad;
     if (aURI) {
       SerializeURI(aURI, uriToLoad);
     }
 
     SendCreateWindow(aTabOpener, newChild, aChromeFlags, aCalledFromJS,
                      aPositionSpecified, aSizeSpecified, uriToLoad, features,
-                     baseURIString, fullZoom, Principal(triggeringPrincipal),
-                     csp, referrerPolicy, std::move(resolve),
-                     std::move(reject));
+                     fullZoom, Principal(triggeringPrincipal), csp,
+                     referrerInfo, std::move(resolve), std::move(reject));
   }
 
   // =======================
   // Begin Nested Event Loop
   // =======================
 
   // We have to wait for a response from either SendCreateWindow or
   // SendBrowserFrameOpenWindow with information we're going to need to return
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -4689,21 +4689,21 @@ bool ContentParent::DeallocPWebBrowserPe
   delete aActor;
   return true;
 }
 
 mozilla::ipc::IPCResult ContentParent::CommonCreateWindow(
     PBrowserParent* aThisTab, bool aSetOpener, const uint32_t& aChromeFlags,
     const bool& aCalledFromJS, const bool& aPositionSpecified,
     const bool& aSizeSpecified, nsIURI* aURIToLoad, const nsCString& aFeatures,
-    const nsCString& aBaseURI, const float& aFullZoom,
-    uint64_t aNextTabParentId, const nsString& aName, nsresult& aResult,
-    nsCOMPtr<nsITabParent>& aNewTabParent, bool* aWindowIsNew,
-    int32_t& aOpenLocation, nsIPrincipal* aTriggeringPrincipal,
-    uint32_t aReferrerPolicy, bool aLoadURI, nsIContentSecurityPolicy* aCsp)
+    const float& aFullZoom, uint64_t aNextTabParentId, const nsString& aName,
+    nsresult& aResult, nsCOMPtr<nsITabParent>& aNewTabParent,
+    bool* aWindowIsNew, int32_t& aOpenLocation,
+    nsIPrincipal* aTriggeringPrincipal, nsIReferrerInfo* aReferrerInfo,
+    bool aLoadURI, nsIContentSecurityPolicy* aCsp)
 
 {
   // The content process should never be in charge of computing whether or
   // not a window should be private or remote - the parent will do that.
   const uint32_t badFlags = nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW |
                             nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW |
                             nsIWebBrowserChrome::CHROME_PRIVATE_LIFETIME |
                             nsIWebBrowserChrome::CHROME_REMOTE_WINDOW;
@@ -4773,20 +4773,19 @@ mozilla::ipc::IPCResult ContentParent::C
       aResult = NS_ERROR_ABORT;
       return IPC_OK();
     }
 
     RefPtr<Element> openerElement = do_QueryObject(frame);
 
     nsCOMPtr<nsIOpenURIInFrameParams> params =
         new nsOpenURIInFrameParams(openerOriginAttributes, openerElement);
-    params->SetReferrer(NS_ConvertUTF8toUTF16(aBaseURI));
+    params->SetReferrerInfo(aReferrerInfo);
     MOZ_ASSERT(aTriggeringPrincipal, "need a valid triggeringPrincipal");
     params->SetTriggeringPrincipal(aTriggeringPrincipal);
-    params->SetReferrerPolicy(aReferrerPolicy);
     params->SetCsp(aCsp);
 
     RefPtr<Element> el;
 
     if (aLoadURI) {
       aResult = browserDOMWin->OpenURIInFrame(
           aURIToLoad, params, aOpenLocation, nsIBrowserDOMWindow::OPEN_NEW,
           aNextTabParentId, aName, getter_AddRefs(el));
@@ -4893,19 +4892,19 @@ mozilla::ipc::IPCResult ContentParent::C
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentParent::RecvCreateWindow(
     PBrowserParent* aThisTab, PBrowserParent* aNewTab,
     const uint32_t& aChromeFlags, const bool& aCalledFromJS,
     const bool& aPositionSpecified, const bool& aSizeSpecified,
     const Maybe<URIParams>& aURIToLoad, const nsCString& aFeatures,
-    const nsCString& aBaseURI, const float& aFullZoom,
-    const IPC::Principal& aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp,
-    const uint32_t& aReferrerPolicy, CreateWindowResolver&& aResolve) {
+    const float& aFullZoom, const IPC::Principal& aTriggeringPrincipal,
+    nsIContentSecurityPolicy* aCsp, nsIReferrerInfo* aReferrerInfo,
+    CreateWindowResolver&& aResolve) {
   nsresult rv = NS_OK;
   CreatedWindowInfo cwi;
 
   // We always expect to open a new window here. If we don't, it's an error.
   cwi.windowOpened() = true;
   cwi.maxTouchPoints() = 0;
   cwi.hasSiblings() = false;
 
@@ -4938,19 +4937,19 @@ mozilla::ipc::IPCResult ContentParent::R
   sNextTabParents.Put(nextTabParentId, newTab);
 
   const nsCOMPtr<nsIURI> uriToLoad = DeserializeURI(aURIToLoad);
 
   nsCOMPtr<nsITabParent> newRemoteTab;
   int32_t openLocation = nsIBrowserDOMWindow::OPEN_NEWWINDOW;
   mozilla::ipc::IPCResult ipcResult = CommonCreateWindow(
       aThisTab, /* aSetOpener = */ true, aChromeFlags, aCalledFromJS,
-      aPositionSpecified, aSizeSpecified, uriToLoad, aFeatures, aBaseURI,
-      aFullZoom, nextTabParentId, VoidString(), rv, newRemoteTab,
-      &cwi.windowOpened(), openLocation, aTriggeringPrincipal, aReferrerPolicy,
+      aPositionSpecified, aSizeSpecified, uriToLoad, aFeatures, aFullZoom,
+      nextTabParentId, VoidString(), rv, newRemoteTab, &cwi.windowOpened(),
+      openLocation, aTriggeringPrincipal, aReferrerInfo,
       /* aLoadUri = */ false, aCsp);
   if (!ipcResult) {
     return ipcResult;
   }
 
   if (NS_WARN_IF(NS_FAILED(rv)) || !newRemoteTab) {
     return IPC_OK();
   }
@@ -4973,32 +4972,30 @@ mozilla::ipc::IPCResult ContentParent::R
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentParent::RecvCreateWindowInDifferentProcess(
     PBrowserParent* aThisTab, const uint32_t& aChromeFlags,
     const bool& aCalledFromJS, const bool& aPositionSpecified,
     const bool& aSizeSpecified, const Maybe<URIParams>& aURIToLoad,
-    const nsCString& aFeatures, const nsCString& aBaseURI,
-    const float& aFullZoom, const nsString& aName,
+    const nsCString& aFeatures, const float& aFullZoom, const nsString& aName,
     const IPC::Principal& aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp,
-    const uint32_t& aReferrerPolicy) {
+    nsIReferrerInfo* aReferrerInfo) {
   nsCOMPtr<nsITabParent> newRemoteTab;
   bool windowIsNew;
   nsCOMPtr<nsIURI> uriToLoad = DeserializeURI(aURIToLoad);
   int32_t openLocation = nsIBrowserDOMWindow::OPEN_NEWWINDOW;
 
   nsresult rv;
   mozilla::ipc::IPCResult ipcResult = CommonCreateWindow(
       aThisTab, /* aSetOpener = */ false, aChromeFlags, aCalledFromJS,
-      aPositionSpecified, aSizeSpecified, uriToLoad, aFeatures, aBaseURI,
-      aFullZoom,
+      aPositionSpecified, aSizeSpecified, uriToLoad, aFeatures, aFullZoom,
       /* aNextTabParentId = */ 0, aName, rv, newRemoteTab, &windowIsNew,
-      openLocation, aTriggeringPrincipal, aReferrerPolicy,
+      openLocation, aTriggeringPrincipal, aReferrerInfo,
       /* aLoadUri = */ true, aCsp);
   if (!ipcResult) {
     return ipcResult;
   }
 
   if (NS_FAILED(rv)) {
     NS_WARNING("Call to CommonCreateWindow failed.");
   }
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -33,16 +33,17 @@
 #include "nsIInterfaceRequestor.h"
 #include "nsIObserver.h"
 #include "nsIThreadInternal.h"
 #include "nsIDOMGeoPositionCallback.h"
 #include "nsIDOMGeoPositionErrorCallback.h"
 #include "nsRefPtrHashtable.h"
 #include "PermissionMessageUtils.h"
 #include "DriverCrashGuard.h"
+#include "nsIReferrerInfo.h"
 
 #define CHILD_PROCESS_SHUTDOWN_MESSAGE \
   NS_LITERAL_STRING("child-process-shutdown")
 
 // These must match the similar ones in E10SUtils.jsm and ProcInfo.h.
 // Process names as reported by about:memory are defined in
 // ContentChild:RecvRemoteType.  Add your value there too or it will be called
 // "Web Content".
@@ -508,29 +509,27 @@ class ContentParent final : public PCont
 
   void ForkNewProcess(bool aBlocking);
 
   mozilla::ipc::IPCResult RecvCreateWindow(
       PBrowserParent* aThisTabParent, PBrowserParent* aNewTab,
       const uint32_t& aChromeFlags, const bool& aCalledFromJS,
       const bool& aPositionSpecified, const bool& aSizeSpecified,
       const Maybe<URIParams>& aURIToLoad, const nsCString& aFeatures,
-      const nsCString& aBaseURI, const float& aFullZoom,
-      const IPC::Principal& aTriggeringPrincipal,
-      nsIContentSecurityPolicy* aCsp, const uint32_t& aReferrerPolicy,
+      const float& aFullZoom, const IPC::Principal& aTriggeringPrincipal,
+      nsIContentSecurityPolicy* aCsp, nsIReferrerInfo* aReferrerInfo,
       CreateWindowResolver&& aResolve);
 
   mozilla::ipc::IPCResult RecvCreateWindowInDifferentProcess(
       PBrowserParent* aThisTab, const uint32_t& aChromeFlags,
       const bool& aCalledFromJS, const bool& aPositionSpecified,
       const bool& aSizeSpecified, const Maybe<URIParams>& aURIToLoad,
-      const nsCString& aFeatures, const nsCString& aBaseURI,
-      const float& aFullZoom, const nsString& aName,
+      const nsCString& aFeatures, const float& aFullZoom, const nsString& aName,
       const IPC::Principal& aTriggeringPrincipal,
-      nsIContentSecurityPolicy* aCsp, const uint32_t& aReferrerPolicy);
+      nsIContentSecurityPolicy* aCsp, nsIReferrerInfo* aReferrerInfo);
 
   static void BroadcastBlobURLRegistration(
       const nsACString& aURI, BlobImpl* aBlobImpl, nsIPrincipal* aPrincipal,
       ContentParent* aIgnoreThisCP = nullptr);
 
   static void BroadcastBlobURLUnregistration(
       const nsACString& aURI, ContentParent* aIgnoreThisCP = nullptr);
 
@@ -658,22 +657,22 @@ class ContentParent final : public PCont
 
   // Set aLoadUri to true to load aURIToLoad and to false to only create the
   // window. aURIToLoad should always be provided, if available, to ensure
   // compatibility with GeckoView.
   mozilla::ipc::IPCResult CommonCreateWindow(
       PBrowserParent* aThisTab, bool aSetOpener, const uint32_t& aChromeFlags,
       const bool& aCalledFromJS, const bool& aPositionSpecified,
       const bool& aSizeSpecified, nsIURI* aURIToLoad,
-      const nsCString& aFeatures, const nsCString& aBaseURI,
-      const float& aFullZoom, uint64_t aNextTabParentId, const nsString& aName,
-      nsresult& aResult, nsCOMPtr<nsITabParent>& aNewTabParent,
-      bool* aWindowIsNew, int32_t& aOpenLocation,
-      nsIPrincipal* aTriggeringPrincipal, uint32_t aReferrerPolicy,
-      bool aLoadUri, nsIContentSecurityPolicy* aCsp);
+      const nsCString& aFeatures, const float& aFullZoom,
+      uint64_t aNextTabParentId, const nsString& aName, nsresult& aResult,
+      nsCOMPtr<nsITabParent>& aNewTabParent, bool* aWindowIsNew,
+      int32_t& aOpenLocation, nsIPrincipal* aTriggeringPrincipal,
+      nsIReferrerInfo* aReferrerInfo, bool aLoadUri,
+      nsIContentSecurityPolicy* aCsp);
 
   enum RecordReplayState { eNotRecordingOrReplaying, eRecording, eReplaying };
 
   explicit ContentParent(int32_t aPluginID)
       : ContentParent(nullptr, EmptyString(), eNotRecordingOrReplaying,
                       EmptyString(), aPluginID) {}
   ContentParent(ContentParent* aOpener, const nsAString& aRemoteType,
                 RecordReplayState aRecordReplayState = eNotRecordingOrReplaying,
--- a/dom/ipc/DOMTypes.ipdlh
+++ b/dom/ipc/DOMTypes.ipdlh
@@ -25,16 +25,17 @@ using CSSRect from "Units.h";
 using CSSSize from "Units.h";
 using mozilla::LayoutDeviceIntPoint from "Units.h";
 using hal::ScreenOrientation from "mozilla/HalScreenConfiguration.h";
 using mozilla::gfx::SurfaceFormat from "mozilla/gfx/Types.h";
 using refcounted class nsIPrincipal from "mozilla/dom/PermissionMessageUtils.h";
 using refcounted class mozilla::dom::BrowsingContext from "mozilla/dom/BrowsingContext.h";
 using refcounted class nsIURI from "mozilla/ipc/URIUtils.h";
 using refcounted class nsIContentSecurityPolicy from "mozilla/dom/CSPMessageUtils.h";
+using refcounted class nsIReferrerInfo from "mozilla/dom/ReferrerInfoUtils.h";
 
 namespace mozilla {
 namespace dom {
 
 struct MessagePortIdentifier
 {
   nsID uuid;
   nsID destinationUuid;
@@ -192,31 +193,29 @@ struct WindowGlobalInit
   nsIPrincipal principal;
   BrowsingContext browsingContext;
   uint64_t innerWindowId;
   uint64_t outerWindowId;
 };
 
 struct DocShellLoadStateInit
 {
-  nsIURI Referrer;
   nsIURI URI;
   nsIURI OriginalURI;
   nsIURI ResultPrincipalURI;
   bool ResultPrincipalURIIsSome;
   nsIPrincipal TriggeringPrincipal;
+  nsIReferrerInfo ReferrerInfo;
   bool KeepResultPrincipalURIIfSet;
   bool LoadReplace;
   bool InheritPrincipal;
   bool PrincipalIsExplicit;
   nsIPrincipal PrincipalToInherit;
   bool ForceAllowDataURI;
   bool OriginalFrameSrc;
-  bool SendReferrer;
-  uint32_t ReferrerPolicy;
   uint32_t LoadType;
   nsString Target;
   nsIURI BaseURI;
   uint32_t LoadFlags;
   bool FirstParty;
   nsCString TypeHint;
   nsString FileName;
   bool IsFromProcessingFrameAttributes;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -1119,37 +1119,35 @@ parent:
     async CreateWindow(nullable PBrowser aThisTab,
                        PBrowser aNewTab,
                        uint32_t aChromeFlags,
                        bool aCalledFromJS,
                        bool aPositionSpecified,
                        bool aSizeSpecified,
                        URIParams? aURIToLoad,
                        nsCString aFeatures,
-                       nsCString aBaseURI,
                        float aFullZoom,
                        Principal aTriggeringPrincipal,
                        nsIContentSecurityPolicy aCsp,
-                       uint32_t aReferrerPolicy)
+                       nsIReferrerInfo aReferrerInfo)
         returns (CreatedWindowInfo window);
 
     async CreateWindowInDifferentProcess(
       PBrowser aThisTab,
       uint32_t aChromeFlags,
       bool aCalledFromJS,
       bool aPositionSpecified,
       bool aSizeSpecified,
       URIParams? aURIToLoad,
       nsCString aFeatures,
-      nsCString aBaseURI,
       float aFullZoom,
       nsString aName,
       Principal aTriggeringPrincipal,
       nsIContentSecurityPolicy aCsp,
-      uint32_t aReferrerPolicy);
+      nsIReferrerInfo aReferrerInfo);
 
     /**
      * Tells the parent to ungrab the pointer on the default display.
      *
      * This is for GTK platforms where we have to ensure the pointer ungrab happens in the
      * chrome process as that's the process that receives the pointer event.
      */
     sync UngrabPointer(uint32_t time);
new file mode 100644
--- /dev/null
+++ b/dom/ipc/ReferrerInfoUtils.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ReferrerInfoUtils.h"
+#include "nsISerializable.h"
+#include "nsSerializationHelper.h"
+
+namespace IPC {
+
+void ParamTraits<nsIReferrerInfo>::Write(Message* aMsg,
+                                         nsIReferrerInfo* aParam) {
+  bool isNull = !aParam;
+  WriteParam(aMsg, isNull);
+  if (isNull) {
+    return;
+  }
+  nsAutoCString infoString;
+  nsresult rv = NS_SerializeToString(aParam, infoString);
+  if (NS_FAILED(rv)) {
+    MOZ_CRASH("Unable to serialize referrer info.");
+    return;
+  }
+  WriteParam(aMsg, infoString);
+}
+
+bool ParamTraits<nsIReferrerInfo>::Read(const Message* aMsg,
+                                        PickleIterator* aIter,
+                                        RefPtr<nsIReferrerInfo>* aResult) {
+  bool isNull;
+  if (!ReadParam(aMsg, aIter, &isNull)) {
+    return false;
+  }
+  if (isNull) {
+    *aResult = nullptr;
+    return true;
+  }
+  nsAutoCString infoString;
+  if (!ReadParam(aMsg, aIter, &infoString)) {
+    return false;
+  }
+  nsCOMPtr<nsISupports> iSupports;
+  nsresult rv = NS_DeserializeObject(infoString, getter_AddRefs(iSupports));
+  NS_ENSURE_SUCCESS(rv, false);
+  nsCOMPtr<nsIReferrerInfo> referrerInfo = do_QueryInterface(iSupports);
+  NS_ENSURE_TRUE(referrerInfo, false);
+  *aResult = referrerInfo.forget();
+  return true;
+}
+
+}  // namespace IPC
new file mode 100644
--- /dev/null
+++ b/dom/ipc/ReferrerInfoUtils.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_referrer_info_utils_h__
+#define mozilla_dom_referrer_info_utils_h__
+
+#include "ipc/IPCMessageUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIReferrerInfo.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<nsIReferrerInfo> {
+  static void Write(Message* aMsg, nsIReferrerInfo* aParam);
+  static bool Read(const Message* aMsg, PickleIterator* aIter,
+                   RefPtr<nsIReferrerInfo>* aResult);
+};
+
+}  // namespace IPC
+
+#endif  // mozilla_dom_referrer_info_utils_h__
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -41,16 +41,17 @@ EXPORTS.mozilla.dom += [
     'CSPMessageUtils.h',
     'DocShellMessageUtils.h',
     'FilePickerParent.h',
     'JSWindowActorChild.h',
     'JSWindowActorParent.h',
     'JSWindowActorService.h',
     'MemoryReportRequest.h',
     'PermissionMessageUtils.h',
+    'ReferrerInfoUtils.h',
     'TabChild.h',
     'TabContext.h',
     'TabMessageUtils.h',
     'TabParent.h',
     'URLClassifierChild.h',
     'URLClassifierParent.h',
     'WindowGlobalChild.h',
     'WindowGlobalParent.h',
@@ -79,16 +80,17 @@ UNIFIED_SOURCES += [
     'JSWindowActorParent.cpp',
     'JSWindowActorService.cpp',
     'MemMapSnapshot.cpp',
     'MemoryReportRequest.cpp',
     'MMPrinter.cpp',
     'PermissionMessageUtils.cpp',
     'PreallocatedProcessManager.cpp',
     'ProcessPriorityManager.cpp',
+    'ReferrerInfoUtils.cpp',
     'SharedMap.cpp',
     'SharedStringMap.cpp',
     'StructuredCloneData.cpp',
     'TabChild.cpp',
     'TabContext.cpp',
     'TabMessageUtils.cpp',
     'TabParent.cpp',
     'URLClassifierParent.cpp',
--- a/dom/media/GraphRunner.cpp
+++ b/dom/media/GraphRunner.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 #include "GraphRunner.h"
 
 #include "GraphDriver.h"
 #include "MediaStreamGraph.h"
 #include "MediaStreamGraphImpl.h"
+#include "mozilla/dom/WorkletThread.h"
 #include "nsISupportsImpl.h"
 #include "prthread.h"
 #include "Tracing.h"
 
 namespace mozilla {
 
 static void Start(void* aArg) {
   GraphRunner* th = static_cast<GraphRunner*>(aArg);
@@ -96,16 +97,18 @@ void GraphRunner::Run() {
     mMonitor.Wait();  // Wait for mStateEnd or mShutdown to update
     if (mShutdown) {
       break;
     }
     TRACE();
     mStillProcessing = mGraph->OneIterationImpl(mStateEnd);
     mMonitor.Notify();  // Signal that mStillProcessing was updated
   }
+
+  dom::WorkletThread::DeleteCycleCollectedJSContext();
 }
 
 bool GraphRunner::OnThread() { return PR_GetCurrentThread() == mThread; }
 
 #ifdef DEBUG
 bool GraphRunner::RunByGraphDriver(GraphDriver* aDriver) {
   if (!OnThread()) {
     return false;
--- a/dom/media/TextTrack.cpp
+++ b/dom/media/TextTrack.cpp
@@ -10,19 +10,54 @@
 #include "mozilla/dom/TextTrackList.h"
 #include "mozilla/dom/TextTrackCue.h"
 #include "mozilla/dom/TextTrackCueList.h"
 #include "mozilla/dom/TextTrackRegion.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/HTMLTrackElement.h"
 #include "nsGlobalWindow.h"
 
+extern mozilla::LazyLogModule gTextTrackLog;
+
+#define WEBVTT_LOG(msg, ...) \
+  MOZ_LOG(gTextTrackLog, LogLevel::Debug, ("TextTrack=%p, " msg, this, ##__VA_ARGS__))
+
 namespace mozilla {
 namespace dom {
 
+static const char* ToStateStr(const TextTrackMode aMode) {
+  switch (aMode) {
+    case TextTrackMode::Disabled:
+      return "DISABLED";
+    case TextTrackMode::Hidden:
+      return "HIDDEN";
+    case TextTrackMode::Showing:
+      return "SHOWING";
+    default:
+      MOZ_ASSERT_UNREACHABLE("Invalid state.");
+  }
+  return "Unknown";
+}
+
+static const char* ToReadyStateStr(const TextTrackReadyState aState) {
+  switch (aState) {
+    case TextTrackReadyState::NotLoaded:
+      return "NotLoaded";
+    case TextTrackReadyState::Loading:
+      return "Loading";
+    case TextTrackReadyState::Loaded:
+      return "Loaded";
+    case TextTrackReadyState::FailedToLoad:
+      return "FailedToLoad";
+    default:
+      MOZ_ASSERT_UNREACHABLE("Invalid state.");
+  }
+  return "Unknown";
+}
+
 NS_IMPL_CYCLE_COLLECTION_INHERITED(TextTrack, DOMEventTargetHelper, mCueList,
                                    mActiveCueList, mTextTrackList,
                                    mTrackElement)
 
 NS_IMPL_ADDREF_INHERITED(TextTrack, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(TextTrack, DOMEventTargetHelper)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextTrack)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
@@ -71,16 +106,17 @@ JSObject* TextTrack::WrapObject(JSContex
                                 JS::Handle<JSObject*> aGivenProto) {
   return TextTrack_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 void TextTrack::SetMode(TextTrackMode aValue) {
   if (mMode == aValue) {
     return;
   }
+  WEBVTT_LOG("Set mode=%s", ToStateStr(aValue));
   mMode = aValue;
 
   HTMLMediaElement* mediaElement = GetMediaElement();
   if (aValue == TextTrackMode::Disabled) {
     for (size_t i = 0; i < mCueList->Length() && mediaElement; ++i) {
       mediaElement->NotifyCueRemoved(*(*mCueList)[i]);
     }
     SetCuesInactive();
@@ -101,30 +137,32 @@ void TextTrack::GetId(nsAString& aId) co
   // If the track has a track element then its id should be the same as the
   // track element's id.
   if (mTrackElement) {
     mTrackElement->GetAttribute(NS_LITERAL_STRING("id"), aId);
   }
 }
 
 void TextTrack::AddCue(TextTrackCue& aCue) {
+  WEBVTT_LOG("AddCue %p", &aCue);
   TextTrack* oldTextTrack = aCue.GetTrack();
   if (oldTextTrack) {
     ErrorResult dummy;
     oldTextTrack->RemoveCue(aCue, dummy);
   }
   mCueList->AddCue(aCue);
   aCue.SetTrack(this);
   HTMLMediaElement* mediaElement = GetMediaElement();
   if (mediaElement && (mMode != TextTrackMode::Disabled)) {
     mediaElement->NotifyCueAdded(aCue);
   }
 }
 
 void TextTrack::RemoveCue(TextTrackCue& aCue, ErrorResult& aRv) {
+  WEBVTT_LOG("RemoveCue %p", &aCue);
   // Bug1304948, check the aCue belongs to the TextTrack.
   mCueList->RemoveCue(aCue, aRv);
   if (aRv.Failed()) {
     return;
   }
   aCue.SetActive(false);
   aCue.SetTrack(nullptr);
   HTMLMediaElement* mediaElement = GetMediaElement();
@@ -156,16 +194,17 @@ TextTrackReadyState TextTrack::ReadyStat
 
 void TextTrack::SetReadyState(uint32_t aReadyState) {
   if (aReadyState <= TextTrackReadyState::FailedToLoad) {
     SetReadyState(static_cast<TextTrackReadyState>(aReadyState));
   }
 }
 
 void TextTrack::SetReadyState(TextTrackReadyState aState) {
+  WEBVTT_LOG("SetReadyState=%s", ToReadyStateStr(aState));
   mReadyState = aState;
   HTMLMediaElement* mediaElement = GetMediaElement();
   if (mediaElement && (mReadyState == TextTrackReadyState::Loaded ||
                        mReadyState == TextTrackReadyState::FailedToLoad)) {
     mediaElement->RemoveTextTrack(this, true);
     mediaElement->UpdateReadyState();
   }
 }
@@ -180,16 +219,17 @@ HTMLTrackElement* TextTrack::GetTrackEle
 
 void TextTrack::SetTrackElement(HTMLTrackElement* aTrackElement) {
   mTrackElement = aTrackElement;
 }
 
 void TextTrack::SetCuesInactive() { mCueList->SetCuesInactive(); }
 
 void TextTrack::NotifyCueUpdated(TextTrackCue* aCue) {
+  WEBVTT_LOG("NotifyCueUpdated, cue=%p", aCue);
   mCueList->NotifyCueUpdated(aCue);
   HTMLMediaElement* mediaElement = GetMediaElement();
   if (mediaElement) {
     mediaElement->NotifyCueUpdated(aCue);
   }
 }
 
 void TextTrack::GetLabel(nsAString& aLabel) const {
@@ -234,19 +274,21 @@ bool TextTrack::IsLoaded() {
   }
   return (mReadyState >= Loaded);
 }
 
 void TextTrack::NotifyCueActiveStateChanged(TextTrackCue* aCue) {
   MOZ_ASSERT(aCue);
   if (aCue->GetActive()) {
     MOZ_ASSERT(!mActiveCueList->IsCueExist(aCue));
+    WEBVTT_LOG("NotifyCueActiveStateChanged, add cue %p to the active list", aCue);
     mActiveCueList->AddCue(*aCue);
   } else {
     MOZ_ASSERT(mActiveCueList->IsCueExist(aCue));
+    WEBVTT_LOG("NotifyCueActiveStateChanged, remove cue %p from the active list", aCue);
     mActiveCueList->RemoveCue(*aCue);
   }
 }
 
 void TextTrack::GetCurrentCueList(RefPtr<TextTrackCueList>& aCueList) const {
   const HTMLMediaElement* mediaElement = GetMediaElement();
   if (!mediaElement) {
     return;
@@ -256,16 +298,18 @@ void TextTrack::GetCurrentCueList(RefPtr
   // whose start times are less than or equal to the current playback position
   // and whose end times are greater than the current playback position.
   // https://html.spec.whatwg.org/multipage/media.html#time-marches-on
   MOZ_ASSERT(aCueList);
   const double playbackTime = mediaElement->CurrentTime();
   for (uint32_t idx = 0; idx < mCueList->Length(); idx++) {
     TextTrackCue* cue = (*mCueList)[idx];
     if (cue->StartTime() <= playbackTime && cue->EndTime() > playbackTime) {
+      WEBVTT_LOG("Add cue %p [%f:%f] to current cue list",
+                 cue, cue->StartTime(), cue->EndTime());
       aCueList->AddCue(*cue);
     }
   }
 }
 
 HTMLMediaElement* TextTrack::GetMediaElement() const {
   return mTextTrackList ? mTextTrackList->GetMediaElement() : nullptr;
 }
--- a/dom/media/TextTrackCue.cpp
+++ b/dom/media/TextTrackCue.cpp
@@ -5,16 +5,21 @@
 
 #include "mozilla/dom/HTMLTrackElement.h"
 #include "mozilla/dom/TextTrackCue.h"
 #include "mozilla/dom/TextTrackList.h"
 #include "mozilla/dom/TextTrackRegion.h"
 #include "nsComponentManagerUtils.h"
 #include "mozilla/ClearOnShutdown.h"
 
+extern mozilla::LazyLogModule gTextTrackLog;
+
+#define LOG(msg, ...) \
+  MOZ_LOG(gTextTrackLog, LogLevel::Debug, ("TextTrackCue=%p, " msg, this, ##__VA_ARGS__))
+
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(TextTrackCue, DOMEventTargetHelper,
                                    mDocument, mTrack, mTrackElement,
                                    mDisplayState, mRegion)
 
 NS_IMPL_ADDREF_INHERITED(TextTrackCue, DOMEventTargetHelper)
@@ -47,16 +52,17 @@ TextTrackCue::TextTrackCue(nsPIDOMWindow
       mStartTime(aStartTime),
       mEndTime(aEndTime),
       mPosition(0.0),
       mLine(0.0),
       mReset(false, "TextTrackCue::mReset"),
       mHaveStartedWatcher(false),
       mWatchManager(
           this, GetOwnerGlobal()->AbstractMainThreadFor(TaskCategory::Other)) {
+  LOG("create TextTrackCue");
   SetDefaultCueSettings();
   MOZ_ASSERT(aOwnerWindow);
   if (NS_FAILED(StashDocument())) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 }
 
 TextTrackCue::TextTrackCue(nsPIDOMWindowInner* aOwnerWindow, double aStartTime,
@@ -68,16 +74,17 @@ TextTrackCue::TextTrackCue(nsPIDOMWindow
       mEndTime(aEndTime),
       mTrackElement(aTrackElement),
       mPosition(0.0),
       mLine(0.0),
       mReset(false, "TextTrackCue::mReset"),
       mHaveStartedWatcher(false),
       mWatchManager(
           this, GetOwnerGlobal()->AbstractMainThreadFor(TaskCategory::Other)) {
+  LOG("create TextTrackCue");
   SetDefaultCueSettings();
   MOZ_ASSERT(aOwnerWindow);
   if (NS_FAILED(StashDocument())) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 }
 
 TextTrackCue::~TextTrackCue() {}
@@ -216,16 +223,17 @@ void TextTrackCue::NotifyDisplayStatesCh
       ->NotifyCueDisplayStatesChanged();
 }
 
 void TextTrackCue::SetActive(bool aActive) {
   if (mActive == aActive) {
     return;
   }
 
+  LOG("TextTrackCue, SetActive=%d", aActive);
   mActive = aActive;
   mDisplayState = mActive ? mDisplayState : nullptr;
   if (mTrack) {
     mTrack->NotifyCueActiveStateChanged(this);
   }
 }
 
 }  // namespace dom
--- a/dom/media/webaudio/AudioWorkletImpl.cpp
+++ b/dom/media/webaudio/AudioWorkletImpl.cpp
@@ -53,25 +53,16 @@ AudioWorkletImpl::~AudioWorkletImpl() = 
 JSObject* AudioWorkletImpl::WrapWorklet(JSContext* aCx, dom::Worklet* aWorklet,
                                         JS::Handle<JSObject*> aGivenProto) {
   MOZ_ASSERT(NS_IsMainThread());
   return dom::AudioWorklet_Binding::Wrap(aCx, aWorklet, aGivenProto);
 }
 
 nsresult AudioWorkletImpl::SendControlMessage(
     already_AddRefed<nsIRunnable> aRunnable) {
-  MediaStreamGraph* graph = mDestinationStream->Graph();
-  if (!graph->IsNonRealtime()) {
-    // Bug 1473469 Realtime graphs currently use multiple threads, which is
-    // not compatible with the JS engine implementation.  In the meantime,
-    // this creates a separate thread which can process addModule() but not
-    // much else.
-    return WorkletImpl::SendControlMessage(std::move(aRunnable));
-  }
-
   mDestinationStream->SendRunnable(std::move(aRunnable));
   return NS_OK;
 }
 
 already_AddRefed<dom::WorkletGlobalScope>
 AudioWorkletImpl::ConstructGlobalScope() {
   dom::WorkletThread::AssertIsOnWorkletThread();
 
--- a/dom/push/PushService.jsm
+++ b/dom/push/PushService.jsm
@@ -212,36 +212,29 @@ var PushService = {
 
     if (this._state == PUSH_SERVICE_RUNNING) {
       // PushService was not in the offline state, but got notification to
       // go online (a offline notification has not been sent).
       // Disconnect first.
       this._service.disconnect();
     }
 
-    let records = await this.getAllUnexpired();
     let broadcastListeners = await pushBroadcastService.getListeners();
 
     // In principle, a listener could be added to the
     // pushBroadcastService here, after we have gotten listeners and
     // before we're RUNNING, but this can't happen in practice because
     // the only caller that can add listeners is PushBroadcastService,
     // and it waits on the same promise we are before it can add
     // listeners. If PushBroadcastService gets woken first, it will
     // update the value that is eventually returned from
     // getListeners.
     this._setState(PUSH_SERVICE_RUNNING);
 
-    if (records.length > 0 || prefs.get("alwaysConnect")) {
-      // Connect if we have existing subscriptions, or if the always-on pref
-      // is set. We gate on the pref to let us do load testing before
-      // turning it on for everyone, but if the user has push
-      // subscriptions, we need to connect them anyhow.
-      this._service.connect(records, broadcastListeners);
-    }
+    this._service.connect(broadcastListeners);
   },
 
   _changeStateConnectionEnabledEvent: function(enabled) {
     console.debug("changeStateConnectionEnabledEvent()", enabled);
 
     if (this._state < PUSH_SERVICE_CONNECTION_DISABLE &&
         this._state != PUSH_SERVICE_ACTIVATING) {
       return Promise.resolve();
@@ -292,17 +285,17 @@ var PushService = {
         if (aData == "dom.push.serverURL") {
           console.debug("observe: dom.push.serverURL changed for websocket",
                 prefs.get("serverURL"));
           this._stateChangeProcessEnqueue(_ =>
             this._changeServerURL(prefs.get("serverURL"),
                                   CHANGING_SERVICE_EVENT)
           );
 
-        } else if (aData == "dom.push.connection.enabled" || aData == "dom.push.alwaysConnect") {
+        } else if (aData == "dom.push.connection.enabled") {
           this._stateChangeProcessEnqueue(_ =>
             this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled"))
           );
         }
         break;
 
       case "idle-daily":
         this._dropExpiredRegistrations().catch(error => {
@@ -455,38 +448,38 @@ var PushService = {
    *                  state is change to PUSH_SERVICE_ACTIVATING.
    * startObservers() - start other observers.
    * changeStateConnectionEnabledEvent  - checks prefs and offline state.
    *                                      It changes state to:
    *                                        PUSH_SERVICE_RUNNING,
    *                                        PUSH_SERVICE_ACTIVE_OFFLINE or
    *                                        PUSH_SERVICE_CONNECTION_DISABLE.
    */
-  init: function(options = {}) {
+  async init(options = {}) {
     console.debug("init()");
 
     if (this._state > PUSH_SERVICE_UNINIT) {
       return;
     }
 
     this._setState(PUSH_SERVICE_ACTIVATING);
 
     prefs.observe("serverURL", this);
     Services.obs.addObserver(this, "quit-application");
 
     if (options.serverURI) {
       // this is use for xpcshell test.
 
-      return this._stateChangeProcessEnqueue(_ =>
+      await this._stateChangeProcessEnqueue(_ =>
         this._changeServerURL(options.serverURI, STARTING_SERVICE_EVENT, options));
 
     } else {
       // This is only used for testing. Different tests require connecting to
       // slightly different URLs.
-      return this._stateChangeProcessEnqueue(_ =>
+      await this._stateChangeProcessEnqueue(_ =>
         this._changeServerURL(prefs.get("serverURL"), STARTING_SERVICE_EVENT));
     }
   },
 
   _startObservers: function() {
     console.debug("startObservers()");
 
     if (this._state != PUSH_SERVICE_ACTIVATING) {
@@ -497,18 +490,16 @@ var PushService = {
 
     // The offline-status-changed event is used to know
     // when to (dis)connect. It may not fire if the underlying OS changes
     // networks; in such a case we rely on timeout.
     Services.obs.addObserver(this, "network:offline-status-changed");
 
     // Used to monitor if the user wishes to disable Push.
     prefs.observe("connection.enabled", this);
-    // Used to load-test the server-side infrastructure for broadcast.
-    prefs.observe("alwaysConnect", this);
 
     // Prunes expired registrations and notifies dormant service workers.
     Services.obs.addObserver(this, "idle-daily");
 
     // Prunes registrations for sites for which the user revokes push
     // permissions.
     Services.obs.addObserver(this, "perm-changed");
   },
@@ -582,42 +573,41 @@ var PushService = {
   _stopObservers: function() {
     console.debug("stopObservers()");
 
     if (this._state < PUSH_SERVICE_ACTIVATING) {
       return;
     }
 
     prefs.ignore("connection.enabled", this);
-    prefs.ignore("alwaysConnect", this);
 
     Services.obs.removeObserver(this, "network:offline-status-changed");
     Services.obs.removeObserver(this, "clear-origin-attributes-data");
     Services.obs.removeObserver(this, "idle-daily");
     Services.obs.removeObserver(this, "perm-changed");
   },
 
   _shutdownService() {
     let promiseChangeURL = this._changeServerURL("", UNINIT_EVENT);
     this._setState(PUSH_SERVICE_UNINIT);
     console.debug("shutdownService: shutdown complete!");
     return promiseChangeURL;
   },
 
-  uninit: function() {
+  async uninit() {
     console.debug("uninit()");
 
     if (this._state == PUSH_SERVICE_UNINIT) {
       return;
     }
 
     prefs.ignore("serverURL", this);
     Services.obs.removeObserver(this, "quit-application");
 
-    this._stateChangeProcessEnqueue(_ => this._shutdownService());
+    await this._stateChangeProcessEnqueue(_ => this._shutdownService());
   },
 
   /**
    * Drops all active registrations and notifies the associated service
    * workers. This function is called when the user switches Push servers,
    * or when the server invalidates all existing registrations.
    *
    * We ignore expired registrations because they're already handled in other
--- a/dom/push/PushServiceHttp2.jsm
+++ b/dom/push/PushServiceHttp2.jsm
@@ -420,17 +420,18 @@ var PushServiceHttp2 = {
 
   validServerURI: function(serverURI) {
     if (serverURI.scheme == "http") {
       return !!prefs.getBoolPref("testing.allowInsecureServerURL", false);
     }
     return serverURI.scheme == "https";
   },
 
-  connect: function(subscriptions, broadcastListeners) {
+  async connect(broadcastListeners) {
+    let subscriptions = await this._mainPushService.getAllUnexpired();
     this.startConnections(subscriptions);
   },
 
   sendSubscribeBroadcast: async function(serviceId, version) {
     // Not implemented yet
   },
 
   isConnected: function() {
--- a/dom/push/PushServiceWebSocket.jsm
+++ b/dom/push/PushServiceWebSocket.jsm
@@ -507,17 +507,17 @@ var PushServiceWebSocket = {
       this._currentState = STATE_WAITING_FOR_WS_START;
     } catch(e) {
       console.error("beginWSSetup: Error opening websocket.",
         "asyncOpen failed", e);
       this._reconnect();
     }
   },
 
-  connect: function(records, broadcastListeners) {
+  connect: function(broadcastListeners) {
     console.debug("connect()", broadcastListeners);
     this._broadcastListeners = broadcastListeners;
     this._beginWSSetup();
   },
 
   isConnected: function() {
     return !!this._ws;
   },
--- a/dom/push/test/mockpushserviceparent.js
+++ b/dom/push/test/mockpushserviceparent.js
@@ -141,21 +141,39 @@ var MockService = {
   },
 
   reportDeliveryError(messageId, reason) {
     sendAsyncMessage("service-delivery-error", {
       messageId: messageId,
       reason: reason,
     });
   },
+
+  uninit() {
+    return Promise.resolve();
+  },
 };
 
-addMessageListener("service-replace", function () {
-  pushService.service = MockService;
+async function replaceService(service) {
+  await pushService.service.uninit();
+  pushService.service = service;
+  await pushService.service.init();
+}
+
+addMessageListener("service-replace", function() {
+  replaceService(MockService).then(_ => {
+    sendAsyncMessage("service-replaced");
+  }).catch(error => {
+    Cu.reportError(`Error replacing service: ${error}`);
+  });
 });
 
-addMessageListener("service-restore", function () {
-  pushService.service = null;
+addMessageListener("service-restore", function() {
+  replaceService(null).then(_ => {
+    sendAsyncMessage("service-restored");
+  }).catch(error => {
+    Cu.reportError(`Error restoring service: ${error}`);
+  });
 });
 
 addMessageListener("service-response", function (response) {
   MockService.handleResponse(response);
 });
--- a/dom/push/test/test_utils.js
+++ b/dom/push/test/test_utils.js
@@ -4,18 +4,17 @@
   let url = SimpleTest.getTestFileURL("mockpushserviceparent.js");
   let chromeScript = SpecialPowers.loadChromeScript(url);
 
   /**
    * Replaces `PushService.jsm` with a mock implementation that handles requests
    * from the DOM API. This allows tests to simulate local errors and error
    * reporting, bypassing the `PushService.jsm` machinery.
    */
-  function replacePushService(mockService) {
-    chromeScript.sendSyncMessage("service-replace");
+  async function replacePushService(mockService) {
     chromeScript.addMessageListener("service-delivery-error", function(msg) {
       mockService.reportDeliveryError(msg.messageId, msg.reason);
     });
     chromeScript.addMessageListener("service-request", function(msg) {
       let promise;
       try {
         let handler = mockService[msg.name];
         promise = Promise.resolve(handler(msg.params));
@@ -29,20 +28,33 @@
         });
       }, error => {
         chromeScript.sendAsyncMessage("service-response", {
           id: msg.id,
           error: error,
         });
       });
     });
+    await new Promise(resolve => {
+      chromeScript.addMessageListener("service-replaced", function onReplaced() {
+        chromeScript.removeMessageListener("service-replaced", onReplaced);
+        resolve();
+      });
+      chromeScript.sendAsyncMessage("service-replace");
+    });
   }
 
-  function restorePushService() {
-    chromeScript.sendSyncMessage("service-restore");
+  async function restorePushService() {
+    await new Promise(resolve => {
+      chromeScript.addMessageListener("service-restored", function onRestored() {
+        chromeScript.removeMessageListener("service-restored", onRestored);
+        resolve();
+      });
+      chromeScript.sendAsyncMessage("service-restore");
+    });
   }
 
   let userAgentID = "8e1c93a9-139b-419c-b200-e715bb1e8ce8";
 
   let currentMockSocket = null;
 
   /**
    * Sets up a mock connection for the WebSocket backend. This only replaces
@@ -148,23 +160,21 @@
   g.MockWebSocket = MockWebSocket;
   g.setupMockPushSocket = setupMockPushSocket;
   g.teardownMockPushSocket = teardownMockPushSocket;
   g.replacePushService = replacePushService;
   g.restorePushService = restorePushService;
 }(this));
 
 // Remove permissions and prefs when the test finishes.
-SimpleTest.registerCleanupFunction(() => {
-  return new Promise(resolve =>
-    SpecialPowers.flushPermissions(resolve)
-  ).then(_ => SpecialPowers.flushPrefEnv()).then(_ => {
-    restorePushService();
-    return teardownMockPushSocket();
-  });
+SimpleTest.registerCleanupFunction(async function() {
+  await new Promise(resolve => SpecialPowers.flushPermissions(resolve));
+  await SpecialPowers.flushPrefEnv();
+  await restorePushService();
+  await teardownMockPushSocket();
 });
 
 function setPushPermission(allow) {
   return new Promise(resolve => {
     SpecialPowers.pushPermissions([
       { type: "desktop-notification", allow, context: document },
       ], resolve);
   });
@@ -176,19 +186,19 @@ function setupPrefs() {
     ["dom.push.connection.enabled", true],
     ["dom.push.maxRecentMessageIDsPerSubscription", 0],
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
     ]});
 }
 
-function setupPrefsAndReplaceService(mockService) {
-  replacePushService(mockService);
-  return setupPrefs();
+async function setupPrefsAndReplaceService(mockService) {
+  await replacePushService(mockService);
+  await setupPrefs();
 }
 
 function setupPrefsAndMockSocket(mockSocket) {
   setupMockPushSocket(mockSocket);
   return setupPrefs();
 }
 
 function injectControlledFrame(target = document.body) {
deleted file mode 100644
--- a/dom/push/test/xpcshell/test_startup_error.js
+++ /dev/null
@@ -1,73 +0,0 @@
-'use strict';
-
-const {PushService, PushServiceWebSocket} = serviceExports;
-
-function run_test() {
-  setPrefs();
-  do_get_profile();
-  run_next_test();
-}
-
-add_task(async function test_startup_error() {
-  let db = PushServiceWebSocket.newPushDB();
-  registerCleanupFunction(() => {return db.drop().then(_ => db.close());});
-
-  PushService.init({
-    serverURI: 'wss://push.example.org/',
-    db: makeStub(db, {
-      getAllExpired(prev) {
-        return Promise.reject('database corruption on startup');
-      },
-    }),
-    makeWebSocket(uri) {
-      return new MockWebSocket(uri, {
-        onHello(request) {
-          ok(false, 'Unexpected handshake');
-        },
-        onRegister(request) {
-          ok(false, 'Unexpected register request');
-        },
-      });
-    },
-  });
-
-  await rejects(
-    PushService.register({
-      scope: `https://example.net/1`,
-      originAttributes: ChromeUtils.originAttributesToSuffix(
-        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
-    }),
-    /Push service not active/,
-    'Should not register if startup failed'
-  );
-
-  PushService.uninit();
-
-  PushService.init({
-    serverURI: 'wss://push.example.org/',
-    db: makeStub(db, {
-      getAllUnexpired(prev) {
-        return Promise.reject('database corruption on connect');
-      },
-    }),
-    makeWebSocket(uri) {
-      return new MockWebSocket(uri, {
-        onHello(request) {
-          ok(false, 'Unexpected handshake');
-        },
-        onRegister(request) {
-          ok(false, 'Unexpected register request');
-        },
-      });
-    },
-  });
-  await rejects(
-    PushService.registration({
-      scope: `https://example.net/1`,
-      originAttributes: ChromeUtils.originAttributesToSuffix(
-        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
-    }),
-    /Push service not active/,
-    'Should not return registration if connection failed'
-  );
-});
--- a/dom/push/test/xpcshell/xpcshell.ini
+++ b/dom/push/test/xpcshell/xpcshell.ini
@@ -51,17 +51,16 @@ run-sequentially = This will delete all 
 [test_unregister_not_found.js]
 [test_unregister_success.js]
 [test_updateRecordNoEncryptionKeys_ws.js]
 skip-if = os == "linux" # Bug 1265233
 [test_reconnect_retry.js]
 [test_retry_ws.js]
 [test_service_parent.js]
 [test_service_child.js]
-[test_startup_error.js]
 
 #http2 test
 [test_resubscribe_4xxCode_http2.js]
 [test_resubscribe_5xxCode_http2.js]
 [test_resubscribe_listening_for_msg_error_http2.js]
 [test_register_5xxCode_http2.js]
 [test_updateRecordNoEncryptionKeys_http2.js]
 [test_register_success_http2.js]
new file mode 100644
--- /dev/null
+++ b/dom/smil/crashtests/1343357-1.html
@@ -0,0 +1,12 @@
+<svg>
+  <animateMotion to="500,500"></animateMotion>
+  <animateMotion to="10,40"></animateMotion>
+</svg>
+<svg width="100" height="100">
+  <rect width="100%" height="100%" />
+  <circle r="2" fill="red">
+    <animateMotion dur="1s" from="50,50" to="80,70" additive="sum"></animateMotion>
+    <animateMotion dur="1s" from="50,50" to="80,70" additive="sum"></animateMotion>
+    <animateMotion dur="3s" to="0,80"></animateMotion>
+  </circle>
+</svg>
--- a/dom/smil/crashtests/crashtests.list
+++ b/dom/smil/crashtests/crashtests.list
@@ -50,11 +50,12 @@ load 691337-2.svg
 load 697640-1.svg
 load 699325-1.svg
 load 709907-1.svg
 load 720103-1.svg
 load 849593-1.xhtml
 load 1010681-1.svg
 load 1322770-1.svg
 load 1322849-1.svg
+load 1343357-1.html
 load 1375596-1.svg
 load 1402547-1.html
 load 1411963-1.html
--- a/dom/svg/SVGMotionSMILType.cpp
+++ b/dom/svg/SVGMotionSMILType.cpp
@@ -358,48 +358,56 @@ nsresult SVGMotionSMILType::Interpolate(
   MOZ_ASSERT(aResult.mType == this, "Unexpected result type");
   MOZ_ASSERT(aUnitDistance >= 0.0 && aUnitDistance <= 1.0,
              "unit distance value out of bounds");
 
   const MotionSegmentArray& startArr = ExtractMotionSegmentArray(aStartVal);
   const MotionSegmentArray& endArr = ExtractMotionSegmentArray(aEndVal);
   MotionSegmentArray& resultArr = ExtractMotionSegmentArray(aResult);
 
-  MOZ_ASSERT(startArr.Length() <= 1,
-             "Invalid start-point for animateMotion interpolation");
   MOZ_ASSERT(endArr.Length() == 1,
              "Invalid end-point for animateMotion interpolation");
   MOZ_ASSERT(resultArr.IsEmpty(),
              "Expecting result to be just-initialized w/ empty array");
 
   const MotionSegment& endSeg = endArr[0];
   MOZ_ASSERT(endSeg.mSegmentType == eSegmentType_PathPoint,
              "Expecting to be interpolating along a path");
 
   const PathPointParams& endParams = endSeg.mU.mPathPointParams;
-  // NOTE: path & angle should match between start & end (since presumably
-  // start & end came from the same <animateMotion> element), unless start is
-  // empty. (as it would be for pure 'to' animation)
+  // NOTE: Ususally, path & angle should match between start & end (since
+  // presumably start & end came from the same <animateMotion> element),
+  // unless start is empty. (as it would be for pure 'to' animation)
+  // Notable exception: when a to-animation layers on top of lower priority
+  // animation(s) -- see comment below.
   Path* path = endParams.mPath;
   RotateType rotateType = endSeg.mRotateType;
   float rotateAngle = endSeg.mRotateAngle;
 
   float startDist;
-  if (startArr.IsEmpty()) {
+  if (startArr.IsEmpty() ||
+      startArr[0].mU.mPathPointParams.mPath != endParams.mPath) {
+    // When a to-animation follows other lower priority animation(s),
+    // the endParams will contain a different path from the animation(s)
+    // that it layers on top of.
+    // Per SMIL spec, we should interpolate from the value at startArr.
+    // However, neither Chrome nor Safari implements to-animation that way.
+    // For simplicity, we use the same behavior as other browsers: override
+    // previous animations and start at the initial underlying value.
     startDist = 0.0f;
   } else {
+    MOZ_ASSERT(startArr.Length() <= 1,
+               "Invalid start-point for animateMotion interpolation");
     const MotionSegment& startSeg = startArr[0];
     MOZ_ASSERT(startSeg.mSegmentType == eSegmentType_PathPoint,
                "Expecting to be interpolating along a path");
     const PathPointParams& startParams = startSeg.mU.mPathPointParams;
     MOZ_ASSERT(startSeg.mRotateType == endSeg.mRotateType &&
                    startSeg.mRotateAngle == endSeg.mRotateAngle,
                "unexpected angle mismatch");
-    MOZ_ASSERT(startParams.mPath == endParams.mPath,
-               "unexpected path mismatch");
     startDist = startParams.mDistToPoint;
   }
 
   // Get the interpolated distance along our path.
   float resultDist =
       InterpolateFloat(startDist, endParams.mDistToPoint, aUnitDistance);
 
   // Construct the intermediate result segment, and put it in our outparam.
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -2168,16 +2168,21 @@ static bool SettlePromiseNow(JSContext* 
 
   Rooted<PromiseObject*> promise(cx, &args[0].toObject().as<PromiseObject>());
   if (IsPromiseForAsync(promise)) {
     JS_ReportErrorASCII(
         cx, "async function's promise shouldn't be manually settled");
     return false;
   }
 
+  if (promise->state() != JS::PromiseState::Pending) {
+    JS_ReportErrorASCII(cx, "cannot settle an already-resolved promise");
+    return false;
+  }
+
   int32_t flags = promise->flags();
   promise->setFixedSlot(
       PromiseSlot_Flags,
       Int32Value(flags | PROMISE_FLAG_RESOLVED | PROMISE_FLAG_FULFILLED));
   promise->setFixedSlot(PromiseSlot_ReactionsOrResult, UndefinedValue());
 
   Debugger::onPromiseSettled(cx, promise);
   return true;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/promise/settle-now-already-resolved.js
@@ -0,0 +1,28 @@
+// |jit-test| error:Unhandled rejection
+
+load(libdir + "asserts.js");
+
+// Calling settlePromiseNow on already-resolved promise should throw, and
+// unhandled rejection tracking should work.
+
+assertThrowsInstanceOf(() => {
+  var promise = new Promise(resolve => {
+    resolve(10);
+  });
+  settlePromiseNow(promise);
+}, Error);
+
+
+assertThrowsInstanceOf(() => {
+  var promise = new Promise((_, reject) => {
+    reject(10);
+  });
+  settlePromiseNow(promise);
+}, Error);
+
+assertThrowsInstanceOf(() => {
+  var promise = new Promise(() => {
+    throw 10;
+  });
+  settlePromiseNow(promise);
+}, Error);
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5257,18 +5257,16 @@ pref("dom.vibrator.enabled", true);
 pref("dom.vibrator.max_vibrate_ms", 10000);
 pref("dom.vibrator.max_vibrate_list_len", 128);
 
 // Battery API
 pref("dom.battery.enabled", true);
 
 // Push
 
-pref("dom.push.alwaysConnect", false);
-
 pref("dom.push.loglevel", "Error");
 
 pref("dom.push.serverURL", "wss://push.services.mozilla.com/");
 pref("dom.push.userAgentID", "");
 
 // The maximum number of push messages that a service worker can receive
 // without user interaction.
 pref("dom.push.maxQuotaPerSubscription", 16);
--- a/taskcluster/ci/fetch/toolchains.yml
+++ b/taskcluster/ci/fetch/toolchains.yml
@@ -15,27 +15,16 @@ binutils-2.27:
     type: static-url
     url: ftp://ftp.gnu.org/gnu/binutils/binutils-2.27.tar.bz2
     sha256: 369737ce51587f92466041a97ab7d2358c6d9e1b6490b3940eb09fb0a9a6ac88
     size: 26099568
     gpg-signature:
       sig-url: "{url}.sig"
       key-path: build/unix/build-gcc/EAF1C276A747E9ED86210CBAC3126D3B4AE55E93.key
 
-binutils-2.28.1:
-  description: binutils 2.28.1 source code
-  fetch:
-    type: static-url
-    url: ftp://ftp.gnu.org/gnu/binutils/binutils-2.28.1.tar.xz
-    sha256: 16328a906e55a3c633854beec8e9e255a639b366436470b4f6245eb0d2fde942
-    size: 19440112
-    gpg-signature:
-      sig-url: "{url}.sig"
-      key-path: build/unix/build-gcc/EAF1C276A747E9ED86210CBAC3126D3B4AE55E93.key
-
 binutils-2.31.1:
   description: binutils 2.31.1 source code
   fetch:
     type: static-url
     url: ftp://ftp.gnu.org/gnu/binutils/binutils-2.31.1.tar.xz
     sha256: 5d20086ecf5752cc7d9134246e9588fa201740d540f7eb84d795b1f7a93bca86
     size: 20467996
     gpg-signature:
@@ -45,27 +34,16 @@ binutils-2.31.1:
 cloog-0.18.1:
   description: cloog source code
   fetch:
     type: static-url
     url: ftp://gcc.gnu.org/pub/gcc/infrastructure/cloog-0.18.1.tar.gz
     sha256: 02500a4edd14875f94fe84cbeda4290425cb0c1c2474c6f75d75a303d64b4196
     size: 3857324
 
-gcc-4.9.4:
-  description: GCC 4.9.4 source code
-  fetch:
-    type: static-url
-    url: ftp://ftp.gnu.org/gnu/gcc/gcc-4.9.4/gcc-4.9.4.tar.bz2
-    sha256: 6c11d292cd01b294f9f84c9a59c230d80e9e4a47e5c6355f046bb36d4f358092
-    size: 90097606
-    gpg-signature:
-      sig-url: "{url}.sig"
-      key-path: build/unix/build-gcc/13975A70E63C361C73AE69EF6EEB81F8981C74C7.key
-
 gcc-6.4.0:
   description: GCC 6.4.0 source code
   fetch:
     type: static-url
     url: ftp://ftp.gnu.org/gnu/gcc/gcc-6.4.0/gcc-6.4.0.tar.xz
     sha256: 850bf21eafdfe5cd5f6827148184c08c4a0852a37ccf36ce69855334d2c914d4
     size: 76156220
     gpg-signature:
--- a/taskcluster/ci/toolchain/clang-tidy.yml
+++ b/taskcluster/ci/toolchain/clang-tidy.yml
@@ -29,17 +29,17 @@ linux64-clang-tidy:
     treeherder:
         symbol: TL(clang-tidy)
     run:
         script: build-clang-tidy-linux.sh
         resources:
             - 'build/build-clang/clang-tidy-linux64.json'
             - 'taskcluster/scripts/misc/tooltool-download.sh'
     toolchains:
-        - linux64-gcc-4.9
+        - linux64-gcc-6
 
 macosx64-clang-tidy:
     index:
         job-name: macosx64-clang-tidy
     treeherder:
         symbol: TM(clang-tidy)
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux-large
     worker:
@@ -50,17 +50,17 @@ macosx64-clang-tidy:
         script: build-clang-tidy-macosx.sh
         tooltool-downloads: internal
         resources:
             - 'build/build-clang/clang-tidy-macosx64.json'
             - 'taskcluster/scripts/misc/tooltool-download.sh'
     toolchains:
         - linux64-cctools-port
         - linux64-clang-7
-        - linux64-gcc-4.9
+        - linux64-gcc-6
         - linux64-node
 
 win64-clang-tidy:
     description: "Clang-tidy toolchain build"
     index:
         job-name: win64-clang-tidy
     treeherder:
         symbol: TW64(clang-tidy)
--- a/taskcluster/ci/toolchain/clang.yml
+++ b/taskcluster/ci/toolchain/clang.yml
@@ -17,81 +17,81 @@ linux64-clang-4.0:
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux-large
     run:
         script: build-clang-4.0-linux.sh
         resources:
             - 'build/build-clang/clang-4.0-linux64.json'
             - 'taskcluster/scripts/misc/tooltool-download.sh'
         toolchain-artifact: public/build/clang.tar.xz
     toolchains:
-        - linux64-gcc-4.9
+        - linux64-gcc-6
 
 linux64-clang-7:
     description: "Clang 7 toolchain build"
     treeherder:
         symbol: TL(clang7)
     run:
         using: toolchain-script
         script: build-clang-7-linux.sh
         resources:
             - 'build/build-clang/build-clang.py'
             - 'build/build-clang/clang-7-linux64.json'
             - 'taskcluster/scripts/misc/tooltool-download.sh'
         toolchain-alias: linux64-clang
         toolchain-artifact: public/build/clang.tar.xz
     toolchains:
-        - linux64-gcc-4.9
+        - linux64-gcc-6
 
 linux64-clang-trunk-mingw-x86:
     description: "MinGW-Clang Trunk x86 toolchain build"
     treeherder:
         symbol: TMW(clang-x86)
     run:
         script: build-clang-trunk-mingw.sh
         arguments: [
             'x86'
         ]
         resources:
             - 'build/build-clang/clang-trunk-mingw.json'
             - 'taskcluster/scripts/misc/tooltool-download.sh'
         toolchain-artifact: public/build/clangmingw.tar.xz
     toolchains:
-        - linux64-gcc-4.9
+        - linux64-gcc-6
 
 linux64-clang-trunk-mingw-x64:
     description: "MinGW-Clang Trunk x64 toolchain build"
     treeherder:
         symbol: TMW(clang-x64)
         tier: 1
     run:
         script: build-clang-trunk-mingw.sh
         arguments: [
             'x64'
         ]
         resources:
             - 'build/build-clang/clang-trunk-mingw.json'
             - 'taskcluster/scripts/misc/tooltool-download.sh'
         toolchain-artifact: public/build/clangmingw.tar.xz
     toolchains:
-        - linux64-gcc-4.9
+        - linux64-gcc-6
 
 linux64-clang-7-android-cross:
     description: "Clang 7 toolchain build"
     treeherder:
         symbol: TL(clang7-android)
     run:
         using: toolchain-script
         script: build-clang-7-android.sh
         resources:
             - 'build/build-clang/clang-7-android.json'
             - 'taskcluster/scripts/misc/tooltool-download.sh'
         toolchain-alias: linux64-clang-android-cross
         toolchain-artifact: public/build/clang.tar.xz
     toolchains:
-        - linux64-gcc-4.9
+        - linux64-gcc-6
         - linux64-android-ndk-linux-repack
 
 linux64-clang-7-macosx-cross:
     description: "Clang 7 toolchain build with MacOS Compiler RT libs"
     treeherder:
         symbol: TL(clang7-macosx-cross)
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
@@ -104,17 +104,17 @@ linux64-clang-7-macosx-cross:
             - 'build/build-clang/clang-7-macosx64.json'
             - 'taskcluster/scripts/misc/tooltool-download.sh'
         toolchain-alias: linux64-clang-macosx-cross
         toolchain-artifact: public/build/clang.tar.xz
         tooltool-downloads: internal
     toolchains:
         - linux64-cctools-port
         - linux64-clang-7
-        - linux64-gcc-4.9
+        - linux64-gcc-6
 
 macosx64-clang:
     description: "Clang toolchain build"
     treeherder:
         symbol: TM(clang)
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux-large
     worker:
         max-run-time: 3600
@@ -128,17 +128,17 @@ macosx64-clang:
         tooltool-downloads: internal
         resources:
             - 'build/build-clang/clang-7-macosx64.json'
             - 'taskcluster/scripts/misc/tooltool-download.sh'
         toolchain-artifact: public/build/clang.tar.xz
     toolchains:
         - linux64-cctools-port
         - linux64-clang-7
-        - linux64-gcc-4.9
+        - linux64-gcc-6
         - linux64-node
 
 win64-clang-cl:
     description: "Clang-cl toolchain build"
     treeherder:
         symbol: TW64(clang-cl)
     worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
     worker:
--- a/taskcluster/ci/toolchain/gcc.yml
+++ b/taskcluster/ci/toolchain/gcc.yml
@@ -6,57 +6,41 @@ job-defaults:
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 3600
     run:
         resources:
             - 'build/unix/build-gcc/build-gcc.sh'
         toolchain-artifact: public/build/gcc.tar.xz
 
-linux64-gcc-4.9:
-    description: "GCC 4.9 toolchain build"
-    treeherder:
-        symbol: TL(gcc4.9)
-    run:
-        script: build-gcc-4.9-linux.sh
-    fetches:
-        fetch:
-            - binutils-2.25.1
-            - cloog-0.18.1
-            - gcc-4.9.4
-            - gmp-5.1.3
-            - isl-0.12.2
-            - mpc-0.8.2
-            - mpfr-3.1.5
-
 linux64-gcc-6:
     description: "GCC 6 toolchain build"
     treeherder:
         symbol: TL(gcc6)
     run:
         script: build-gcc-6-linux.sh
         toolchain-alias: linux64-gcc
     fetches:
         fetch:
-            - binutils-2.28.1
+            - binutils-2.31.1
             - gcc-6.4.0
             - gmp-5.1.3
             - isl-0.15
             - mpc-0.8.2
             - mpfr-3.1.5
 
 linux64-gcc-7:
     description: "GCC 7 toolchain build"
     treeherder:
         symbol: TL(gcc7)
     run:
         script: build-gcc-7-linux.sh
     fetches:
         fetch:
-            - binutils-2.28.1
+            - binutils-2.31.1
             - gcc-7.3.0
             - gmp-6.1.0
             - isl-0.16.1
             - mpc-1.0.3
             - mpfr-3.1.4
 
 linux64-gcc-sixgill:
     description: "sixgill GCC plugin build"
@@ -66,14 +50,14 @@ linux64-gcc-sixgill:
         script: build-gcc-sixgill-plugin-linux.sh
         resources:
             - 'taskcluster/scripts/misc/build-gcc-sixgill-plugin-linux.sh'
         toolchain-artifact: public/build/sixgill.tar.xz
     toolchains:
         - linux64-gcc-6
     fetches:
         fetch:
-            - binutils-2.28.1
+            - binutils-2.31.1
             - isl-0.15
             - gcc-6.4.0
             - gmp-5.1.3
             - mpc-0.8.2
             - mpfr-3.1.5
--- a/taskcluster/ci/toolchain/misc.yml
+++ b/taskcluster/ci/toolchain/misc.yml
@@ -27,17 +27,17 @@ linux64-infer:
 linux64-llvm-dsymutil:
     description: "llvm-dsymutil toolchain build"
     treeherder:
         symbol: TL(dsymutil)
     run:
         script: build-llvm-dsymutil.sh
         toolchain-artifact: public/build/llvm-dsymutil.tar.xz
     toolchains:
-        - linux64-gcc-4.9
+        - linux64-gcc-6
 
 linux64-binutils:
     description: "Binutils toolchain build"
     treeherder:
         symbol: TL(binutil)
     worker:
         max-run-time: 3600
     run:
@@ -80,17 +80,17 @@ linux64-tup:
         script: build-tup-linux.sh
         resources:
             - 'taskcluster/scripts/misc/tooltool-download.sh'
         toolchain-artifact: public/build/tup.tar.xz
     run-on-projects:
         - trunk
         - try
     toolchains:
-        - linux64-gcc-4.9
+        - linux64-gcc-6
 
 linux64-custom-v8:
     description: "Custom v8 build"
     treeherder:
         symbol: TL(custom-v8)
     worker:
         docker-image: {in-tree: custom-v8}
         max-run-time: 3600
--- a/taskcluster/scripts/misc/build-clang-trunk-mingw.sh
+++ b/taskcluster/scripts/misc/build-clang-trunk-mingw.sh
@@ -316,12 +316,11 @@ build_libcxx
 build_utils
 
 popd
 
 # Put a tarball in the artifacts dir
 mkdir -p $UPLOAD_DIR
 
 pushd $(dirname $INSTALL_DIR)
-rm -f clang/lib/libstdc++*
 tar caf clangmingw.tar.xz clang
 mv clangmingw.tar.xz $UPLOAD_DIR
 popd
--- a/taskcluster/scripts/misc/build-gcc-6-linux.sh
+++ b/taskcluster/scripts/misc/build-gcc-6-linux.sh
@@ -9,17 +9,17 @@ UPLOAD_DIR=$HOME/artifacts
 
 root_dir=$HOME_DIR
 data_dir=$HOME_DIR/src/build/unix/build-gcc
 
 . $data_dir/build-gcc.sh
 
 gcc_version=6.4.0
 gcc_ext=xz
-binutils_version=2.28.1
+binutils_version=2.31.1
 binutils_ext=xz
 
 pushd $root_dir/gcc-$gcc_version
 ln -sf ../gmp-5.1.3 gmp
 ln -sf ../isl-0.15 isl
 ln -sf ../mpc-0.8.2 mpc
 ln -sf ../mpfr-3.1.5 mpfr
 popd
--- a/taskcluster/scripts/misc/build-gcc-7-linux.sh
+++ b/taskcluster/scripts/misc/build-gcc-7-linux.sh
@@ -9,17 +9,17 @@ UPLOAD_DIR=$HOME/artifacts
 
 root_dir=$HOME_DIR
 data_dir=$HOME_DIR/src/build/unix/build-gcc
 
 . $data_dir/build-gcc.sh
 
 gcc_version=7.3.0
 gcc_ext=xz
-binutils_version=2.28.1
+binutils_version=2.31.1
 binutils_ext=xz
 
 pushd $root_dir/gcc-$gcc_version
 ln -sf ../gmp-6.1.0 gmp
 ln -sf ../isl-0.16.1 isl
 ln -sf ../mpc-1.0.3 mpc
 ln -sf ../mpfr-3.1.4 mpfr
 popd
--- a/taskcluster/scripts/misc/build-gcc-sixgill-plugin-linux.sh
+++ b/taskcluster/scripts/misc/build-gcc-sixgill-plugin-linux.sh
@@ -23,17 +23,17 @@ gcc_ext=xz
 binutils_version=2.28.1
 binutils_ext=xz
 sixgill_rev=bc0ef9258470
 sixgill_repo=https://hg.mozilla.org/users/sfink_mozilla.com/sixgill
 
 . $data_dir/build-gcc.sh
 
 pushd $root_dir/gcc-$gcc_version
-ln -sf ../binutils-2.28.1 binutils
+ln -sf ../binutils-2.31.1 binutils
 ln -sf ../gmp-5.1.3 gmp
 ln -sf ../isl-0.15 isl
 ln -sf ../mpc-0.8.2 mpc
 ln -sf ../mpfr-3.1.5 mpfr
 popd
 
 export TMPDIR=${TMPDIR:-/tmp/}
 export gcc_bindir=$root_dir/src/gcc/bin
--- a/testing/mochitest/api.js
+++ b/testing/mochitest/api.js
@@ -94,17 +94,17 @@ function loadMochitest(e) {
   let flavor = e.detail[0];
   let url = e.detail[1];
 
   let win = Services.wm.getMostRecentWindow(WINDOW_TYPE);
   win.removeEventListener("mochitest-load", loadMochitest);
 
   // for mochitest-plain, navigating to the url is all we need
   if (!IS_THUNDERBIRD) {
-    win.loadURI(url, null, null, null, null, null, null, null,
+    win.loadURI(url, null, null, null, null, null, null,
       Services.scriptSecurityManager.getSystemPrincipal());
   }
   if (flavor == "mochitest") {
     return;
   }
 
   WindowListener.setupWindow(win);
   Services.wm.addListener(WindowListener);
--- a/testing/specialpowers/content/SpecialPowersObserverAPI.js
+++ b/testing/specialpowers/content/SpecialPowersObserverAPI.js
@@ -596,19 +596,28 @@ SpecialPowersObserverAPI.prototype = {
         extension.on("test-message", messageListener);
 
         this._extensions.set(id, extension);
         return undefined;
       }
 
       case "SPStartupExtension": {
         let id = aMessage.data.id;
+        // This is either an Extension, or (if useAddonManager is set) a MockExtension.
         let extension = this._extensions.get(id);
-        extension.on("startup", () => {
-          this._sendReply(aMessage, "SPExtensionMessage", {id, type: "extensionSetId", args: [extension.id, extension.uuid]});
+        extension.on("startup", (eventName, ext) => {
+          if (!ext) {
+            // ext is only set by the "startup" event from Extension.jsm.
+            // Unfortunately ext-backgroundPage.js emits an event with the same
+            // name, but without the extension object as parameter.
+            return;
+          }
+          // ext is always the "real" Extension object, even when "extension"
+          // is a MockExtension.
+          this._sendReply(aMessage, "SPExtensionMessage", {id, type: "extensionSetId", args: [ext.id, ext.uuid]});
         });
 
         // Make sure the extension passes the packaging checks when
         // they're run on a bare archive rather than a running instance,
         // as the add-on manager runs them.
         let extensionData = new ExtensionData(extension.rootURI);
         extensionData.loadManifest().then(
           () => {
--- a/toolkit/components/extensions/ExtensionTestCommon.jsm
+++ b/toolkit/components/extensions/ExtensionTestCommon.jsm
@@ -106,16 +106,25 @@ class MockExtension {
   testMessage(...args) {
     return this._extension.testMessage(...args);
   }
 
   on(...args) {
     this._extensionPromise.then(extension => {
       extension.on(...args);
     });
+    // Extension.jsm emits a "startup" event on |extension| before emitting the
+    // "startup" event on |apiManager|. Trigger the "startup" event anyway, to
+    // make sure that the MockExtension behaves like an Extension with regards
+    // to the startup event.
+    if (args[0] === "startup" && !this._extension) {
+      this._extensionPromise.then(extension => {
+        args[1]("startup", extension);
+      });
+    }
   }
 
   off(...args) {
     this._extensionPromise.then(extension => {
       extension.off(...args);
     });
   }
 
--- a/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
+++ b/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
@@ -250,17 +250,16 @@ class ExtensionWrapper {
       } else if (this.state == "unloading") {
         this.testScope.equal(this.state, "unloaded", "Extension not fully unloaded at test shutdown");
       }
       this.destroy();
     });
 
     if (extension) {
       this.id = extension.id;
-      this.uuid = extension.uuid;
       this.attachExtension(extension);
     }
   }
 
   destroy() {
     // This method should be implemented in subclasses which need to
     // perform cleanup when destroyed.
   }
@@ -273,16 +272,17 @@ class ExtensionWrapper {
     if (this.extension) {
       this.extension.off("test-eq", this.handleResult);
       this.extension.off("test-log", this.handleResult);
       this.extension.off("test-result", this.handleResult);
       this.extension.off("test-done", this.handleResult);
       this.extension.off("test-message", this.handleMessage);
       this.clearMessageQueues();
     }
+    this.uuid = extension.uuid;
     this.extension = extension;
 
     extension.on("test-eq", this.handleResult);
     extension.on("test-log", this.handleResult);
     extension.on("test-result", this.handleResult);
     extension.on("test-done", this.handleResult);
     extension.on("test-message", this.handleMessage);
 
--- a/toolkit/components/extensions/test/browser/browser.ini
+++ b/toolkit/components/extensions/test/browser/browser.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 support-files =
   head.js
 
 [browser_ext_management_themes.js]
 skip-if = verify
+[browser_ext_test_mock.js]
 [browser_ext_themes_additional_backgrounds_alignment.js]
 [browser_ext_themes_alpha_accentcolor.js]
 [browser_ext_themes_chromeparity.js]
 [browser_ext_themes_dynamic_getCurrent.js]
 [browser_ext_themes_dynamic_onUpdated.js]
 [browser_ext_themes_dynamic_updates.js]
 [browser_ext_themes_experiment.js]
 [browser_ext_themes_getCurrent_differentExt.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_test_mock.js
@@ -0,0 +1,45 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+// This test verifies that the extension mocks behave consistently, regardless
+// of test type (xpcshell vs browser test).
+// See also toolkit/components/extensions/test/xpcshell/test_ext_test_mock.js
+
+// Check the state of the extension object. This should be consistent between
+// browser tests and xpcshell tests.
+async function checkExtensionStartupAndUnload(ext) {
+  await ext.startup();
+  Assert.ok(ext.id, "Extension ID should be available");
+  Assert.ok(ext.uuid, "Extension UUID should be available");
+  await ext.unload();
+  // Once set nothing clears the UUID.
+  Assert.ok(ext.uuid, "Extension UUID exists after unload");
+}
+
+add_task(async function test_MockExtension() {
+  // When "useAddonManager" is set, a MockExtension is created in the main
+  // process, which does not necessarily behave identically to an Extension.
+  let ext = ExtensionTestUtils.loadExtension({
+    // xpcshell/test_ext_test_mock.js tests "temporary", so here we use
+    // "permanent" to have even more test coverage.
+    useAddonManager: "permanent",
+    manifest: {applications: {gecko: {id: "@permanent-mock-extension"}}},
+  });
+
+  Assert.ok(!ext.id, "Extension ID is initially unavailable");
+  Assert.ok(!ext.uuid, "Extension UUID is initially unavailable");
+  await checkExtensionStartupAndUnload(ext);
+  Assert.ok(ext.id, "Extension ID exists after unload");
+});
+
+add_task(async function test_generated_Extension() {
+  let ext = ExtensionTestUtils.loadExtension({
+    manifest: {},
+  });
+
+  Assert.ok(!ext.id, "Extension ID is initially unavailable");
+  Assert.ok(!ext.uuid, "Extension UUID is initially unavailable");
+  await checkExtensionStartupAndUnload(ext);
+  Assert.ok(ext.id, "Extension ID exists after unload");
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_test_mock.js
@@ -0,0 +1,53 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+// This test verifies that the extension mocks behave consistently, regardless
+// of test type (xpcshell vs browser test).
+// See also toolkit/components/extensions/test/browser/browser_ext_test_mock.js
+
+// Check the state of the extension object. This should be consistent between
+// browser tests and xpcshell tests.
+async function checkExtensionStartupAndUnload(ext) {
+  await ext.startup();
+  Assert.ok(ext.id, "Extension ID should be available");
+  Assert.ok(ext.uuid, "Extension UUID should be available");
+  await ext.unload();
+  // Once set nothing clears the UUID.
+  Assert.ok(ext.uuid, "Extension UUID exists after unload");
+}
+
+add_task(async function setup() {
+  await ExtensionTestUtils.startAddonManager();
+});
+
+add_task(async function test_MockExtension() {
+  let ext = ExtensionTestUtils.loadExtension({
+    useAddonManager: "temporary",
+    manifest: {},
+  });
+
+  Assert.equal(ext.constructor.name, "InstallableWrapper", "expected class");
+  Assert.ok(!ext.id, "Extension ID is initially unavailable");
+  Assert.ok(!ext.uuid, "Extension UUID is initially unavailable");
+  await checkExtensionStartupAndUnload(ext);
+  // When useAddonManager is set, AOMExtensionWrapper clears the ID upon unload.
+  // TODO: Fix AOMExtensionWrapper to not clear the ID after unload, and move
+  // this assertion inside |checkExtensionStartupAndUnload| (since then the
+  // behavior will be consistent across all test types).
+  Assert.ok(!ext.id, "Extension ID is cleared after unload");
+});
+
+add_task(async function test_generated_Extension() {
+  let ext = ExtensionTestUtils.loadExtension({
+    manifest: {},
+  });
+
+  Assert.equal(ext.constructor.name, "ExtensionWrapper", "expected class");
+  // Without "useAddonManager", an Extension is generated and their IDs are
+  // immediately available.
+  Assert.ok(ext.id, "Extension ID is initially available");
+  Assert.ok(ext.uuid, "Extension UUID is initially available");
+  await checkExtensionStartupAndUnload(ext);
+  Assert.ok(ext.id, "Extension ID exists after unload");
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -44,16 +44,17 @@ skip-if = toolkit == 'android' # browser
 [test_ext_schemas.js]
 [test_ext_schemas_roots.js]
 [test_ext_schemas_async.js]
 [test_ext_schemas_allowed_contexts.js]
 [test_ext_schemas_interactive.js]
 [test_ext_schemas_manifest_permissions.js]
 [test_ext_schemas_privileged.js]
 [test_ext_schemas_revoke.js]
+[test_ext_test_mock.js]
 [test_ext_unknown_permissions.js]
 [test_load_all_api_modules.js]
 [test_locale_converter.js]
 [test_locale_data.js]
 [test_ext_ipcBlob.js]
 
 [test_ext_runtime_sendMessage_args.js]
 
--- a/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
+++ b/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
@@ -378,16 +378,23 @@ async function test_push_cleared() {
         return new MockWebSocket(uriObj, {
           onHello(request) {
             this.serverSendMsg(JSON.stringify({
               messageType: "hello",
               status: 200,
               uaid: userAgentID,
             }));
           },
+          onUnregister(request) {
+            this.serverSendMsg(JSON.stringify({
+              messageType: "unregister",
+              status: 200,
+              channelID: request.channelID,
+            }));
+          },
         });
       },
     });
 
     const TEST_URL = "https://www.mozilla.org/scope/";
     Assert.equal(false, await push_registration_exists(TEST_URL, ps));
     await db.put({
       channelID,
--- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
@@ -1470,16 +1470,51 @@ var AddonTestUtils = {
           Management.off("ready", listener);
           resolve(extension);
         }
       });
     });
   },
 
   /**
+   * Wait until an extension with a search provider has been loaded.
+   * This should be called after the extension has started, but before shutdown.
+   *
+   * @param {object} extension
+   *        The return value of ExtensionTestUtils.loadExtension.
+   *        For browser tests, see mochitest/tests/SimpleTest/ExtensionTestUtils.js
+   *        For xpcshell tests, see toolkit/components/extensions/ExtensionXPCShellUtils.jsm
+   * @param {object} [options]
+   *        Optional options.
+   * @param {boolean} [options.expectPending = false]
+   *        Whether to expect the search provider to still be starting up.
+   */
+  async waitForSearchProviderStartup(extension, {expectPending = false} = {}) {
+    // In xpcshell tests, equal/ok are defined in the global scope.
+    let {equal, ok} = this.testScope;
+    if (!equal || !ok) {
+      // In mochitests, these are available via Assert.jsm.
+      let {Assert} = this.testScope;
+      equal = Assert.equal.bind(Assert);
+      ok = Assert.ok.bind(Assert);
+    }
+
+    equal(extension.state, "running", "Search provider extension should be running");
+    ok(extension.id, "Extension ID of search provider should be set");
+
+    // The map of promises from browser/components/extensions/parent/ext-chrome-settings-overrides.js
+    let {pendingSearchSetupTasks} = Management.global;
+    let searchStartupPromise = pendingSearchSetupTasks.get(extension.id);
+    if (expectPending) {
+      ok(searchStartupPromise, "Search provider registration should be in progress");
+    }
+    return searchStartupPromise;
+  },
+
+  /**
    * Initializes the URLPreloader, which is required in order to load
    * built_in_addons.json. This has the side-effect of setting
    * preferences which flip Cu.isInAutomation to true.
    */
   initializeURLPreloader() {
     Services.prefs.setBoolPref(PREF_DISABLE_SECURITY, true);
     aomStartup.initializeURLPreloader();
   },