Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit 1e96300

Browse files
committed
Bug 1353013 - create preloaded newtab browser from an idle task, only in top window(s), r=dthayer
This limits us to 1 preloaded browser per window, in the top 3 normal windows + top 3 private windows. If we try to create additional browsers beyond that, we instead move a pre-existing browser across. Differential Revision: https://phabricator.services.mozilla.com/D21129 --HG-- extra : moz-landing-system : lando
1 parent 44dd6b4 commit 1e96300

9 files changed

Lines changed: 222 additions & 19 deletions

File tree

accessible/tests/mochitest/relations/test_tabbrowser.xul

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,10 @@
4848
////////////////////////////////////////////////////////////////////////
4949
// 'labelled by'/'label for' relations for xul:tab and xul:tabpanel
5050
51-
var tabs = tabBrowser().tabContainer.children;
52-
var panels = tabBrowser().tabbox.tabpanels.children;
51+
var tabs = Array.from(tabBrowser().tabContainer.children);
52+
// For preloaded tabs, there might be items in this array where this relation
53+
// doesn't hold, so just deal with that:
54+
var panels = tabs.map(t => t.linkedBrowser.closest("tabpanels > *"));
5355
5456
testRelation(panels[0], RELATION_LABELLED_BY, tabs[0]);
5557
testRelation(tabs[0], RELATION_LABEL_FOR, panels[0]);

browser/base/content/browser.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1931,6 +1931,10 @@ var gBrowserInit = {
19311931
}, 300 * 1000);
19321932
});
19331933

1934+
scheduleIdleTask(async () => {
1935+
NewTabPagePreloading.maybeCreatePreloadedBrowser(window);
1936+
});
1937+
19341938
// This should always go last, since the idle tasks (except for the ones with
19351939
// timeouts) should execute in order. Note that this observer notification is
19361940
// not guaranteed to fire, since the window could close before we get here.
@@ -2058,6 +2062,8 @@ var gBrowserInit = {
20582062

20592063
BrowserSearch.uninit();
20602064

2065+
NewTabPagePreloading.removePreloadedBrowser(window);
2066+
20612067
// Now either cancel delayedStartup, or clean up the services initialized from
20622068
// it.
20632069
if (this._boundDelayedStartup) {

browser/base/content/test/statuspanel/head.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515
async function promiseStatusPanelShown(win, value) {
1616
let panel = win.StatusPanel.panel;
17+
info("Waiting to show panel");
1718
await BrowserTestUtils.waitForEvent(panel, "transitionend", (e) => {
1819
return e.propertyName === "opacity" &&
1920
win.getComputedStyle(e.target).opacity == "1";
@@ -31,8 +32,18 @@ async function promiseStatusPanelShown(win, value) {
3132
*/
3233
async function promiseStatusPanelHidden(win) {
3334
let panel = win.StatusPanel.panel;
34-
await BrowserTestUtils.waitForEvent(panel, "transitionend", (e) => {
35-
return e.propertyName === "opacity" &&
36-
win.getComputedStyle(e.target).opacity == "0";
35+
info("Waiting to hide panel");
36+
await new Promise(resolve => {
37+
let l = e => {
38+
if (e.propertyName === "opacity" &&
39+
win.getComputedStyle(e.target).opacity == "0") {
40+
info("Panel hid after " + e.type + " event");
41+
panel.removeEventListener("transitionend", l);
42+
panel.removeEventListener("transitioncancel", l);
43+
resolve();
44+
}
45+
};
46+
panel.addEventListener("transitionend", l);
47+
panel.addEventListener("transitioncancel", l);
3748
});
3849
}

browser/components/originattributes/test/browser/browser_firstPartyIsolation_about_newtab.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
add_task(async function setup() {
2-
Services.prefs.setBoolPref("privacy.firstparty.isolate", true);
3-
4-
registerCleanupFunction(function() {
5-
Services.prefs.clearUserPref("privacy.firstparty.isolate");
6-
});
2+
await SpecialPowers.pushPrefEnv({set: [
3+
["browser.newtab.preload", false],
4+
["privacy.firstparty.isolate", true],
5+
]});
76
});
87

98
/**

browser/components/tests/browser/browser_initial_tab_remoteType.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ add_task(async function setup() {
148148
NewTabPagePreloading.removePreloadedBrowser(window);
149149

150150
await SpecialPowers.pushPrefEnv({"set": [
151+
["browser.newtab.preload", false],
151152
["browser.startup.homepage", "about:home"],
152153
["browser.startup.page", 1],
153154
]});

browser/modules/NewTabPagePreloading.jsm

Lines changed: 96 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,39 @@ var EXPORTED_SYMBOLS = ["NewTabPagePreloading"];
1212
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
1313
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
1414

15-
ChromeUtils.defineModuleGetter(this, "E10SUtils", "resource://gre/modules/E10SUtils.jsm");
15+
XPCOMUtils.defineLazyModuleGetters(this, {
16+
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
17+
E10SUtils: "resource://gre/modules/E10SUtils.jsm",
18+
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
19+
});
1620

1721
XPCOMUtils.defineLazyServiceGetters(this, {
1822
gAboutNewTabService: ["@mozilla.org/browser/aboutnewtab-service;1", "nsIAboutNewTabService"],
1923
});
2024

2125
let NewTabPagePreloading = {
26+
// Maximum number of instances of a given page we'll preload at any time.
27+
// Because we preload about:newtab for normal windows, and about:privatebrowsing
28+
// for private ones, we could have 3 of each.
29+
MAX_COUNT: 3,
30+
31+
// How many preloaded tabs we have, across all windows, for the private and non-private
32+
// case:
33+
browserCounts: {
34+
normal: 0,
35+
private: 0,
36+
},
37+
2238
get enabled() {
2339
return this.prefEnabled && this.newTabEnabled &&
2440
!gAboutNewTabService.overridden;
2541
},
2642

27-
maybeCreatePreloadedBrowser(window) {
28-
if (!this.enabled || window.gBrowser.preloadedBrowser) {
29-
return;
30-
}
31-
32-
const {gBrowser, gMultiProcessBrowser, BROWSER_NEW_TAB_URL} = window;
43+
/**
44+
* Create a browser in the right process type.
45+
*/
46+
_createBrowser(win) {
47+
const {gBrowser, gMultiProcessBrowser, BROWSER_NEW_TAB_URL} = win;
3348
let remoteType =
3449
E10SUtils.getRemoteTypeForURI(BROWSER_NEW_TAB_URL, gMultiProcessBrowser);
3550
let browser = gBrowser.createBrowser({ isPreloadBrowser: true, remoteType });
@@ -44,16 +59,86 @@ let NewTabPagePreloading = {
4459
// of the preloaded browser until it gets attached to a tab.
4560
browser.webProgress;
4661
}
62+
return browser;
63+
},
64+
65+
/**
66+
* Move the contents of a preload browser across to a different window.
67+
*/
68+
_adoptBrowserFromOtherWindow(window) {
69+
let winPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
70+
// Grab the least-recently-focused window with a preloaded browser:
71+
let oldWin = BrowserWindowTracker.orderedWindows.filter(w => {
72+
return winPrivate == PrivateBrowsingUtils.isWindowPrivate(w) &&
73+
w.gBrowser && w.gBrowser.preloadedBrowser;
74+
}).pop();
75+
if (!oldWin) {
76+
return null;
77+
}
78+
// Don't call getPreloadedBrowser because it'll consume the browser:
79+
let oldBrowser = oldWin.gBrowser.preloadedBrowser;
80+
oldWin.gBrowser.preloadedBrowser = null;
81+
82+
let newBrowser = this._createBrowser(window);
83+
84+
oldWin.gBrowser._outerWindowIDBrowserMap.delete(oldBrowser.outerWindowID);
85+
window.gBrowser._outerWindowIDBrowserMap.delete(newBrowser.outerWindowID);
86+
87+
oldBrowser.swapBrowsers(newBrowser);
88+
89+
// Switch outerWindowIDs for remote browsers.
90+
if (newBrowser.isRemoteBrowser) {
91+
newBrowser._outerWindowID = oldBrowser._outerWindowID;
92+
}
93+
94+
window.gBrowser._outerWindowIDBrowserMap.set(newBrowser.outerWindowID, newBrowser);
95+
newBrowser.permanentKey = oldBrowser.permanentKey;
96+
97+
oldWin.gBrowser.getPanel(oldBrowser).remove();
98+
return newBrowser;
99+
},
47100

48-
browser.loadURI(BROWSER_NEW_TAB_URL, {
101+
maybeCreatePreloadedBrowser(window) {
102+
// If we're not enabled, have already got one, or are in a popup window,
103+
// don't bother creating a preload browser - there's no point.
104+
if (!this.enabled || window.gBrowser.preloadedBrowser ||
105+
!window.toolbar.visible) {
106+
return;
107+
}
108+
109+
// Don't bother creating a preload browser if we're not in the top set of windows:
110+
let windowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
111+
let countKey = windowPrivate ? "private" : "normal";
112+
let topWindows = BrowserWindowTracker.orderedWindows.filter(w =>
113+
PrivateBrowsingUtils.isWindowPrivate(w) == windowPrivate);
114+
if (topWindows.indexOf(window) >= this.MAX_COUNT) {
115+
return;
116+
}
117+
118+
// If we're in the top set of windows, and we already have enough preloaded
119+
// tabs, don't create yet another one, just steal an existing one:
120+
if (this.browserCounts[countKey] >= this.MAX_COUNT) {
121+
let browser = this._adoptBrowserFromOtherWindow(window);
122+
// We can potentially get null here if we couldn't actually find another
123+
// browser to adopt from. This can be the case when there's a mix of
124+
// private and non-private windows, for instance.
125+
if (browser) {
126+
return;
127+
}
128+
}
129+
130+
let browser = this._createBrowser(window);
131+
browser.loadURI(window.BROWSER_NEW_TAB_URL, {
49132
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
50133
});
51134
browser.docShellIsActive = false;
52135
browser._urlbarFocused = true;
53136

54137
// Make sure the preloaded browser is loaded with desired zoom level
55-
let tabURI = Services.io.newURI(BROWSER_NEW_TAB_URL);
138+
let tabURI = Services.io.newURI(window.BROWSER_NEW_TAB_URL);
56139
window.FullZoom.onLocationChange(tabURI, false, browser);
140+
141+
this.browserCounts[countKey]++;
57142
},
58143

59144
getPreloadedBrowser(window) {
@@ -74,6 +159,8 @@ let NewTabPagePreloading = {
74159
// in the case that the browser is remote, as remote browsers take
75160
// care of that themselves.
76161
if (browser) {
162+
let countKey = PrivateBrowsingUtils.isWindowPrivate(window) ? "private" : "normal";
163+
this.browserCounts[countKey]--;
77164
browser.setAttribute("preloadedState", "consumed");
78165
browser.setAttribute("autocompletepopup", "PopupAutoComplete");
79166
}

browser/modules/test/browser/browser.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ support-files =
1818
[browser_PageActions.js]
1919
[browser_PermissionUI.js]
2020
[browser_PermissionUI_prompts.js]
21+
[browser_preloading_tab_moving.js]
2122
[browser_ProcessHangNotifications.js]
2223
skip-if = !e10s
2324
[browser_SitePermissions.js]
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/* Any copyright is dedicated to the Public Domain.
2+
* http://creativecommons.org/publicdomain/zero/1.0/ */
3+
4+
"use strict";
5+
6+
var gOldCount = NewTabPagePreloading.MAX_COUNT;
7+
registerCleanupFunction(() => {
8+
NewTabPagePreloading.MAX_COUNT = gOldCount;
9+
});
10+
11+
async function openWinWithPreloadBrowser(options = {}) {
12+
let idleFinishedPromise = TestUtils.topicObserved("browser-idle-startup-tasks-finished", w => {
13+
return w != window;
14+
});
15+
let newWin = await BrowserTestUtils.openNewBrowserWindow(options);
16+
await idleFinishedPromise;
17+
await TestUtils.waitForCondition(() => newWin.gBrowser.preloadedBrowser);
18+
return newWin;
19+
}
20+
21+
/**
22+
* Verify that moving a preloaded browser's content from one window to the next
23+
* works correctly.
24+
*/
25+
add_task(async function moving_works() {
26+
NewTabPagePreloading.MAX_COUNT = 1;
27+
28+
NewTabPagePreloading.removePreloadedBrowser(window);
29+
30+
NewTabPagePreloading.maybeCreatePreloadedBrowser(window);
31+
isnot(gBrowser.preloadedBrowser, null, "Should have preloaded browser");
32+
33+
let oldKey = gBrowser.preloadedBrowser.permanentKey;
34+
35+
let newWin = await openWinWithPreloadBrowser();
36+
is(gBrowser.preloadedBrowser, null, "Preloaded browser should be gone");
37+
isnot(newWin.gBrowser.preloadedBrowser, null, "Should have moved the preload browser");
38+
is(newWin.gBrowser.preloadedBrowser.permanentKey, oldKey, "Should have the same permanent key");
39+
let browser = newWin.gBrowser.preloadedBrowser;
40+
let tab = BrowserTestUtils.addTab(newWin.gBrowser, newWin.BROWSER_NEW_TAB_URL);
41+
is(tab.linkedBrowser, browser, "Preloaded browser is usable when opening a new tab.");
42+
await ContentTask.spawn(browser, null, function() {
43+
return ContentTaskUtils.waitForCondition(() => {
44+
return content.document.readyState == "complete";
45+
});
46+
});
47+
ok(true, "Successfully loaded the tab.");
48+
49+
tab = browser = null;
50+
await BrowserTestUtils.closeWindow(newWin);
51+
52+
tab = BrowserTestUtils.addTab(gBrowser, BROWSER_NEW_TAB_URL);
53+
await ContentTask.spawn(tab.linkedBrowser, null, function() {
54+
return ContentTaskUtils.waitForCondition(() => {
55+
return content.document.readyState == "complete";
56+
});
57+
});
58+
59+
ok(true, "Managed to open a tab in the original window still.");
60+
61+
BrowserTestUtils.removeTab(tab);
62+
});
63+
64+
add_task(async function moving_shouldnt_move_across_private_state() {
65+
NewTabPagePreloading.MAX_COUNT = 1;
66+
67+
NewTabPagePreloading.removePreloadedBrowser(window);
68+
69+
NewTabPagePreloading.maybeCreatePreloadedBrowser(window);
70+
isnot(gBrowser.preloadedBrowser, null, "Should have preloaded browser");
71+
72+
let oldKey = gBrowser.preloadedBrowser.permanentKey;
73+
let newWin = await openWinWithPreloadBrowser({private: true});
74+
75+
isnot(gBrowser.preloadedBrowser, null, "Preloaded browser in original window should persist");
76+
isnot(newWin.gBrowser.preloadedBrowser, null, "Should have created another preload browser");
77+
isnot(newWin.gBrowser.preloadedBrowser.permanentKey, oldKey, "Should not have the same permanent key");
78+
let browser = newWin.gBrowser.preloadedBrowser;
79+
let tab = BrowserTestUtils.addTab(newWin.gBrowser, newWin.BROWSER_NEW_TAB_URL);
80+
is(tab.linkedBrowser, browser, "Preloaded browser is usable when opening a new tab.");
81+
await ContentTask.spawn(browser, null, function() {
82+
return ContentTaskUtils.waitForCondition(() => {
83+
return content.document.readyState == "complete";
84+
});
85+
});
86+
ok(true, "Successfully loaded the tab.");
87+
88+
tab = browser = null;
89+
await BrowserTestUtils.closeWindow(newWin);
90+
});
91+

startupcache/test/browser_startupcache_telemetry.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ const LABELS_STARTUP_CACHE_REQUESTS = {
1010
};
1111

1212
add_task(async function() {
13+
// Turn off tab preloading to avoid issues with RemoteController.js
14+
await SpecialPowers.pushPrefEnv({
15+
set: [["browser.newtab.preload", false]],
16+
});
17+
1318
Services.obs.notifyObservers(null, "startupcache-invalidate");
1419
Services.telemetry.getSnapshotForHistograms("main", true);
1520
let newWin = await BrowserTestUtils.openNewBrowserWindow();

0 commit comments

Comments
 (0)