NewTabPagePreloading.sys.mjs (7473B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /* 6 * This module is in charge of preloading 'new tab' pages for use when 7 * the user opens a new tab. 8 */ 9 10 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; 11 12 const lazy = {}; 13 14 ChromeUtils.defineESModuleGetters(lazy, { 15 AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs", 16 AIWindow: 17 "moz-src:///browser/components/aiwindow/ui/modules/AIWindow.sys.mjs", 18 BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs", 19 E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs", 20 PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", 21 }); 22 23 export let NewTabPagePreloading = { 24 // Maximum number of instances of a given page we'll preload at any time. 25 // Because we preload about:newtab for normal windows, and about:privatebrowsing 26 // for private ones, we could have 3 of each. 27 MAX_COUNT: 3, 28 29 // How many preloaded tabs we have, across all windows, for the private and non-private 30 // case: 31 browserCounts: { 32 normal: 0, 33 private: 0, 34 }, 35 36 get enabled() { 37 return ( 38 this.prefEnabled && 39 this.newTabEnabled && 40 !lazy.AboutNewTab.newTabURLOverridden 41 ); 42 }, 43 44 /** 45 * Create a browser in the right process type. 46 */ 47 _createBrowser(win) { 48 const { 49 gBrowser, 50 gMultiProcessBrowser, 51 gFissionBrowser, 52 BROWSER_NEW_TAB_URL, 53 } = win; 54 55 let oa = lazy.E10SUtils.predictOriginAttributes({ window: win }); 56 57 let remoteType = lazy.E10SUtils.getRemoteTypeForURI( 58 BROWSER_NEW_TAB_URL, 59 gMultiProcessBrowser, 60 gFissionBrowser, 61 lazy.E10SUtils.DEFAULT_REMOTE_TYPE, 62 null, 63 oa 64 ); 65 let browser = gBrowser.createBrowser({ 66 isPreloadBrowser: true, 67 remoteType, 68 }); 69 gBrowser.preloadedBrowser = browser; 70 71 let panel = gBrowser.getPanel(browser); 72 gBrowser.tabpanels.appendChild(panel); 73 74 return browser; 75 }, 76 77 /** 78 * Move the contents of a preload browser across to a different window. 79 */ 80 _adoptBrowserFromOtherWindow(window) { 81 let winPrivate = lazy.PrivateBrowsingUtils.isWindowPrivate(window); 82 let winAIWindow = lazy.AIWindow.isAIWindowActive(window); 83 // Grab the least-recently-focused window with a preloaded browser: 84 let oldWin = lazy.BrowserWindowTracker.orderedWindows 85 .filter(w => { 86 return ( 87 winPrivate == lazy.PrivateBrowsingUtils.isWindowPrivate(w) && 88 winAIWindow == lazy.AIWindow.isAIWindowActive(w) && 89 w.gBrowser && 90 w.gBrowser.preloadedBrowser 91 ); 92 }) 93 .pop(); 94 if (!oldWin) { 95 return null; 96 } 97 // Don't call getPreloadedBrowser because it'll consume the browser: 98 let oldBrowser = oldWin.gBrowser.preloadedBrowser; 99 oldWin.gBrowser.preloadedBrowser = null; 100 101 let newBrowser = this._createBrowser(window); 102 103 oldBrowser.swapBrowsers(newBrowser); 104 105 newBrowser.permanentKey = oldBrowser.permanentKey; 106 107 oldWin.gBrowser.getPanel(oldBrowser).remove(); 108 return newBrowser; 109 }, 110 111 maybeCreatePreloadedBrowser(window) { 112 // If we're not enabled, have already got one, are in a popup window, or the 113 // window is minimized / occluded, don't bother creating a preload browser - 114 // there's no point. 115 if ( 116 !this.enabled || 117 window.gBrowser.preloadedBrowser || 118 !window.toolbar.visible || 119 window.document.hidden 120 ) { 121 return; 122 } 123 124 // Don't bother creating a preload browser if we're not in the top set of windows: 125 let windowPrivate = lazy.PrivateBrowsingUtils.isWindowPrivate(window); 126 let countKey = windowPrivate ? "private" : "normal"; 127 let topWindows = lazy.BrowserWindowTracker.orderedWindows.filter( 128 w => lazy.PrivateBrowsingUtils.isWindowPrivate(w) == windowPrivate 129 ); 130 if (topWindows.indexOf(window) >= this.MAX_COUNT) { 131 return; 132 } 133 134 // If we're in the top set of windows, and we already have enough preloaded 135 // tabs, don't create yet another one, just steal an existing one: 136 if (this.browserCounts[countKey] >= this.MAX_COUNT) { 137 let browser = this._adoptBrowserFromOtherWindow(window); 138 // We can potentially get null here if we couldn't actually find another 139 // browser to adopt from. This can be the case when there's a mix of 140 // private and non-private windows, for instance. 141 if (browser) { 142 return; 143 } 144 } 145 146 let browser = this._createBrowser(window); 147 browser.loadURI(Services.io.newURI(window.BROWSER_NEW_TAB_URL), { 148 triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), 149 }); 150 browser.docShellIsActive = false; 151 152 window.gURLBar.getBrowserState(browser).urlbarFocused = true; 153 154 // Make sure the preloaded browser is loaded with desired zoom level 155 let tabURI = Services.io.newURI(window.BROWSER_NEW_TAB_URL); 156 window.FullZoom.onLocationChange(tabURI, false, browser); 157 158 this.browserCounts[countKey]++; 159 }, 160 161 getPreloadedBrowser(window) { 162 if (!this.enabled) { 163 return null; 164 } 165 166 // The preloaded browser might be null. 167 let browser = window.gBrowser.preloadedBrowser; 168 169 // Consume the browser. 170 window.gBrowser.preloadedBrowser = null; 171 172 // Attach the nsIFormFillController now that we know the browser 173 // will be used. If we do that before and the preloaded browser 174 // won't be consumed until shutdown then we leak a docShell. 175 // Also, we do not need to take care of attaching nsIFormFillControllers 176 // in the case that the browser is remote, as remote browsers take 177 // care of that themselves. 178 if (browser) { 179 let countKey = lazy.PrivateBrowsingUtils.isWindowPrivate(window) 180 ? "private" 181 : "normal"; 182 this.browserCounts[countKey]--; 183 browser.removeAttribute("preloadedState"); 184 browser.setAttribute("autocompletepopup", "PopupAutoComplete"); 185 // Let a preloaded about:tor page know that it is no longer preloaded 186 // (about to be shown). See tor-browser#44314. 187 // NOTE: We call the AboutTorParent instance directly because it is not 188 // reliable for the AboutTorParent to wait for the "preloadedState" 189 // attribute to change via a MutationObserver on the browsingContext's 190 // browser element because the AboutTorParent's browsingContext's browser 191 // element may be swapped out. E.g. see the "SwapDocShells" event. 192 // NOTE: We assume that this is the only place that removes the 193 // "preloadedState" attribute. 194 // NOTE: Alternatively, we could have the AboutTorParent wait for 195 // MozAfterPaint, but this would be slightly delayed. 196 try { 197 browser.browsingContext?.currentWindowGlobal 198 ?.getActor("AboutTor") 199 .preloadedRemoved(); 200 } catch { 201 // Not an about:tor page with an AboutTorParent instance. 202 } 203 } 204 205 return browser; 206 }, 207 208 removePreloadedBrowser(window) { 209 let browser = this.getPreloadedBrowser(window); 210 if (browser) { 211 window.gBrowser.getPanel(browser).remove(); 212 } 213 }, 214 }; 215 216 XPCOMUtils.defineLazyPreferenceGetter( 217 NewTabPagePreloading, 218 "prefEnabled", 219 "browser.newtab.preload", 220 true 221 ); 222 XPCOMUtils.defineLazyPreferenceGetter( 223 NewTabPagePreloading, 224 "newTabEnabled", 225 "browser.newtabpage.enabled", 226 true 227 );