Merge autoland to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 05 Jun 2017 11:59:54 -0400
changeset 589150 cad53f061da634a16ea75887558301b77f65745d
parent 589123 2f77e11bf03d535873759601f73e2e65f464ba26 (current diff)
parent 589149 4a153e231a306587f5ef53b9bfc352d566648db3 (diff)
child 589151 1cd13c469a80985df0e0eae23e70db17f03db6a2
child 589158 14f7721a3ee18258450fd7c83abbef64325d040e
child 589175 955fac9359159d81f3f56af8eb3782afa697a42d
child 589181 5564a0e857cfb572dde0cdd538c453a7f81884f8
child 589183 d68123bb51d35b9a895b10708db49897dda3b903
child 589185 fdfb55bc9a77798f334f6fb1de78c63eb4852659
child 589187 8695099d42e26bddf81215266e057f5bc2b082e1
child 589193 8c09718be78da4f692ee72a3bed2c8b40280d160
child 589199 978581ff6bf41d89d911efab7c2a6798c4eae7b8
child 589220 d35241f988e291345d1db7ec1fd725640ba6ee8d
child 589227 4772cb78e53c106fbe753dbc35b4db67c3366c4c
child 589231 5c66ffe67cbdcf50c4ba9bb0b5ee73f85d624db6
child 589232 abd8b72fce01b29a841d3aed441ad0869460347f
child 589242 8205ff1e03bddd91b348a3b9af03f091c8560f23
child 589243 5451c356550073495e1457101ddb7605a697e6d0
child 589245 856cc26cb791d7bcd5ccb7faecee8b5bfc0c5ed8
child 589247 9d72288355ffd92af951112e90f3cede03f55b93
child 589255 c64ee4786614fae2439c4ff030189a43e2d701dd
child 589258 d4036954f3298c6d3fb66bb3448ce44a77ae9b1b
child 589261 22fd0a9bf6a3624a24239d741698601f51e3d6cc
child 589262 ea26d2c67e995bc9a071f60636738a7cd71bf295
child 589270 99a862c9787b3cf164750490ad514c8834acd896
child 589280 8814375529f130f680f1257c14a678bbc65ce440
child 589383 353ff1d6b3d6b3fb8018dc03c319a70293ee04e9
child 589415 861e5061bff2d0ea9c0c0ffbd273d375b326abf2
child 589434 e9ab07d3a95a5470ccd81761c912ef27da17ed49
child 589440 7bcaf4d09d794b9839e12ea9b3696ea5c2494051
child 589477 d85dd4bcf472366cb5e7995ee9ebeaa2c64ea269
child 589571 f47cc17ab2d7966d71cbd53ad3ad0f3ba86e07a5
child 589588 45061ec3688f5588d8c6be3c970e7623d59c0bd9
child 589589 e48fdf6d478ba0e5bd122b416514cc82ec1595dc
child 589605 520c56042b3c83136f80c6a0ad585731fe272f36
child 589623 e00f18ad25d90e795c0beaefc397474e2bc69060
child 589709 0ce0e4505c283955ac088ec8f27581cb193831b1
child 589763 5c10921fcff2f89838e76ca0b23c17274ee1794c
child 589800 9cf2620e49ca2249a0473c4d730990f2a0d2fa94
child 589801 e3840211b65f398d825d741b7bbf9407bc3b4505
child 589803 72e368ff1b483c7c56260744fbf95c6bca16a162
child 589862 fdb2638e9ab945181fc0a94bca9cab441ac75978
child 590272 ab5ca9b9cef0c6d4fbacdcf92ff74df0ad8be024
child 590279 19074b75203bde71127fea4cd461360729e1d803
child 590307 21d4bf2eec2143e8c208a92756c9e55f51af1219
child 590321 fedaf1464966691c117ee76c5a2d1c4a60742c41
child 590354 0f07758ee0f95dd40372c7cee67375619a819e6f
child 590365 59b2f0812f93e87219e78f281629ff82b3742f52
child 590375 b76bb60b6c9179493ee9c15d32d8b0b6fcfdb605
child 590376 9835d38f71123d84b1b03db9edeafcb07bfb4913
child 590386 78a851011ba0017974bfd32a64b2683d3a33b70f
child 590458 03252e4b5d68fcecb52c515a37fbce2796ea8217
child 590596 7ecff17795223d9fe025e3794c9602a360b58b3e
child 590736 e43ce2e7fff593c3dd8b21aa22bae60146fee732
child 590895 0f8207ccd9c1661f2c0f9538f14054b083c1c3f3
child 590896 2aef255e0c133cea35387adb7f575c32ce6216c8
child 590897 43ea571d18d7bef9cd134378f82fb248e69c53ce
child 590898 276a458fe7649758ffdcc987862d9e6fcb18aacf
child 590920 12e99037bfa9865eda78b357473c49a66933e667
child 591081 bbc57a536d2e4a293303400131cbaba521c0a308
child 591130 9dd8a637f4f9b9bb568fea62cde53033a05e719e
child 591173 beddb257de2659f04a8cb97c2de268cb4c5e3439
child 591345 4ff979dee72e35ab902182e25d6aeadb7ac8496c
child 591360 affb81064afff7f030bca2d51d6dc5cf5c78d90d
child 591521 f7fc62f372a0574c8400e1d944dd2be0e8e8fde7
child 591522 a0ea4c954740a72b179adbdce7f44a703bc8262e
child 591525 e17bfaab90fa88d9809fcb2ce89f44bff1c57e94
child 591581 7df8236c9988a674ae128faf63b949fd711ab4e0
child 591813 28ba05b8fb74c693680d8c896c7d5596a2999cf9
child 591947 561e8e6598fa98d4b7e5b2f21a0587ba2df3d569
child 597749 81d0d9b8010d3bdbedc8794ec64a6134127efdab
child 611551 12a17d1ce8e218ba3570fcd38d24e71b76dd39e9
push id62255
push userbmo:dkeeler@mozilla.com
push dateMon, 05 Jun 2017 17:15:48 +0000
reviewersmerge
milestone55.0a1
Merge autoland to m-c. a=merge
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -4023,31 +4023,26 @@ function FillHistoryMenu(aParent) {
 
     for (let j = end - 1; j >= start; j--) {
       let entry = sessionHistory.entries[j];
       let uri = entry.url;
 
       let item = existingIndex < children.length ?
                    children[existingIndex] : document.createElement("menuitem");
 
-      let entryURI = Services.io.newURI(entry.url, entry.charset);
       item.setAttribute("uri", uri);
       item.setAttribute("label", entry.title || uri);
       item.setAttribute("index", j);
 
       // Cache this so that gotoHistoryIndex doesn't need the original index
       item.setAttribute("historyindex", j - index);
 
       if (j != index) {
-        PlacesUtils.favicons.getFaviconURLForPage(entryURI, function(aURI) {
-          if (aURI) {
-            let iconURL = PlacesUtils.favicons.getFaviconLinkForIcon(aURI).spec;
-            item.style.listStyleImage = "url(" + iconURL + ")";
-          }
-        });
+        item.setAttribute("image",
+                          PlacesUtils.urlWithSizeRef(window, "page-icon:" + uri, 16));
       }
 
       if (j < index) {
         item.className = "unified-nav-back menuitem-iconic menuitem-with-favicon";
         item.setAttribute("tooltiptext", tooltipBack);
       } else if (j == index) {
         item.setAttribute("type", "radio");
         item.setAttribute("checked", "true");
--- a/browser/components/places/tests/browser/browser_toolbarbutton_menu_context.js
+++ b/browser/components/places/tests/browser/browser_toolbarbutton_menu_context.js
@@ -4,17 +4,19 @@ var BMB_showAllBookmarks = document.getE
 var contextMenu = document.getElementById("placesContext");
 var newBookmarkItem = document.getElementById("placesContext_new:bookmark");
 
 waitForExplicitFinish();
 add_task(async function testPopup() {
   info("Checking popup context menu before moving the bookmarks button");
   await checkPopupContextMenu();
   let pos = CustomizableUI.getPlacementOfWidget("bookmarks-menu-button").position;
-  CustomizableUI.addWidgetToArea("bookmarks-menu-button", CustomizableUI.AREA_PANEL);
+  let target = gPhotonStructure ? CustomizableUI.AREA_FIXED_OVERFLOW_PANEL
+                                : CustomizableUI.AREA_PANEL;
+  CustomizableUI.addWidgetToArea("bookmarks-menu-button", target);
   CustomizableUI.addWidgetToArea("bookmarks-menu-button", CustomizableUI.AREA_NAVBAR, pos);
   info("Checking popup context menu after moving the bookmarks button");
   await checkPopupContextMenu();
 });
 
 async function checkPopupContextMenu() {
   let dropmarker = document.getAnonymousElementByAttribute(bookmarksMenuButton, "anonid", "dropmarker");
   BMB_menuPopup.setAttribute("style", "transition: none;");
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -28,16 +28,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/UITelemetry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
   "resource:///modules/BrowserUITelemetry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderParent",
   "resource:///modules/ReaderParent.jsm");
 
+XPCOMUtils.defineLazyPreferenceGetter(this, "gPhotonStructure", "browser.photon.structure.enabled");
+
 // See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
 const PREF_LOG_LEVEL      = "browser.uitour.loglevel";
 const PREF_SEENPAGEIDS    = "browser.uitour.seenPageIDs";
 
 const BACKGROUND_PAGE_ACTIONS_ALLOWED = new Set([
   "forceShowReaderIcon",
   "getConfiguration",
   "getTreatmentTag",
@@ -91,75 +93,116 @@ this.UITour = {
   },
 
   _annotationPanelMutationObservers: new WeakMap(),
 
   highlightEffects: ["random", "wobble", "zoom", "color"],
   targets: new Map([
     ["accountStatus", {
       query: (aDocument) => {
+        let prefix = gPhotonStructure ? "appMenu" : "PanelUI"
         // If the user is logged in, use the avatar element.
-        let fxAFooter = aDocument.getElementById("PanelUI-fxa-container");
+        let fxAFooter = aDocument.getElementById(prefix + "-fxa-container");
         if (fxAFooter.getAttribute("fxastatus")) {
-          return aDocument.getElementById("PanelUI-fxa-avatar");
+          return aDocument.getElementById(prefix + "-fxa-avatar");
         }
 
         // Otherwise use the sync setup icon.
-        let statusButton = aDocument.getElementById("PanelUI-fxa-label");
+        let statusButton = aDocument.getElementById(prefix + "-fxa-label");
         return aDocument.getAnonymousElementByAttribute(statusButton,
                                                         "class",
                                                         "toolbarbutton-icon");
       },
-      // This is a fake widgetName starting with the "PanelUI-" prefix so we know
+      // This is a fake widgetName starting with the "PanelUI-"/"appMenu-" prefix so we know
       // to automatically open the appMenu when annotating this target.
-      widgetName: "PanelUI-fxa-label",
+      get widgetName() {
+        return gPhotonStructure ? "appMenu-fxa-label" : "PanelUI-fxa-label";
+      },
     }],
-    ["addons",      {query: "#add-ons-button"}],
+    ["addons", {
+      query(aDocument) {
+        let buttonId = gPhotonStructure ? "appMenu-addons-button" : "add-ons-button";
+        return aDocument.getElementById(buttonId);
+      }
+    }],
     ["appMenu",     {
       addTargetListener: (aDocument, aCallback) => {
-        let panelPopup = aDocument.getElementById("PanelUI-popup");
+        let panelPopup = aDocument.defaultView.PanelUI.panel;
         panelPopup.addEventListener("popupshown", aCallback);
       },
       query: "#PanelUI-button",
       removeTargetListener: (aDocument, aCallback) => {
-        let panelPopup = aDocument.getElementById("PanelUI-popup");
+        let panelPopup = aDocument.defaultView.PanelUI.panel;
         panelPopup.removeEventListener("popupshown", aCallback);
       },
     }],
     ["backForward", {
       query: "#back-button",
       widgetName: "urlbar-container",
     }],
     ["bookmarks",   {query: "#bookmarks-menu-button"}],
     ["controlCenter-trackingUnblock", controlCenterTrackingToggleTarget(true)],
     ["controlCenter-trackingBlock", controlCenterTrackingToggleTarget(false)],
     ["customize",   {
       query: (aDocument) => {
+        if (gPhotonStructure) {
+          return aDocument.getElementById("appMenu-customize-button");
+        }
         let customizeButton = aDocument.getElementById("PanelUI-customize");
         return aDocument.getAnonymousElementByAttribute(customizeButton,
                                                         "class",
                                                         "toolbarbutton-icon");
       },
-      widgetName: "PanelUI-customize",
+      get widgetName() {
+        return gPhotonStructure ? "appMenu-customize-button" : "PanelUI-customize";
+      },
     }],
-    ["devtools",    {query: "#developer-button"}],
-    ["help",        {query: "#PanelUI-help"}],
+    ["devtools", {
+      query(aDocument) {
+        let button = aDocument.getElementById("developer-button");
+        if (button || !gPhotonStructure) {
+          return button;
+        }
+        return aDocument.getElementById("appMenu-developer-button");
+      },
+      get widgetName() {
+        return gPhotonStructure ? "appMenu-developer-button" : "developer-button";
+      },
+    }],
+    ["help", {
+      query: (aDocument) => {
+        let buttonId = gPhotonStructure ? "appMenu-help-button" : "PanelUI-help";
+        return aDocument.getElementById(buttonId);
+      }
+    }],
     ["home",        {query: "#home-button"}],
     ["forget", {
       allowAdd: true,
       query: "#panic-button",
       widgetName: "panic-button",
     }],
     ["pocket", {
       allowAdd: true,
       query: "#pocket-button",
       widgetName: "pocket-button",
     }],
-    ["privateWindow",  {query: "#privatebrowsing-button"}],
-    ["quit",        {query: "#PanelUI-quit"}],
+    ["privateWindow", {
+      query(aDocument) {
+        let buttonId = gPhotonStructure ? "appMenu-private-window-button"
+                                        : "privatebrowsing-button";
+        return aDocument.getElementById(buttonId);
+      }
+    }],
+    ["quit", {
+      query(aDocument) {
+        let buttonId = gPhotonStructure ? "appMenu-quit-button"
+                                        : "PanelUI-quit";
+        return aDocument.getElementById(buttonId);
+      }
+    }],
     ["readerMode-urlBar", {query: "#reader-mode-button"}],
     ["search",      {
       infoPanelOffsetX: 18,
       infoPanelPosition: "after_start",
       query: "#searchbar",
       widgetName: "search-container",
     }],
     ["searchIcon", {
@@ -939,17 +982,18 @@ this.UITour = {
 
     let targetElement = aTarget.node;
     // Use the widget for filtering if it exists since the target may be the icon inside.
     if (aTarget.widgetName) {
       targetElement = aTarget.node.ownerDocument.getElementById(aTarget.widgetName);
     }
 
     // Handle the non-customizable buttons at the bottom of the menu which aren't proper widgets.
-    return targetElement.id.startsWith("PanelUI-")
+    let prefix = gPhotonStructure ? "appMenu-" : "PanelUI-";
+    return targetElement.id.startsWith(prefix)
              && targetElement.id != "PanelUI-button";
   },
 
   /**
    * Called before opening or after closing a highlight or info panel to see if
    * we need to open or close the appMenu to see the annotation's anchor.
    */
   _setAppMenuStateForAnnotation(aWindow, aAnnotationType, aShouldOpenForHighlight, aCallback = null) {
--- a/browser/components/uitour/test/browser_UITour.js
+++ b/browser/components/uitour/test/browser_UITour.js
@@ -115,17 +115,17 @@ var tests = [
       is(Math.round(highlightRect.height), maxDimension, "The height of the highlight should be equal to the largest dimension of the target");
       is(Math.round(highlightRect.height), Math.round(highlightRect.width), "The height and width of the highlight should be the same to create a circle");
       is(highlight.style.borderRadius, "100%", "The border-radius should be 100% to create a circle");
       done();
     }
     let highlight = document.getElementById("UITourHighlight");
     is_element_hidden(highlight, "Highlight should initially be hidden");
 
-    gContentAPI.showHighlight("addons");
+    gContentAPI.showHighlight("home");
     waitForElementToBeVisible(highlight, check_highlight_size, "Highlight should be shown after showHighlight()");
   },
   function test_highlight_customize_auto_open_close(done) {
     let highlight = document.getElementById("UITourHighlight");
     gContentAPI.showHighlight("customize");
     waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
       isnot(PanelUI.panel.state, "closed", "Panel should have opened");
 
--- a/browser/components/uitour/test/browser_UITour_availableTargets.js
+++ b/browser/components/uitour/test/browser_UITour_availableTargets.js
@@ -1,15 +1,17 @@
 "use strict";
 
 var gTestTab;
 var gContentAPI;
 var gContentWindow;
 
 var hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled");
+var hasQuit = !Services.prefs.getBoolPref("browser.photon.structure.enabled") ||
+              false; // Update this with AppConstants.platform != "macosx" after bug 1368734 lands;
 
 requestLongerTimeout(2);
 add_task(setup_UITourTest);
 
 add_UITour_task(async function test_availableTargets() {
   let data = await getConfigurationPromise("availableTargets");
   ok_targets(data, [
     "accountStatus",
@@ -18,17 +20,17 @@ add_UITour_task(async function test_avai
     "backForward",
     "bookmarks",
     "customize",
     "help",
     "home",
     "devtools",
       ...(hasPocket ? ["pocket"] : []),
     "privateWindow",
-    "quit",
+      ...(hasQuit ? ["quit"] : []),
     "readerMode-urlBar",
     "search",
     "searchIcon",
     "trackingProtection",
     "urlbar",
   ]);
 
   ok(UITour.availableTargetsCache.has(window),
@@ -46,17 +48,17 @@ add_UITour_task(async function test_avai
     "appMenu",
     "backForward",
     "customize",
     "help",
     "devtools",
     "home",
       ...(hasPocket ? ["pocket"] : []),
     "privateWindow",
-    "quit",
+      ...(hasQuit ? ["quit"] : []),
     "readerMode-urlBar",
     "search",
     "searchIcon",
     "trackingProtection",
     "urlbar",
   ]);
 
   ok(UITour.availableTargetsCache.has(window),
@@ -79,17 +81,17 @@ add_UITour_task(async function test_avai
     "backForward",
     "bookmarks",
     "customize",
     "help",
     "home",
     "devtools",
       ...(hasPocket ? ["pocket"] : []),
     "privateWindow",
-    "quit",
+      ...(hasQuit ? ["quit"] : []),
     "readerMode-urlBar",
     "trackingProtection",
     "urlbar",
   ]);
 
   CustomizableUI.reset();
 });
 
--- a/browser/components/uitour/test/browser_UITour_detach_tab.js
+++ b/browser/components/uitour/test/browser_UITour_detach_tab.js
@@ -76,17 +76,19 @@ var tests = [
     // reasons.
     gTestTab = gContentWindow.gBrowser.selectedTab;
 
     let shownPromise = promisePanelShown(gContentWindow);
     gContentAPI.showMenu("appMenu");
     await shownPromise;
 
     isnot(gContentWindow.PanelUI.panel.state, "closed", "Panel should be open");
-    ok(gContentWindow.PanelUI.contents.children.length > 0, "Panel contents should have children");
+    if (!gContentWindow.gPhotonStructure) {
+      ok(gContentWindow.PanelUI.contents.children.length > 0, "Panel contents should have children");
+    }
     gContentAPI.hideHighlight();
     gContentAPI.hideMenu("appMenu");
     gTestTab = null;
 
     Services.obs.addObserver(onDOMWindowDestroyed, "dom-window-destroyed");
     gContentWindow.close();
 
     await windowDestroyedDeferred.promise;
--- a/browser/components/uitour/test/browser_fxa.js
+++ b/browser/components/uitour/test/browser_fxa.js
@@ -34,17 +34,19 @@ var tests = [
   taskify(async function test_highlight_accountStatus_loggedIn() {
     await setSignedInUser();
     let userData = await fxAccounts.getSignedInUser();
     isnot(userData, null, "Logged in now");
     gSync.updateAllUI(UIState.get());
     await showMenuPromise("appMenu");
     await showHighlightPromise("accountStatus");
     let highlight = document.getElementById("UITourHighlightContainer");
-    is(highlight.popupBoxObject.anchorNode.id, "PanelUI-fxa-avatar", "Anchored on avatar");
+    let photon = Services.prefs.getBoolPref("browser.photon.structure.enabled");
+    let expectedTarget = photon ? "appMenu-fxa-avatar" : "PanelUI-fxa-avatar";
+    is(highlight.popupBoxObject.anchorNode.id, expectedTarget, "Anchored on avatar");
     is(highlight.getAttribute("targetName"), "accountStatus", "Correct highlight target");
   }),
 ];
 
 // Helpers copied from browser_aboutAccounts.js
 // watch out - these will fire observers which if you aren't careful, may
 // interfere with the tests.
 function setSignedInUser(data) {
--- a/browser/extensions/webcompat-reporter/test/browser/browser_button_state.js
+++ b/browser/extensions/webcompat-reporter/test/browser/browser_button_state.js
@@ -1,28 +1,29 @@
 const REPORTABLE_PAGE = "http://example.com/";
 const REPORTABLE_PAGE2 = "https://example.com/";
 const NONREPORTABLE_PAGE = "about:blank";
 
 /* Test that the Report Site Issue button is enabled for http and https tabs,
    on page load, or TabSelect, and disabled for everything else. */
 add_task(async function test_button_state_disabled() {
+  CustomizableUI.addWidgetToArea("webcompat-reporter-button", "nav-bar");
   let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, REPORTABLE_PAGE);
-  await PanelUI.show();
   is(isButtonDisabled(), false, "Check that button is enabled for reportable schemes on tab load");
 
   let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, NONREPORTABLE_PAGE);
   is(isButtonDisabled(), true, "Check that button is disabled for non-reportable schemes on tab load");
 
   let tab3 = await BrowserTestUtils.openNewForegroundTab(gBrowser, REPORTABLE_PAGE2);
   is(isButtonDisabled(), false, "Check that button is enabled for reportable schemes on tab load");
 
 
   await BrowserTestUtils.switchTab(gBrowser, tab2);
   is(isButtonDisabled(), true, "Check that button is disabled for non-reportable schemes on TabSelect");
 
   await BrowserTestUtils.switchTab(gBrowser, tab1);
   is(isButtonDisabled(), false, "Check that button is enabled for reportable schemes on TabSelect");
 
+  CustomizableUI.reset();
   await BrowserTestUtils.removeTab(tab1);
   await BrowserTestUtils.removeTab(tab2);
   await BrowserTestUtils.removeTab(tab3);
 });
--- a/browser/extensions/webcompat-reporter/test/browser/browser_report_site_issue.js
+++ b/browser/extensions/webcompat-reporter/test/browser/browser_report_site_issue.js
@@ -1,18 +1,18 @@
 /* Test that clicking on the Report Site Issue button opens a new tab
    and sends a postMessaged blob to it. */
 add_task(async function test_screenshot() {
+  CustomizableUI.addWidgetToArea("webcompat-reporter-button", "nav-bar");
   requestLongerTimeout(2);
 
   // ./head.js sets the value for PREF_WC_REPORTER_ENDPOINT
   await SpecialPowers.pushPrefEnv({set: [[PREF_WC_REPORTER_ENDPOINT, NEW_ISSUE_PAGE]]});
 
   let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE);
-  await PanelUI.show();
 
   let webcompatButton = document.getElementById("webcompat-reporter-button");
   ok(webcompatButton, "Report Site Issue button exists.");
 
   let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
   webcompatButton.click();
   let tab2 = await newTabPromise;
 
@@ -25,9 +25,10 @@ add_task(async function test_screenshot(
     is(urlParam, args.TEST_PAGE, "Reported page is correctly added to the url param");
 
     is(preview.innerText, "Pass", "A Blob object was successfully transferred to the test page.")
     ok(preview.style.backgroundImage.startsWith("url(\"data:image/png;base64,iVBOR"), "A green screenshot was successfully postMessaged");
   });
 
   await BrowserTestUtils.removeTab(tab2);
   await BrowserTestUtils.removeTab(tab1);
+  CustomizableUI.reset();
 });
--- a/browser/modules/BrowserUITelemetry.jsm
+++ b/browser/modules/BrowserUITelemetry.jsm
@@ -22,37 +22,40 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "UITour",
   "resource:///modules/UITour.jsm");
 XPCOMUtils.defineLazyGetter(this, "Timer", function() {
   let timer = {};
   Cu.import("resource://gre/modules/Timer.jsm", timer);
   return timer;
 });
 
+XPCOMUtils.defineLazyPreferenceGetter(this, "gPhotonStructure", "browser.photon.structure.enabled");
+
 const MS_SECOND = 1000;
 const MS_MINUTE = MS_SECOND * 60;
 const MS_HOUR = MS_MINUTE * 60;
 
+const LEGACY_PANEL_PLACEMENTS = [
+  "edit-controls",
+  "zoom-controls",
+  "new-window-button",
+  "privatebrowsing-button",
+  "save-page-button",
+  "print-button",
+  "history-panelmenu",
+  "fullscreen-button",
+  "find-button",
+  "preferences-button",
+  "add-ons-button",
+  "sync-button",
+  "developer-button"
+];
+
 XPCOMUtils.defineLazyGetter(this, "DEFAULT_AREA_PLACEMENTS", function() {
   let result = {
-    "PanelUI-contents": [
-      "edit-controls",
-      "zoom-controls",
-      "new-window-button",
-      "privatebrowsing-button",
-      "save-page-button",
-      "print-button",
-      "history-panelmenu",
-      "fullscreen-button",
-      "find-button",
-      "preferences-button",
-      "add-ons-button",
-      "sync-button",
-      "developer-button",
-    ],
     "nav-bar": [
       "urlbar-container",
       "search-container",
       "bookmarks-menu-button",
       "pocket-button",
       "downloads-button",
       "home-button",
       "social-share-button",
@@ -68,27 +71,32 @@ XPCOMUtils.defineLazyGetter(this, "DEFAU
       "new-tab-button",
       "alltabs-button",
     ],
     "PersonalToolbar": [
       "personal-bookmarks",
     ],
   };
 
-  let showCharacterEncoding = Services.prefs.getComplexValue(
-    "browser.menu.showCharacterEncoding",
-    Ci.nsIPrefLocalizedString
-  ).data;
-  if (showCharacterEncoding == "true") {
-    result["PanelUI-contents"].push("characterencoding-button");
-  }
+  if (gPhotonStructure) {
+    result["widget-overflow-fixed-list"] = [];
+  } else {
+    result["PanelUI-contents"] = LEGACY_PANEL_PLACEMENTS;
+    let showCharacterEncoding = Services.prefs.getComplexValue(
+      "browser.menu.showCharacterEncoding",
+      Ci.nsIPrefLocalizedString
+    ).data;
+    if (showCharacterEncoding == "true") {
+      result["PanelUI-contents"].push("characterencoding-button");
+    }
 
-  if (!AppConstants.RELEASE_OR_BETA) {
-    if (Services.prefs.getBoolPref("extensions.webcompat-reporter.enabled")) {
-      result["PanelUI-contents"].push("webcompat-reporter-button");
+    if (!AppConstants.RELEASE_OR_BETA) {
+      if (Services.prefs.getBoolPref("extensions.webcompat-reporter.enabled")) {
+        result["PanelUI-contents"].push("webcompat-reporter-button");
+      }
     }
   }
 
   return result;
 });
 
 XPCOMUtils.defineLazyGetter(this, "DEFAULT_AREAS", function() {
   return Object.keys(DEFAULT_AREA_PLACEMENTS);
@@ -99,17 +107,20 @@ XPCOMUtils.defineLazyGetter(this, "PALET
     "open-file-button",
     "developer-button",
     "feed-button",
     "email-link-button",
     "containers-panelmenu",
   ];
 
   let panelPlacements = DEFAULT_AREA_PLACEMENTS["PanelUI-contents"];
-  if (panelPlacements.indexOf("characterencoding-button") == -1) {
+  if (!panelPlacements) {
+    result.push(...LEGACY_PANEL_PLACEMENTS);
+  }
+  if (!panelPlacements || !panelPlacements.includes("characterencoding-button")) {
     result.push("characterencoding-button");
   }
 
   if (Services.prefs.getBoolPref("privacy.panicButton.enabled")) {
     result.push("panic-button");
   }
 
   return result;
@@ -463,34 +474,34 @@ this.BrowserUITelemetry = {
     if (item.id == "bookmarks-menu-button" ||
         getIDBasedOnFirstIDedAncestor(item) == "bookmarks-menu-button") {
       this._bookmarksMenuButtonMouseUp(aEvent);
       return;
     }
 
     // Perhaps we're seeing one of the default toolbar items
     // being clicked.
-    if (ALL_BUILTIN_ITEMS.indexOf(item.id) != -1) {
+    if (ALL_BUILTIN_ITEMS.includes(item.id)) {
       // Base case - we clicked directly on one of our built-in items,
       // and we can go ahead and register that click.
       this._countMouseUpEvent("click-builtin-item", item.id, aEvent.button);
       return;
     }
 
     // If not, we need to check if the item's anonid is in our list
     // of built-in items to check.
-    if (ALL_BUILTIN_ITEMS.indexOf(item.getAttribute("anonid")) != -1) {
+    if (ALL_BUILTIN_ITEMS.includes(item.getAttribute("anonid"))) {
       this._countMouseUpEvent("click-builtin-item", item.getAttribute("anonid"), aEvent.button);
       return;
     }
 
     // If not, we need to check if one of the ancestors of the clicked
     // item is in our list of built-in items to check.
     let candidate = getIDBasedOnFirstIDedAncestor(item);
-    if (ALL_BUILTIN_ITEMS.indexOf(candidate) != -1) {
+    if (ALL_BUILTIN_ITEMS.includes(candidate)) {
       this._countMouseUpEvent("click-builtin-item", candidate, aEvent.button);
     }
   },
 
   _getWindowMeasurements(aWindow, searchResult) {
     let document = aWindow.document;
     let result = {};
 
--- a/browser/modules/test/browser/browser_BrowserUITelemetry_syncedtabs.js
+++ b/browser/modules/test/browser/browser_BrowserUITelemetry_syncedtabs.js
@@ -60,32 +60,33 @@ function promiseTabsUpdated() {
 
 add_task(async function test_menu() {
   // Reset BrowserUITelemetry's world.
   BUIT._countableEvents = {};
 
   let tabsUpdated = promiseTabsUpdated();
 
   // check the button's functionality
-  await PanelUI.show();
+  CustomizableUI.addWidgetToArea("sync-button", "nav-bar");
 
   let syncButton = document.getElementById("sync-button");
   syncButton.click();
 
   await tabsUpdated;
   // Get our 1 tab and click on it.
   let tabList = document.getElementById("PanelUI-remotetabs-tabslist");
   let tabEntry = tabList.firstChild.nextSibling;
   tabEntry.click();
 
   let counts = BUIT._countableEvents[BUIT.currentBucket];
   Assert.deepEqual(counts, {
     "click-builtin-item": { "sync-button": { left: 1 } },
     "synced-tabs": { open: { "toolbarbutton-subview": 1 } },
   });
+  CustomizableUI.reset();
 });
 
 add_task(async function test_sidebar() {
   // Reset BrowserUITelemetry's world.
   BUIT._countableEvents = {};
 
   await SidebarUI.show("viewTabsSidebar");
 
--- a/devtools/server/actors/script.js
+++ b/devtools/server/actors/script.js
@@ -1956,21 +1956,17 @@ const ThreadActor = ActorClassWithSpec(t
         // this call will replace the previous set of source actors for this source
         // with a new one. If the source actors have not been replaced by the time
         // we try to reset the breakpoints below, their location objects will still
         // point to the old set of source actors, which point to different
         // scripts.
         this.unsafeSynchronize(sourceActorsCreated);
       }
 
-      for (let _actor of bpActors) {
-        // XXX bug 1142115: We do async work in here, so we need to create a fresh
-        // binding because for/of does not yet do that in SpiderMonkey.
-        let actor = _actor;
-
+      for (const actor of bpActors) {
         if (actor.isPending) {
           promises.push(actor.originalLocation.originalSourceActor._setBreakpoint(actor));
         } else {
           promises.push(
             this.sources.getAllGeneratedLocations(actor.originalLocation).then(
               (generatedLocations) => {
                 if (generatedLocations.length > 0 &&
                     generatedLocations[0].generatedSourceActor
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -9584,71 +9584,16 @@ nsDocShell::CheckLoadingPermissions()
 
   return NS_ERROR_DOM_PROP_ACCESS_DENIED;
 }
 
 //*****************************************************************************
 // nsDocShell: Site Loading
 //*****************************************************************************
 
-namespace {
-
-#ifdef MOZ_PLACES
-// Callback used by CopyFavicon to inform the favicon service that one URI
-// (mNewURI) has the same favicon URI (OnComplete's aFaviconURI) as another.
-class nsCopyFaviconCallback final : public nsIFaviconDataCallback
-{
-public:
-  NS_DECL_ISUPPORTS
-
-  nsCopyFaviconCallback(mozIAsyncFavicons* aSvc,
-                        nsIURI* aNewURI,
-                        nsIPrincipal* aLoadingPrincipal,
-                        bool aInPrivateBrowsing)
-    : mSvc(aSvc)
-    , mNewURI(aNewURI)
-    , mLoadingPrincipal(aLoadingPrincipal)
-    , mInPrivateBrowsing(aInPrivateBrowsing)
-  {
-  }
-
-  NS_IMETHOD
-  OnComplete(nsIURI* aFaviconURI, uint32_t aDataLen,
-             const uint8_t* aData, const nsACString& aMimeType, uint16_t aWidth) override
-  {
-    // Continue only if there is an associated favicon.
-    if (!aFaviconURI) {
-      return NS_OK;
-    }
-
-    MOZ_ASSERT(aDataLen == 0,
-               "We weren't expecting the callback to deliver data.");
-
-    nsCOMPtr<mozIPlacesPendingOperation> po;
-    return mSvc->SetAndFetchFaviconForPage(
-      mNewURI, aFaviconURI, false,
-      mInPrivateBrowsing ? nsIFaviconService::FAVICON_LOAD_PRIVATE :
-                           nsIFaviconService::FAVICON_LOAD_NON_PRIVATE,
-      nullptr, mLoadingPrincipal, getter_AddRefs(po));
-  }
-
-private:
-  ~nsCopyFaviconCallback() {}
-
-  nsCOMPtr<mozIAsyncFavicons> mSvc;
-  nsCOMPtr<nsIURI> mNewURI;
-  nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
-  bool mInPrivateBrowsing;
-};
-
-NS_IMPL_ISUPPORTS(nsCopyFaviconCallback, nsIFaviconDataCallback)
-#endif
-
-} // namespace
-
 void
 nsDocShell::CopyFavicon(nsIURI* aOldURI,
                         nsIURI* aNewURI,
                         nsIPrincipal* aLoadingPrincipal,
                         bool aInPrivateBrowsing)
 {
   if (XRE_IsContentProcess()) {
     dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
@@ -9662,21 +9607,19 @@ nsDocShell::CopyFavicon(nsIURI* aOldURI,
     }
     return;
   }
 
 #ifdef MOZ_PLACES
   nsCOMPtr<mozIAsyncFavicons> favSvc =
     do_GetService("@mozilla.org/browser/favicon-service;1");
   if (favSvc) {
-    nsCOMPtr<nsIFaviconDataCallback> callback =
-      new nsCopyFaviconCallback(favSvc, aNewURI,
-                                aLoadingPrincipal,
-                                aInPrivateBrowsing);
-    favSvc->GetFaviconURLForPage(aOldURI, callback, 0);
+    favSvc->CopyFavicons(aOldURI, aNewURI,
+      aInPrivateBrowsing ? nsIFaviconService::FAVICON_LOAD_PRIVATE
+                         : nsIFaviconService::FAVICON_LOAD_NON_PRIVATE, nullptr);
   }
 #endif
 }
 
 class InternalLoadEvent : public Runnable
 {
 public:
   InternalLoadEvent(nsDocShell* aDocShell, nsIURI* aURI,
--- a/dom/media/fmp4/MP4Demuxer.cpp
+++ b/dom/media/fmp4/MP4Demuxer.cpp
@@ -52,16 +52,18 @@ public:
 
   RefPtr<SkipAccessPointPromise>
   SkipToNextRandomAccessPoint(const media::TimeUnit& aTimeThreshold) override;
 
   media::TimeIntervals GetBuffered() override;
 
   void BreakCycles() override;
 
+  void NotifyDataRemoved();
+
 private:
   friend class MP4Demuxer;
   void NotifyDataArrived();
   already_AddRefed<MediaRawData> GetNextSample();
   void EnsureUpToDateIndex();
   void SetNextKeyFrameTime();
   RefPtr<MP4Demuxer> mParent;
   RefPtr<mp4_demuxer::ResourceStream> mStream;
@@ -323,20 +325,20 @@ MP4Demuxer::NotifyDataArrived()
     dmx->NotifyDataArrived();
   }
 }
 
 void
 MP4Demuxer::NotifyDataRemoved()
 {
   for (auto& dmx : mAudioDemuxers) {
-    dmx->NotifyDataArrived();
+    dmx->NotifyDataRemoved();
   }
   for (auto& dmx : mVideoDemuxers) {
-    dmx->NotifyDataArrived();
+    dmx->NotifyDataRemoved();
   }
 }
 
 UniquePtr<EncryptionInfo>
 MP4Demuxer::GetCrypto()
 {
   UniquePtr<EncryptionInfo> crypto;
   if (!mCryptoInitData.IsEmpty()) {
@@ -606,16 +608,29 @@ MP4TrackDemuxer::GetBuffered()
 
 void
 MP4TrackDemuxer::NotifyDataArrived()
 {
   mNeedReIndex = true;
 }
 
 void
+MP4TrackDemuxer::NotifyDataRemoved()
+{
+  AutoPinned<MediaResource> resource(mParent->mResource);
+  MediaByteRangeSet byteRanges;
+  nsresult rv = resource->GetCachedRanges(byteRanges);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+  mIndex->UpdateMoofIndex(byteRanges, true /* can evict */);
+  mNeedReIndex = false;
+}
+
+void
 MP4TrackDemuxer::BreakCycles()
 {
   mParent = nullptr;
 }
 
 } // namespace mozilla
 
 #undef LOG
--- a/media/libstagefright/binding/Index.cpp
+++ b/media/libstagefright/binding/Index.cpp
@@ -78,16 +78,22 @@ RangeFinder::Contains(MediaByteRange aBy
   return false;
 }
 
 SampleIterator::SampleIterator(Index* aIndex)
   : mIndex(aIndex)
   , mCurrentMoof(0)
   , mCurrentSample(0)
 {
+  mIndex->RegisterIterator(this);
+}
+
+SampleIterator::~SampleIterator()
+{
+  mIndex->UnregisterIterator(this);
 }
 
 already_AddRefed<MediaRawData> SampleIterator::GetNext()
 {
   Sample* s(Get());
   if (!s) {
     return nullptr;
   }
@@ -382,21 +388,46 @@ Index::Index(const IndiceWrapper& aIndic
   }
 }
 
 Index::~Index() {}
 
 void
 Index::UpdateMoofIndex(const MediaByteRangeSet& aByteRanges)
 {
+  UpdateMoofIndex(aByteRanges, false);
+}
+
+void
+Index::UpdateMoofIndex(const MediaByteRangeSet& aByteRanges, bool aCanEvict)
+{
   if (!mMoofParser) {
     return;
   }
-
-  mMoofParser->RebuildFragmentedIndex(aByteRanges);
+  size_t moofs = mMoofParser->Moofs().Length();
+  bool canEvict = aCanEvict && moofs > 1;
+  if (canEvict) {
+    // Check that we can trim the mMoofParser. We can only do so if all
+    // iterators have demuxed all possible samples.
+    for (const SampleIterator* iterator : mIterators) {
+      if ((iterator->mCurrentSample == 0 && iterator->mCurrentMoof == moofs) ||
+          iterator->mCurrentMoof == moofs - 1) {
+        continue;
+      }
+      canEvict = false;
+      break;
+    }
+  }
+  mMoofParser->RebuildFragmentedIndex(aByteRanges, &canEvict);
+  if (canEvict) {
+    // The moofparser got trimmed. Adjust all registered iterators.
+    for (SampleIterator* iterator : mIterators) {
+      iterator->mCurrentMoof -= moofs - 1;
+    }
+  }
 }
 
 Microseconds
 Index::GetEndCompositionIfBuffered(const MediaByteRangeSet& aByteRanges)
 {
   FallibleTArray<Sample>* index;
   if (mMoofParser) {
     if (!mMoofParser->ReachedEnd() || mMoofParser->Moofs().IsEmpty()) {
@@ -554,9 +585,22 @@ Index::GetEvictionOffset(Microseconds aT
       const Sample& sample = mIndex[i];
       if (aTime >= sample.mCompositionRange.end) {
         offset = std::min(offset, uint64_t(sample.mByteRange.mEnd));
       }
     }
   }
   return offset;
 }
+
+void
+Index::RegisterIterator(SampleIterator* aIterator)
+{
+  mIterators.AppendElement(aIterator);
 }
+
+void
+Index::UnregisterIterator(SampleIterator* aIterator)
+{
+  mIterators.RemoveElement(aIterator);
+}
+
+}
--- a/media/libstagefright/binding/MoofParser.cpp
+++ b/media/libstagefright/binding/MoofParser.cpp
@@ -26,24 +26,39 @@ namespace mp4_demuxer
 {
 
 using namespace stagefright;
 using namespace mozilla;
 
 const uint32_t kKeyIdSize = 16;
 
 bool
-MoofParser::RebuildFragmentedIndex(
-  const MediaByteRangeSet& aByteRanges)
+MoofParser::RebuildFragmentedIndex(const MediaByteRangeSet& aByteRanges)
 {
   BoxContext context(mSource, aByteRanges);
   return RebuildFragmentedIndex(context);
 }
 
 bool
+MoofParser::RebuildFragmentedIndex(
+  const MediaByteRangeSet& aByteRanges, bool* aCanEvict)
+{
+  MOZ_ASSERT(aCanEvict);
+  if (*aCanEvict && mMoofs.Length() > 1) {
+    MOZ_ASSERT(mMoofs.Length() == mMediaRanges.Length());
+    mMoofs.RemoveElementsAt(0, mMoofs.Length() - 1);
+    mMediaRanges.RemoveElementsAt(0, mMediaRanges.Length() - 1);
+    *aCanEvict = true;
+  } else {
+    *aCanEvict = false;
+  }
+  return RebuildFragmentedIndex(aByteRanges);
+}
+
+bool
 MoofParser::RebuildFragmentedIndex(BoxContext& aContext)
 {
   bool foundValidMoof = false;
   bool foundMdat = false;
 
   for (Box box(&aContext, mOffset); box.IsAvailable(); box = box.Next()) {
     if (box.IsType("moov") && mInitRange.IsEmpty()) {
       mInitRange = MediaByteRange(0, box.Range().mEnd);
--- a/media/libstagefright/binding/include/mp4_demuxer/Index.h
+++ b/media/libstagefright/binding/include/mp4_demuxer/Index.h
@@ -22,27 +22,28 @@ class Index;
 class IndiceWrapper;
 
 typedef int64_t Microseconds;
 
 class SampleIterator
 {
 public:
   explicit SampleIterator(Index* aIndex);
+  ~SampleIterator();
   already_AddRefed<mozilla::MediaRawData> GetNext();
   void Seek(Microseconds aTime);
   Microseconds GetNextKeyframeTime();
-
 private:
   Sample* Get();
 
   CencSampleEncryptionInfoEntry* GetSampleEncryptionEntry();
 
   void Next();
   RefPtr<Index> mIndex;
+  friend class Index;
   size_t mCurrentMoof;
   size_t mCurrentSample;
 };
 
 class Index
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Index)
@@ -93,33 +94,38 @@ public:
     Interval<Microseconds> mTime;
   };
 
   Index(const IndiceWrapper& aIndices,
         Stream* aSource,
         uint32_t aTrackId,
         bool aIsAudio);
 
+  void UpdateMoofIndex(const mozilla::MediaByteRangeSet& aByteRanges,
+                       bool aCanEvict);
   void UpdateMoofIndex(const mozilla::MediaByteRangeSet& aByteRanges);
   Microseconds GetEndCompositionIfBuffered(
     const mozilla::MediaByteRangeSet& aByteRanges);
   mozilla::media::TimeIntervals ConvertByteRangesToTimeRanges(
     const mozilla::MediaByteRangeSet& aByteRanges);
   uint64_t GetEvictionOffset(Microseconds aTime);
   bool IsFragmented() { return mMoofParser; }
 
   friend class SampleIterator;
 
 private:
   ~Index();
+  void RegisterIterator(SampleIterator* aIterator);
+  void UnregisterIterator(SampleIterator* aIterator);
 
   Stream* mSource;
   FallibleTArray<Sample> mIndex;
   FallibleTArray<MP4DataOffset> mDataOffset;
   nsAutoPtr<MoofParser> mMoofParser;
+  nsTArray<SampleIterator*> mIterators;
 
   // ConvertByteRangesToTimeRanges cache
   mozilla::MediaByteRangeSet mLastCachedRanges;
   mozilla::media::TimeIntervals mLastBufferedRanges;
   bool mIsAudio;
 };
 }
 
--- a/media/libstagefright/binding/include/mp4_demuxer/MoofParser.h
+++ b/media/libstagefright/binding/include/mp4_demuxer/MoofParser.h
@@ -257,16 +257,21 @@ public:
     , mIsAudio(aIsAudio)
     , mLastDecodeTime(0)
   {
     // Setting the mTrex.mTrackId to 0 is a nasty work around for calculating
     // the composition range for MSE. We need an array of tracks.
   }
   bool RebuildFragmentedIndex(
     const mozilla::MediaByteRangeSet& aByteRanges);
+  // If *aCanEvict is set to true. then will remove all moofs already parsed
+  // from index then rebuild the index. *aCanEvict is set to true upon return if
+  // some moofs were removed.
+  bool RebuildFragmentedIndex(
+    const mozilla::MediaByteRangeSet& aByteRanges, bool* aCanEvict);
   bool RebuildFragmentedIndex(BoxContext& aContext);
   Interval<Microseconds> GetCompositionRange(
     const mozilla::MediaByteRangeSet& aByteRanges);
   bool ReachedEnd();
   void ParseMoov(Box& aBox);
   void ParseTrak(Box& aBox);
   void ParseMdia(Box& aBox, Tkhd& aTkhd);
   void ParseMvex(Box& aBox);
@@ -281,17 +286,16 @@ public:
   bool HasMetadata();
   already_AddRefed<mozilla::MediaByteBuffer> Metadata();
   MediaByteRange FirstCompleteMediaSegment();
   MediaByteRange FirstCompleteMediaHeader();
 
   mozilla::MediaByteRange mInitRange;
   RefPtr<Stream> mSource;
   uint64_t mOffset;
-  nsTArray<uint64_t> mMoofOffsets;
   Mvhd mMvhd;
   Mdhd mMdhd;
   Trex mTrex;
   Tfdt mTfdt;
   Edts mEdts;
   Sinf mSinf;
 
   nsTArray<CencSampleEncryptionInfoEntry> mTrackSampleEncryptionInfoEntries;
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
@@ -58,20 +58,20 @@
 #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
 
 #include "nsThreadUtils.h"
 
 #include "logging.h"
 
 // Max size given stereo is 480*2*2 = 1920 (10ms of 16-bits stereo audio at
 // 48KHz)
-#define AUDIO_SAMPLE_BUFFER_MAX 480*2*2
+#define AUDIO_SAMPLE_BUFFER_MAX_BYTES 480*2*2
 static_assert((WEBRTC_DEFAULT_SAMPLE_RATE/100)*sizeof(uint16_t) * 2
-               <= AUDIO_SAMPLE_BUFFER_MAX,
-               "AUDIO_SAMPLE_BUFFER_MAX is not large enough");
+               <= AUDIO_SAMPLE_BUFFER_MAX_BYTES,
+               "AUDIO_SAMPLE_BUFFER_MAX_BYTES is not large enough");
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 
 // Logging context
 MOZ_MTLOG_MODULE("mediapipeline")
@@ -543,22 +543,18 @@ public:
       packetizer_ = new AudioPacketizer<int16_t, int16_t>(audio_10ms, outputChannels);
     }
 
     packetizer_->Input(samples, chunk.mDuration);
 
     while (packetizer_->PacketsAvailable()) {
       uint32_t samplesPerPacket = packetizer_->PacketSize() *
                                   packetizer_->Channels();
-      // We know that webrtc.org's code going to copy the samples down the line,
-      // so we can just use a stack buffer here instead of malloc-ing.
-      int16_t packet[AUDIO_SAMPLE_BUFFER_MAX];
-
-      packetizer_->Output(packet);
-      mConduit->SendAudioFrame(packet, samplesPerPacket, rate, 0);
+      packetizer_->Output(packet_);
+      mConduit->SendAudioFrame(packet_, samplesPerPacket, rate, 0);
     }
   }
 
   void QueueAudioChunk(TrackRate rate, AudioChunk& chunk, bool enabled)
   {
     RUN_ON_THREAD(mThread,
                   WrapRunnable(RefPtr<AudioProxyThread>(this),
                                &AudioProxyThread::InternalProcessAudioChunk,
@@ -575,16 +571,18 @@ protected:
     NS_ReleaseOnMainThread(mConduit.forget());
     MOZ_COUNT_DTOR(AudioProxyThread);
   }
 
   RefPtr<AudioSessionConduit> mConduit;
   nsCOMPtr<nsIEventTarget> mThread;
   // Only accessed on mThread
   nsAutoPtr<AudioPacketizer<int16_t, int16_t>> packetizer_;
+  // A buffer to hold a single packet of audio.
+  int16_t packet_[AUDIO_SAMPLE_BUFFER_MAX_BYTES / sizeof(int16_t)];
 };
 
 static char kDTLSExporterLabel[] = "EXTRACTOR-dtls_srtp";
 
 MediaPipeline::MediaPipeline(const std::string& pc,
                              Direction direction,
                              nsCOMPtr<nsIEventTarget> main_thread,
                              nsCOMPtr<nsIEventTarget> sts_thread,
@@ -2033,17 +2031,17 @@ public:
     if (!source_) {
       MOZ_MTLOG(ML_ERROR, "NotifyPull() called from a non-SourceMediaStream");
       return;
     }
 
     // This comparison is done in total time to avoid accumulated roundoff errors.
     while (source_->TicksToTimeRoundDown(WEBRTC_DEFAULT_SAMPLE_RATE,
                                          played_ticks_) < desired_time) {
-      int16_t scratch_buffer[AUDIO_SAMPLE_BUFFER_MAX];
+      int16_t scratch_buffer[AUDIO_SAMPLE_BUFFER_MAX_BYTES / sizeof(int16_t)];
 
       int samples_length;
 
       // This fetches 10ms of data, either mono or stereo
       MediaConduitErrorCode err =
           static_cast<AudioSessionConduit*>(conduit_.get())->GetAudioFrame(
               scratch_buffer,
               WEBRTC_DEFAULT_SAMPLE_RATE,
@@ -2056,17 +2054,17 @@ public:
                   << ") to return data @ " << played_ticks_
                   << " (desired " << desired_time << " -> "
                   << source_->StreamTimeToSeconds(desired_time) << ")");
         // if this is not enough we'll loop and provide more
         samples_length = WEBRTC_DEFAULT_SAMPLE_RATE/100;
         PodArrayZero(scratch_buffer);
       }
 
-      MOZ_ASSERT(samples_length * sizeof(uint16_t) < AUDIO_SAMPLE_BUFFER_MAX);
+      MOZ_ASSERT(samples_length * sizeof(uint16_t) < AUDIO_SAMPLE_BUFFER_MAX_BYTES);
 
       MOZ_MTLOG(ML_DEBUG, "Audio conduit returned buffer of length "
                 << samples_length);
 
       RefPtr<SharedBuffer> samples = SharedBuffer::Create(samples_length * sizeof(uint16_t));
       int16_t *samples_data = static_cast<int16_t *>(samples->Data());
       AudioSegment segment;
       // We derive the number of channels of the stream from the number of samples
--- a/old-configure.in
+++ b/old-configure.in
@@ -4100,17 +4100,17 @@ if test -n "$MOZ_REPLACE_MALLOC"; then
                  dnl forgets to set the weak flag in the dyld info.
                  dnl See http://glandium.org/blog/?p=2764 for more details.
                  dnl
                  dnl Values for ac_cv_weak_dynamic_linking, and subsequently
                  dnl MOZ_REPLACE_MALLOC_LINKAGE are thus:
                  dnl - "flat namespace" when -flat_namespace alone is needed
                  dnl - "dummy library" when a dummy library and -flat_namespace are needed
                  dnl - "compiler support" when nothing is needed
-                 if test -n "$_DYLD_INFO" && dyldinfo -bind conftest${DLL_SUFFIX} 2> /dev/null | grep "_foo (weak import)" > /dev/null; then
+                 if test -n "$_DYLD_INFO" && ${TOOLCHAIN_PREFIX}dyldinfo -bind conftest${DLL_SUFFIX} 2> /dev/null | grep "_foo (weak import)" > /dev/null; then
                      if test -n "$_CLASSIC_INFO"; then
                          ac_cv_weak_dynamic_linking="flat namespace"
                      else
                          ac_cv_weak_dynamic_linking="compiler support"
                      fi
                  else
                      if test -n "$_DYLD_INFO"; then
                          ac_cv_weak_dynamic_linking="dummy library"
--- a/services/sync/modules/util.js
+++ b/services/sync/modules/util.js
@@ -587,17 +587,17 @@ this.Utils = {
     let brand = Services.strings.createBundle(
       "chrome://branding/locale/brand.properties");
     let brandName = brand.GetStringFromName("brandShortName");
 
     let system =
       // 'device' is defined on unix systems
       Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2).get("device") ||
       // hostname of the system, usually assigned by the user or admin
-      Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2).get("host") ||
+      Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService).myHostName ||
       // fall back on ua info string
       Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler).oscpu;
 
     let syncStrings = Services.strings.createBundle("chrome://weave/locale/sync.properties");
     return syncStrings.formatStringFromName("client.name2", [user, brandName, system], 3);
   },
 
   getDeviceName() {
--- a/services/sync/tests/unit/head_helpers.js
+++ b/services/sync/tests/unit/head_helpers.js
@@ -485,16 +485,18 @@ function promiseStopServer(server) {
 function promiseNextTick() {
   return new Promise(resolve => {
     Utils.nextTick(resolve);
   });
 }
 // Avoid an issue where `client.name2` containing unicode characters causes
 // a number of tests to fail, due to them assuming that we do not need to utf-8
 // encode or decode data sent through the mocked server (see bug 1268912).
+// We stash away the original implementation so test_utils_misc.js can test it.
+Utils._orig_getDefaultDeviceName = Utils.getDefaultDeviceName;
 Utils.getDefaultDeviceName = function() {
   return "Test device name";
 };
 
 function registerRotaryEngine() {
   let {RotaryEngine} =
     Cu.import("resource://testing-common/services/sync/rotaryengine.js", {});
   Service.engineManager.clear();
new file mode 100644
--- /dev/null
+++ b/services/sync/tests/unit/test_utils_misc.js
@@ -0,0 +1,29 @@
+_("Misc tests for utils.js");
+
+add_test(function test_default_device_name() {
+  // Note that head_helpers overrides getDefaultDeviceName - this test is
+  // really just to ensure the actual implementation is sane - we can't
+  // really check the value it uses is correct.
+  // We are just hoping to avoid a repeat of bug 1369285.
+  let def = Utils._orig_getDefaultDeviceName(); // make sure it doesn't throw.
+  _("default value is " + def);
+  ok(def.length > 0);
+
+  // This is obviously tied to the implementation, but we want early warning
+  // if any of these things fail.
+  // We really want one of these 2 to provide a value.
+  let hostname = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2).get("device") ||
+                 Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService).myHostName;
+  _("hostname is " + hostname);
+  ok(hostname.length > 0);
+  // the hostname should be in the default.
+  ok(def.includes(hostname));
+  // We expect the following to work as a fallback to the above.
+  let fallback = Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler).oscpu;
+  _("UA fallback is " + fallback);
+  ok(fallback.length > 0);
+  // the fallback should not be in the default
+  ok(!def.includes(fallback));
+
+  run_next_test();
+});
--- a/services/sync/tests/unit/xpcshell.ini
+++ b/services/sync/tests/unit/xpcshell.ini
@@ -25,16 +25,17 @@ support-files =
 # util contains a bunch of functionality used throughout.
 [test_utils_catch.js]
 [test_utils_deepEquals.js]
 [test_utils_deferGetSet.js]
 [test_utils_keyEncoding.js]
 [test_utils_json.js]
 [test_utils_lock.js]
 [test_utils_makeGUID.js]
+[test_utils_misc.js]
 [test_utils_notify.js]
 [test_utils_passphrase.js]
 
 # We have a number of other libraries that are pretty much standalone.
 [test_addon_utils.js]
 run-sequentially = Restarts server, can't change pref.
 tags = addons
 [test_httpd_sync_server.js]
--- a/servo/components/compositing/compositor.rs
+++ b/servo/components/compositing/compositor.rs
@@ -152,21 +152,16 @@ pub struct IOCompositor<Window: WindowMe
     last_composite_time: u64,
 
     /// Tracks whether the zoom action has happened recently.
     zoom_action: bool,
 
     /// The time of the last zoom action has started.
     zoom_time: f64,
 
-    /// Whether the page being rendered has loaded completely.
-    /// Differs from ReadyState because we can finish loading (ready)
-    /// many times for a single page.
-    got_load_complete_message: bool,
-
     /// The current frame tree ID (used to reject old paint buffers)
     frame_tree_id: FrameTreeId,
 
     /// The channel on which messages can be sent to the constellation.
     constellation_chan: Sender<ConstellationMsg>,
 
     /// The channel on which messages can be sent to the time profiler.
     time_profiler_chan: time::ProfilerChan,
@@ -380,17 +375,16 @@ impl<Window: WindowMethods> IOCompositor
             composite_target: composite_target,
             shutdown_state: ShutdownState::NotShuttingDown,
             page_zoom: ScaleFactor::new(1.0),
             viewport_zoom: PinchZoomFactor::new(1.0),
             min_viewport_zoom: None,
             max_viewport_zoom: None,
             zoom_action: false,
             zoom_time: 0f64,
-            got_load_complete_message: false,
             frame_tree_id: FrameTreeId(0),
             constellation_chan: state.constellation_chan,
             time_profiler_chan: state.time_profiler_chan,
             last_composite_time: 0,
             ready_to_save_state: ReadyState::Unknown,
             scroll_in_progress: false,
             in_scroll_transaction: None,
             webrender: state.webrender,
@@ -511,18 +505,16 @@ impl<Window: WindowMethods> IOCompositor
                 self.window.status(message);
             }
 
             (Msg::LoadStart, ShutdownState::NotShuttingDown) => {
                 self.window.load_start();
             }
 
             (Msg::LoadComplete, ShutdownState::NotShuttingDown) => {
-                self.got_load_complete_message = true;
-
                 // If we're painting in headless mode, schedule a recomposite.
                 if opts::get().output_file.is_some() || opts::get().exit_after_load {
                     self.composite_if_necessary(CompositingReason::Headless);
                 }
 
                 // Inform the embedder that the load has finished.
                 //
                 // TODO(pcwalton): Specify which frame's load completed.
@@ -896,17 +888,16 @@ impl<Window: WindowMethods> IOCompositor
         self.frame_size = new_size;
         self.window_rect = new_window_rect;
 
         self.send_window_size(WindowSizeType::Resize);
     }
 
     fn on_load_url_window_event(&mut self, url_string: String) {
         debug!("osmain: loading URL `{}`", url_string);
-        self.got_load_complete_message = false;
         match ServoUrl::parse(&url_string) {
             Ok(url) => {
                 let msg = match self.root_pipeline {
                     Some(ref pipeline) =>
                         ConstellationMsg::LoadUrl(pipeline.id, LoadData::new(url, Some(pipeline.id), None, None)),
                     None => ConstellationMsg::InitLoadUrl(url)
                 };
                 if let Err(e) = self.constellation_chan.send(msg) {
--- a/testing/web-platform/mach_commands.py
+++ b/testing/web-platform/mach_commands.py
@@ -70,25 +70,33 @@ class WebPlatformTestsRunner(MozbuildObj
     def setup_kwargs_wptrun(self, kwargs):
         from wptrunner import wptcommandline
         here = os.path.join(self.topsrcdir, 'testing', 'web-platform')
 
         sys.path.insert(0, os.path.join(here, "tests", "tools"))
 
         import wptrun
 
+        product = kwargs["product"]
+
         setup_func = {
             "chrome": wptrun.setup_chrome,
             "edge": wptrun.setup_edge,
             "servo": wptrun.setup_servo,
-        }[kwargs["product"]]
+        }[product]
+
+        try:
+            wptrun.check_environ(product)
 
-        setup_func(wptrun.virtualenv.Virtualenv(self.virtualenv_manager.virtualenv_root),
-                   kwargs,
-                   True)
+            setup_func(wptrun.virtualenv.Virtualenv(self.virtualenv_manager.virtualenv_root),
+                       kwargs,
+                       True)
+        except wptrun.WptrunError as e:
+            print(e.message, file=sys.stderr)
+            sys.exit(1)
 
         kwargs["tests_root"] = os.path.join(here, "tests")
 
         if kwargs["metadata_root"] is None:
             metadir = os.path.join(here, "products", kwargs["product"])
             if not os.path.exists(metadir):
                 os.makedirs(metadir)
             kwargs["metadata_root"] = metadir
--- a/testing/web-platform/moz.build
+++ b/testing/web-platform/moz.build
@@ -194,17 +194,17 @@ with Files("tests/web-animations/**"):
 
 with Files("tests/webaudio/**"):
     BUG_COMPONENT = ("Core", "DOM: Web Audio")
 
 with Files("tests/webauthn/**"):
     BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
 
 with Files("tests/webdriver/**"):
-    BUG_COMPONENT = ("https://github.com/w3c/web-platform-tests/", "webdriver")
+    BUG_COMPONENT = ("Testing", "geckodriver")
 
 with Files("tests/webgl/**"):
     BUG_COMPONENT = ("Core", "Canvas: WebGL")
 
 with Files("tests/webmessaging/**"):
     BUG_COMPONENT = ("Core", "DOM")
 
 with Files("tests/webrtc/**"):
--- a/testing/web-platform/tests/tools/wptrun.py
+++ b/testing/web-platform/tests/tools/wptrun.py
@@ -9,16 +9,19 @@ from distutils.spawn import find_executa
 
 import localpaths
 from browserutils import browser, utils, virtualenv
 logger = None
 
 wpt_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
 
 
+class WptrunError(Exception):
+    pass
+
 class WptrunnerHelpAction(argparse.Action):
     def __init__(self,
                  option_strings,
                  dest=argparse.SUPPRESS,
                  default=argparse.SUPPRESS,
                  help=None):
         super(WptrunnerHelpAction, self).__init__(
             option_strings=option_strings,
@@ -59,79 +62,86 @@ def exit(msg):
 def args_general(kwargs):
     kwargs.set_if_none("tests_root", wpt_root)
     kwargs.set_if_none("metadata_root", wpt_root)
     kwargs.set_if_none("manifest_update", True)
 
     if kwargs["ssl_type"] == "openssl":
         if not find_executable(kwargs["openssl_binary"]):
             if os.uname()[0] == "Windows":
-                exit("""OpenSSL binary not found. If you need HTTPS tests, install OpenSSL from
+                raise WptrunError("""OpenSSL binary not found. If you need HTTPS tests, install OpenSSL from
 
 https://slproweb.com/products/Win32OpenSSL.html
 
 Ensuring that libraries are added to /bin and add the resulting bin directory to
 your PATH.
 
 Otherwise run with --ssl-type=none""")
             else:
-                exit("""OpenSSL not found. If you don't need HTTPS support run with --ssl-type=none,
+                raise WptrunError("""OpenSSL not found. If you don't need HTTPS support run with --ssl-type=none,
 otherwise install OpenSSL and ensure that it's on your $PATH.""")
 
 
 def check_environ(product):
-    if product != "firefox":
-        expected_hosts = set(["web-platform.test",
-                              "www.web-platform.test",
-                              "www1.web-platform.test",
-                              "www2.web-platform.test",
-                              "xn--n8j6ds53lwwkrqhv28a.web-platform.test",
-                              "xn--lve-6lad.web-platform.test",
-                              "nonexistent-origin.web-platform.test"])
+    if product not in ("firefox", "servo"):
+        expected_hosts = ["web-platform.test",
+                          "www.web-platform.test",
+                          "www1.web-platform.test",
+                          "www2.web-platform.test",
+                          "xn--n8j6ds53lwwkrqhv28a.web-platform.test",
+                          "xn--lve-6lad.web-platform.test",
+                          "nonexistent-origin.web-platform.test"]
+        missing_hosts = set(expected_hosts)
         if platform.uname()[0] != "Windows":
             hosts_path = "/etc/hosts"
         else:
             hosts_path = "C:\Windows\System32\drivers\etc\hosts"
         with open(hosts_path, "r") as f:
             for line in f:
                 line = line.split("#", 1)[0].strip()
                 parts = line.split()
                 if len(parts) == 2:
                     host = parts[1]
-                    expected_hosts.discard(host)
-            if expected_hosts:
-                exit("""Missing hosts file configuration for %s.
-See README.md for more details.""" % ",".join(expected_hosts))
+                    missing_hosts.discard(host)
+            if missing_hosts:
+                raise WptrunError("""Missing hosts file configuration. Expected entries like:
+
+%s
+
+See README.md for more details.""" % "\n".join("%s\t%s" %
+                                               ("127.0.0.1" if "nonexistent" not in host else "0.0.0.0", host)
+                                               for host in expected_hosts))
+
 
 def prompt_install(component, prompt):
     if not prompt:
         return True
     while True:
         resp = raw_input("Download and install %s [Y/n]? " % component).strip().lower()
         if not resp or resp == "y":
             return True
         elif resp == "n":
             return False
 
 
 def args_firefox(venv, kwargs, firefox, prompt=True):
     if kwargs["binary"] is None:
         binary = firefox.find_binary()
         if binary is None:
-            exit("""Firefox binary not found on $PATH.
+            raise WptrunError("""Firefox binary not found on $PATH.
 
 Install Firefox or use --binary to set the binary path""")
         kwargs["binary"] = binary
 
     if kwargs["certutil_binary"] is None and kwargs["ssl_type"] != "none":
         certutil = firefox.find_certutil()
 
         if certutil is None:
             # Can't download this for now because it's missing the libnss3 library
-            exit("""Can't find certutil.
+            raise WptrunError("""Can't find certutil.
 
 This must be installed using your OS package manager or directly e.g.
 
 Debian/Ubuntu:
 sudo apt install libnss3-tools
 
 macOS/Homebrew:
 brew install nss
@@ -194,31 +204,31 @@ def args_chrome(venv, kwargs, chrome, pr
                 print("Downloading chromedriver")
                 webdriver_binary = chrome.install_webdriver(dest=venv.bin_path)
         else:
             print("Using webdriver binary %s" % webdriver_binary)
 
         if webdriver_binary:
             kwargs["webdriver_binary"] = webdriver_binary
         else:
-            exit("Unable to locate or install chromedriver binary")
+            raise WptrunError("Unable to locate or install chromedriver binary")
 
 
 def setup_chrome(venv, kwargs, prompt=True):
     chrome = browser.Chrome()
     args_chrome(venv, kwargs, chrome, prompt)
     venv.install_requirements(os.path.join(wpt_root, "tools", "wptrunner", chrome.requirements))
 
 
 def args_edge(venv, kwargs, edge, prompt=True):
     if kwargs["webdriver_binary"] is None:
         webdriver_binary = edge.find_webdriver()
 
         if webdriver_binary is None:
-            exit("""Unable to find WebDriver and we aren't yet clever enough to work out which
+            raise WptrunError("""Unable to find WebDriver and we aren't yet clever enough to work out which
 version to download. Please go to the following URL and install the correct
 version for your Edge/Windows release somewhere on the %PATH%:
 
 https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
  """)
         kwargs["webdriver_binary"] = webdriver_binary
 
 
@@ -232,17 +242,17 @@ def setup_sauce(kwargs):
     raise NotImplementedError
 
 
 def args_servo(venv, kwargs, servo, prompt=True):
     if kwargs["binary"] is None:
         binary = servo.find_binary()
 
         if binary is None:
-            exit("Unable to find servo binary on the PATH")
+            raise WptrunError("Unable to find servo binary on the PATH")
         kwargs["binary"] = binary
 
 
 def setup_servo(venv, kwargs, prompt=True):
     servo = browser.Servo()
     args_servo(venv, kwargs, servo, prompt)
     venv.install_requirements(os.path.join(wpt_root, "tools", "wptrunner", servo.requirements))
 
@@ -268,41 +278,44 @@ def setup_wptrunner(venv, product, tests
 
     kwargs["product"] = product
     kwargs["test_list"] = tests
 
     check_environ(product)
     args_general(kwargs)
 
     if product not in product_setup:
-        exit("Unsupported product %s" % product)
+        raise WptrunError("Unsupported product %s" % product)
 
     product_setup[product](venv, kwargs, prompt)
 
     wptcommandline.check_args(kwargs)
 
     wptrunner_path = os.path.join(wpt_root, "tools", "wptrunner")
 
     venv.install_requirements(os.path.join(wptrunner_path, "requirements.txt"))
 
     return kwargs
 
 
 def main():
-    parser = create_parser()
-    args = parser.parse_args()
+    try:
+        parser = create_parser()
+        args = parser.parse_args()
 
-    venv = virtualenv.Virtualenv(os.path.join(wpt_root, "_venv_%s") % platform.uname()[0])
-    venv.start()
-    venv.install_requirements(os.path.join(wpt_root, "tools", "wptrunner", "requirements.txt"))
-    venv.install("requests")
+        venv = virtualenv.Virtualenv(os.path.join(wpt_root, "_venv_%s") % platform.uname()[0])
+        venv.start()
+        venv.install_requirements(os.path.join(wpt_root, "tools", "wptrunner", "requirements.txt"))
+        venv.install("requests")
 
-    kwargs = setup_wptrunner(venv, args.product, args.tests, args.wptrunner_args, prompt=args.prompt)
-    from wptrunner import wptrunner
-    wptrunner.start(**kwargs)
+        kwargs = setup_wptrunner(venv, args.product, args.tests, args.wptrunner_args, prompt=args.prompt)
+        from wptrunner import wptrunner
+        wptrunner.start(**kwargs)
+    except WptrunError as e:
+        exit(e.message)
 
 
 if __name__ == "__main__":
     import pdb
     try:
         main()
     except:
         pdb.post_mortem()
--- a/toolkit/components/places/FaviconHelpers.cpp
+++ b/toolkit/components/places/FaviconHelpers.cpp
@@ -872,18 +872,16 @@ AsyncAssociateIconToPage::Run()
       NS_ENSURE_STATE(stmt);
       mozStorageStatementScoper scoper(stmt);
       rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPage.id);
       NS_ENSURE_SUCCESS(rv, rv);
       rv = stmt->Execute();
       NS_ENSURE_SUCCESS(rv, rv);
     } else {
       // We need to create the page entry.
-      // By default, we use the place id for the insertion. While we can't
-      // guarantee 1:1 mapping, in general it should do.
       nsCOMPtr<mozIStorageStatement> stmt;
       stmt = DB->GetStatement(
         "INSERT OR IGNORE INTO moz_pages_w_icons (page_url, page_url_hash) "
         "VALUES (:page_url, hash(:page_url)) "
       );
       NS_ENSURE_STATE(stmt);
       mozStorageStatementScoper scoper(stmt);
       rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mPage.spec);
@@ -1371,10 +1369,104 @@ FetchAndConvertUnsupportedPayloads::Stor
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = stmt->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
+////////////////////////////////////////////////////////////////////////////////
+//// AsyncCopyFavicons
+
+AsyncCopyFavicons::AsyncCopyFavicons(
+  PageData& aFromPage
+, PageData& aToPage
+, nsIFaviconDataCallback* aCallback
+) : mFromPage(aFromPage)
+  , mToPage(aToPage)
+  , mCallback(new nsMainThreadPtrHolder<nsIFaviconDataCallback>(aCallback))
+{
+  MOZ_ASSERT(NS_IsMainThread());
+}
+
+NS_IMETHODIMP
+AsyncCopyFavicons::Run()
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  IconData icon;
+
+  // Ensure we'll callback and dispatch notifications to the main-thread.
+  auto cleanup = MakeScopeExit([&] () {
+    // If we bailed out early, just return a null icon uri, since we didn't
+    // copy anything.
+    if (!(icon.status & ICON_STATUS_ASSOCIATED)) {
+      icon.spec.Truncate();
+    }
+    nsCOMPtr<nsIRunnable> event = new NotifyIconObservers(icon, mToPage, mCallback);
+    NS_DispatchToMainThread(event);
+  });
+
+  RefPtr<Database> DB = Database::GetDatabase();
+  NS_ENSURE_STATE(DB);
+
+  nsresult rv = FetchPageInfo(DB, mToPage);
+  if (rv == NS_ERROR_NOT_AVAILABLE || !mToPage.placeId) {
+    // We have never seen this page, or we can't add this page to history and
+    // and it's not a bookmark. We won't add the page.
+    return NS_OK;
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Get just one icon, to check whether the page has any, and to notify later.
+  rv = FetchIconPerSpec(DB, mFromPage.spec, EmptyCString(), icon, UINT16_MAX);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (icon.spec.IsEmpty()) {
+    // There's nothing to copy.
+    return NS_OK;
+  }
+
+  // Insert an entry in moz_pages_w_icons if needed.
+  if (!mToPage.id) {
+    // We need to create the page entry.
+    nsCOMPtr<mozIStorageStatement> stmt;
+    stmt = DB->GetStatement(
+      "INSERT OR IGNORE INTO moz_pages_w_icons (page_url, page_url_hash) "
+      "VALUES (:page_url, hash(:page_url)) "
+    );
+    NS_ENSURE_STATE(stmt);
+    mozStorageStatementScoper scoper(stmt);
+    rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mToPage.spec);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = stmt->Execute();
+    NS_ENSURE_SUCCESS(rv, rv);
+    // Required to to fetch the id and the guid.
+    rv = FetchPageInfo(DB, mToPage);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  // Create the relations.
+  nsCOMPtr<mozIStorageStatement> stmt = DB->GetStatement(
+    "INSERT OR IGNORE INTO moz_icons_to_pages (page_id, icon_id) "
+      "SELECT :id, icon_id "
+      "FROM moz_icons_to_pages "
+      "WHERE page_id = (SELECT id FROM moz_pages_w_icons WHERE page_url_hash = hash(:url) AND page_url = :url) "
+  );
+  NS_ENSURE_STATE(stmt);
+  mozStorageStatementScoper scoper(stmt);
+  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mToPage.id);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), mFromPage.spec);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = stmt->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Setting this will make us send pageChanged notifications.
+  // The scope exit will take care of the callback and notifications.
+  icon.status |= ICON_STATUS_ASSOCIATED;
+
+  return NS_OK;
+}
+
 } // namespace places
 } // namespace mozilla
--- a/toolkit/components/places/FaviconHelpers.h
+++ b/toolkit/components/places/FaviconHelpers.h
@@ -349,10 +349,40 @@ public:
 private:
   nsresult ConvertPayload(int64_t aId, const nsACString& aMimeType,
                           nsCString& aPayload, int32_t* aWidth);
   nsresult StorePayload(int64_t aId, int32_t aWidth, const nsCString& aPayload);
 
   nsCOMPtr<mozIStorageConnection> mDB;
 };
 
+/**
+ * Copies Favicons from one page to another one.
+ */
+class AsyncCopyFavicons final : public Runnable
+{
+public:
+  NS_DECL_NSIRUNNABLE
+
+  /**
+   * Constructor.
+   *
+   * @param aFromPage
+   *        The originating page.
+   * @param aToPage
+   *        The destination page.
+   * @param aFaviconLoadPrivate
+   *        Whether this favicon load is in private browsing.
+   * @param aCallback
+   *        An optional callback to invoke when done.
+   */
+  AsyncCopyFavicons(PageData& aFromPage,
+                    PageData& aToPage,
+                    nsIFaviconDataCallback* aCallback);
+
+private:
+  PageData mFromPage;
+  PageData mToPage;
+  nsMainThreadPtrHandle<nsIFaviconDataCallback> mCallback;
+};
+
 } // namespace places
 } // namespace mozilla
--- a/toolkit/components/places/mozIAsyncFavicons.idl
+++ b/toolkit/components/places/mozIAsyncFavicons.idl
@@ -42,20 +42,20 @@ interface mozIAsyncFavicons : nsISupport
    * @param aForceReload
    *        If aForceReload is false, we try to reload the favicon only if we
    *        don't have it or it has expired from the cache.  Setting
    *        aForceReload to true causes us to reload the favicon even if we
    *        have a usable copy.
    * @param aFaviconLoadType
    *        Set to FAVICON_LOAD_PRIVATE if the favicon is loaded from a private
    *        browsing window.  Set to FAVICON_LOAD_NON_PRIVATE otherwise.
-   * @param aCallback
+   * @param [optional] aCallback
    *        Once we're done setting and/or fetching the favicon, we invoke this
    *        callback.
-   * @param aLoadingPrincipal
+   * @param [optional] aLoadingPrincipal
    *        Principal of the page whose favicon is being set. If this argument
    *        is omitted, the loadingPrincipal defaults to the nullPrincipal.
    *
    * @see nsIFaviconDataCallback in nsIFaviconService.idl.
    */
   mozIPlacesPendingOperation setAndFetchFaviconForPage(
     in nsIURI aPageURI,
     in nsIURI aFaviconURI,
@@ -93,17 +93,17 @@ interface mozIAsyncFavicons : nsISupport
    * @param aData
    *        Binary contents of the favicon to save
    * @param aDataLength
    *        Length of binary data
    * @param aMimeType
    *        MIME type of the data to store.  This is important so that we know
    *        what to report when the favicon is used.  You should always set this
    *        param unless you are clearing an icon.
-   * @param aExpiration
+   * @param [optional] aExpiration
    *        Time in microseconds since the epoch when this favicon expires.
    *        Until this time, we won't try to load it again.
    * @throws NS_ERROR_FAILURE
    *         Thrown if the favicon is overbloated and won't be saved to the db.
    */
   void replaceFaviconData(in nsIURI aFaviconURI,
                           [const,array,size_is(aDataLen)] in octet aData,
                           in unsigned long aDataLen,
@@ -116,20 +116,20 @@ interface mozIAsyncFavicons : nsISupport
    *
    * @see replaceFaviconData
    *
    * @param aFaviconURI
    *        URI of the favicon whose data is being set.
    * @param aDataURL
    *        string containing a data URL that represents the contents of
    *        the favicon to save
-   * @param aExpiration
+   * @param [optional] aExpiration
    *        Time in microseconds since the epoch when this favicon expires.
    *        Until this time, we won't try to load it again.
-   * @param aLoadingPrincipal
+   * @param [optional] aLoadingPrincipal
    *        Principal of the page whose favicon is being set. If this argument
    *        is omitted, the loadingPrincipal defaults to the nullPrincipal.
    * @throws NS_ERROR_FAILURE
    *         Thrown if the favicon is overbloated and won't be saved to the db.
    */
   void replaceFaviconDataFromDataURL(in nsIURI aFaviconURI,
                                      in AString aDataURL,
                                      [optional] in PRTime aExpiration,
@@ -142,17 +142,17 @@ interface mozIAsyncFavicons : nsISupport
    *        URI of the page whose favicon URI we're looking up.
    * @param aCallback
    *        This callback is always invoked to notify the result of the lookup.
    *        The aURI parameter will be the favicon URI, or null when no favicon
    *        is associated with the page or an error occurred while fetching it.
    *        aDataLen will be always 0, aData will be an empty array, and
    *        aMimeType will be an empty string, regardless of whether a favicon
    *        was found.
-   * @param aPreferredWidth
+   * @param [optional] aPreferredWidth
    *        The preferred icon width, 0 for the biggest available.
    *
    * @note If a favicon specific to this page cannot be found, this will try to
    *       fallback to the /favicon.ico for the root domain.
    *
    * @see nsIFaviconDataCallback in nsIFaviconService.idl.
    */
   void getFaviconURLForPage(in nsIURI aPageURI,
@@ -169,19 +169,39 @@ interface mozIAsyncFavicons : nsISupport
    * @param aCallback
    *        This callback is always invoked to notify the result of the lookup.  The aURI
    *        parameter will be the favicon URI, or null when no favicon is
    *        associated with the page or an error occurred while fetching it.  If
    *        aURI is not null, the other parameters may contain the favicon data.
    *        However, if no favicon data is currently associated with the favicon
    *        URI, aDataLen will be 0, aData will be an empty array, and aMimeType
    *        will be an empty string.
-   * @param aPreferredWidth
+   * @param [optional] aPreferredWidth
    *        The preferred icon width, 0 for the biggest available.
    * @note If a favicon specific to this page cannot be found, this will try to
    *       fallback to the /favicon.ico for the root domain.
    *
    * @see nsIFaviconDataCallback in nsIFaviconService.idl.
    */
   void getFaviconDataForPage(in nsIURI aPageURI,
                              in nsIFaviconDataCallback aCallback,
                              [optional] in unsigned short aPreferredWidth);
+
+  /**
+   * Copies cached favicons from a page to another one.
+   *
+   * @param aFromPageURI
+   *        URI of the originating page.
+   * @param aToPageURI
+   *        URI of the destination page.
+   * @param aFaviconLoadType
+   *        Set to FAVICON_LOAD_PRIVATE if the copy is started from a private
+   *        browsing window.  Set to FAVICON_LOAD_NON_PRIVATE otherwise.
+   * @param [optional] aCallback
+   *        Once we're done copying the favicon, we invoke this callback.
+   *        If a copy has been done, the callback will report one of the
+   *        favicons uri as aFaviconURI, otherwise all the params will be null.
+   */
+  void copyFavicons(in nsIURI aFromPageURI,
+                    in nsIURI aToPageURI,
+                    in unsigned long aFaviconLoadType,
+                    [optional] in nsIFaviconDataCallback aCallback);
 };
--- a/toolkit/components/places/nsFaviconService.cpp
+++ b/toolkit/components/places/nsFaviconService.cpp
@@ -630,16 +630,55 @@ nsFaviconService::GetFaviconDataForPage(
     new AsyncGetFaviconDataForPage(pageSpec, pageHost, aPreferredWidth, aCallback);
   RefPtr<Database> DB = Database::GetDatabase();
   NS_ENSURE_STATE(DB);
   DB->DispatchToAsyncThread(event);
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsFaviconService::CopyFavicons(nsIURI* aFromPageURI,
+                               nsIURI* aToPageURI,
+                               uint32_t aFaviconLoadType,
+                               nsIFaviconDataCallback* aCallback)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_ARG(aFromPageURI);
+  NS_ENSURE_ARG(aToPageURI);
+  NS_ENSURE_TRUE(aFaviconLoadType >= nsIFaviconService::FAVICON_LOAD_PRIVATE &&
+                 aFaviconLoadType <= nsIFaviconService::FAVICON_LOAD_NON_PRIVATE,
+                 NS_ERROR_INVALID_ARG);
+
+  PageData fromPage;
+  nsresult rv = aFromPageURI->GetSpec(fromPage.spec);
+  NS_ENSURE_SUCCESS(rv, rv);
+  PageData toPage;
+  rv = aToPageURI->GetSpec(toPage.spec);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool canAddToHistory;
+  nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
+  NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
+  rv = navHistory->CanAddURI(aToPageURI, &canAddToHistory);
+  NS_ENSURE_SUCCESS(rv, rv);
+  toPage.canAddToHistory = !!canAddToHistory &&
+                           aFaviconLoadType != nsIFaviconService::FAVICON_LOAD_PRIVATE;
+
+  RefPtr<AsyncCopyFavicons> event = new AsyncCopyFavicons(fromPage, toPage, aCallback);
+
+  // Get the target thread and start the work.
+  // DB will be updated and observers notified when done.
+  RefPtr<Database> DB = Database::GetDatabase();
+  NS_ENSURE_STATE(DB);
+  DB->DispatchToAsyncThread(event);
+
+  return NS_OK;
+}
+
 nsresult
 nsFaviconService::GetFaviconLinkForIcon(nsIURI* aFaviconURI,
                                         nsIURI** aOutputURI)
 {
   NS_ENSURE_ARG(aFaviconURI);
   NS_ENSURE_ARG_POINTER(aOutputURI);
 
   nsAutoCString spec;
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_copyFavicons.js
@@ -0,0 +1,123 @@
+const TEST_URI1 = Services.io.newURI("http://mozilla.com/");
+const TEST_URI2 = Services.io.newURI("http://places.com/");
+const TEST_URI3 = Services.io.newURI("http://bookmarked.com/");
+const LOAD_NON_PRIVATE = PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE;
+const LOAD_PRIVATE = PlacesUtils.favicons.FAVICON_LOAD_PRIVATE;
+
+function copyFavicons(source, dest, inPrivate) {
+  return new Promise(resolve => {
+     PlacesUtils.favicons.copyFavicons(source, dest, inPrivate ? LOAD_PRIVATE
+                                                               : LOAD_NON_PRIVATE,
+                                       resolve);
+  });
+}
+
+function promisePageChanged() {
+  return new Promise(resolve => {
+    let observer = new NavHistoryObserver();
+    observer.onPageChanged = (uri, attribute, newValue, guid) => {
+      do_print("onPageChanged for attribute " + attribute + " and uri " + uri.spec);
+      if (attribute == Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
+        PlacesUtils.history.removeObserver(observer);
+        Assert.ok(newValue, "newValue should be a valid value");
+        Assert.ok(guid, "Guid should be a valid value");
+        resolve(uri);
+      }
+    };
+    PlacesUtils.history.addObserver(observer, false);
+  });
+}
+
+add_task(async function test_copyFavicons_inputcheck() {
+  Assert.throws(() => PlacesUtils.favicons.copyFavicons(null, TEST_URI2, LOAD_PRIVATE),
+                /NS_ERROR_ILLEGAL_VALUE/);
+  Assert.throws(() => PlacesUtils.favicons.copyFavicons(TEST_URI1, null, LOAD_PRIVATE),
+                /NS_ERROR_ILLEGAL_VALUE/);
+  Assert.throws(() => PlacesUtils.favicons.copyFavicons(TEST_URI1, TEST_URI2, 3),
+                /NS_ERROR_ILLEGAL_VALUE/);
+  Assert.throws(() => PlacesUtils.favicons.copyFavicons(TEST_URI1, TEST_URI2, -1),
+                /NS_ERROR_ILLEGAL_VALUE/);
+  Assert.throws(() => PlacesUtils.favicons.copyFavicons(TEST_URI1, TEST_URI2, null),
+                /NS_ERROR_ILLEGAL_VALUE/);
+});
+
+add_task(async function test_copyFavicons_noop() {
+  do_print("Unknown uris");
+  Assert.equal(await copyFavicons(TEST_URI1, TEST_URI2, false),
+               null, "Icon should not have been copied");
+
+  do_print("Unknown dest uri");
+  await PlacesTestUtils.addVisits(TEST_URI1);
+  Assert.equal(await copyFavicons(TEST_URI1, TEST_URI2, false),
+               null, "Icon should not have been copied");
+
+  do_print("Unknown dest uri");
+  await PlacesTestUtils.addVisits(TEST_URI1);
+  Assert.equal(await copyFavicons(TEST_URI1, TEST_URI2, false),
+               null, "Icon should not have been copied");
+
+  do_print("Unknown dest uri, source has icon");
+  await setFaviconForPage(TEST_URI1, SMALLPNG_DATA_URI);
+  Assert.equal(await copyFavicons(TEST_URI1, TEST_URI2, false),
+               null, "Icon should not have been copied");
+
+  do_print("Known uris, source has icon, private");
+  await PlacesTestUtils.addVisits(TEST_URI2);
+  Assert.equal(await copyFavicons(TEST_URI1, TEST_URI2, true),
+               null, "Icon should not have been copied");
+
+  PlacesUtils.favicons.expireAllFavicons();
+  await PlacesTestUtils.clearHistory();
+});
+
+add_task(async function test_copyFavicons() {
+  do_print("Normal copy across 2 pages");
+  await PlacesTestUtils.addVisits(TEST_URI1);
+  await setFaviconForPage(TEST_URI1, SMALLPNG_DATA_URI);
+  await setFaviconForPage(TEST_URI1, SMALLSVG_DATA_URI);
+  await PlacesTestUtils.addVisits(TEST_URI2);
+  let promiseChange = promisePageChanged();
+  Assert.equal((await copyFavicons(TEST_URI1, TEST_URI2, false)).spec,
+               SMALLSVG_DATA_URI.spec, "Icon should have been copied");
+  Assert.equal((await promiseChange).spec,
+               TEST_URI2.spec, "Notification should have fired");
+  Assert.equal(await getFaviconUrlForPage(TEST_URI2, 1),
+               SMALLPNG_DATA_URI.spec, "Small icon found");
+  Assert.equal(await getFaviconUrlForPage(TEST_URI2),
+               SMALLSVG_DATA_URI.spec, "Large icon found");
+
+  do_print("Private copy to a bookmarked page");
+  await PlacesUtils.bookmarks.insert({
+    url: TEST_URI3, parentGuid: PlacesUtils.bookmarks.unfiledGuid
+  });
+  promiseChange = promisePageChanged();
+  Assert.equal((await copyFavicons(TEST_URI1, TEST_URI3, true)).spec,
+               SMALLSVG_DATA_URI.spec, "Icon should have been copied");
+  Assert.equal((await promiseChange).spec,
+               TEST_URI3.spec, "Notification should have fired");
+  Assert.equal(await getFaviconUrlForPage(TEST_URI3, 1),
+               SMALLPNG_DATA_URI.spec, "Small icon found");
+  Assert.equal(await getFaviconUrlForPage(TEST_URI3),
+               SMALLSVG_DATA_URI.spec, "Large icon found");
+
+  PlacesUtils.favicons.expireAllFavicons();
+  await PlacesTestUtils.clearHistory();
+});
+
+add_task(async function test_copyFavicons_overlap() {
+  do_print("Copy to a page that has one of the favicons already");
+  await PlacesTestUtils.addVisits(TEST_URI1);
+  await setFaviconForPage(TEST_URI1, SMALLPNG_DATA_URI);
+  await setFaviconForPage(TEST_URI1, SMALLSVG_DATA_URI);
+  await PlacesTestUtils.addVisits(TEST_URI2);
+  await setFaviconForPage(TEST_URI2, SMALLPNG_DATA_URI);
+  let promiseChange = promisePageChanged();
+  Assert.equal((await copyFavicons(TEST_URI1, TEST_URI2, false)).spec,
+               SMALLSVG_DATA_URI.spec, "Icon should have been copied");
+  Assert.equal((await promiseChange).spec,
+               TEST_URI2.spec, "Notification should have fired");
+  Assert.equal(await getFaviconUrlForPage(TEST_URI2, 1),
+               SMALLPNG_DATA_URI.spec, "Small icon found");
+  Assert.equal(await getFaviconUrlForPage(TEST_URI2),
+               SMALLSVG_DATA_URI.spec, "Large icon found");
+});
--- a/toolkit/components/places/tests/favicons/xpcshell.ini
+++ b/toolkit/components/places/tests/favicons/xpcshell.ini
@@ -19,16 +19,17 @@ support-files =
   favicon-multi-frame32.png
   favicon-multi-frame64.png
   favicon-normal16.png
   favicon-normal32.png
   favicon-scale160x3.jpg
   favicon-scale3x160.jpg
   noise.png
 
+[test_copyFavicons.js]
 [test_expireAllFavicons.js]
 [test_expire_migrated_icons.js]
 [test_expire_on_new_icons.js]
 [test_favicons_conversions.js]
 [test_favicons_protocols_ref.js]
 [test_getFaviconDataForPage.js]
 [test_getFaviconURLForPage.js]
 [test_heavy_favicon.js]
--- a/tools/rewriting/ThirdPartyPaths.txt
+++ b/tools/rewriting/ThirdPartyPaths.txt
@@ -17,19 +17,19 @@ gfx/webrender
 gfx/webrender_traits
 gfx/ycbcr/
 intl/hyphenation/hyphen/
 intl/icu/
 ipc/chromium/
 js/src/ctypes/libffi/
 js/src/dtoa.c
 js/src/jit/arm64/vixl/
+media/ffvpx/
 media/gmp-clearkey/0.1/openaes/
 media/kiss_fft/
-media/ffvpx/
 media/libav/
 media/libcubeb/
 media/libjpeg/
 media/libmkv/
 media/libnestegg/
 media/libogg/
 media/libopus/
 media/libpng/
@@ -40,21 +40,22 @@ media/libtheora/
 media/libtremor/
 media/libvorbis/
 media/libvpx/
 media/libyuv/
 media/mtransport/third_party/
 media/openmax_dl/
 media/pocketsphinx/
 media/sphinxbase/
+media/webrtc/signaling/src/sdp/sipcc/
 media/webrtc/trunk/
-media/webrtc/signaling/src/sdp/sipcc/
 mfbt/decimal/
 mfbt/double-conversion/source/
 mfbt/lz4
+mobile/android/geckoview/src/thirdparty/
 mobile/android/thirdparty/
 modules/brotli/
 modules/fdlibm/
 modules/freetype2/
 modules/libbz2/
 modules/libmar/
 modules/zlib/
 netwerk/sctp/src/
@@ -69,18 +70,18 @@ python/futures/
 python/jsmin/
 python/mock-*/
 python/psutil/
 python/py/
 python/pyasn1/
 python/pyasn1-modules/
 python/PyECC/
 python/pytest/
+python/pytoml/
 python/pyyaml/
-python/pytoml/
 python/redo/
 python/requests/
 python/rsa/
 python/which/
 security/nss/
 security/sandbox/chromium/
 testing/gtest/gmock/
 testing/gtest/gtest/