/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * This module is in charge of preloading 'new tab' pages for use when * the user opens a new tab. */ var EXPORTED_SYMBOLS = ["NewTabPagePreloading"]; const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetters(this, { AboutNewTab: "resource:///modules/AboutNewTab.jsm", BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm", E10SUtils: "resource://gre/modules/E10SUtils.jsm", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm", }); let NewTabPagePreloading = { // Maximum number of instances of a given page we'll preload at any time. // Because we preload about:newtab for normal windows, and about:privatebrowsing // for private ones, we could have 3 of each. MAX_COUNT: 3, // How many preloaded tabs we have, across all windows, for the private and non-private // case: browserCounts: { normal: 0, private: 0, }, get enabled() { return ( this.prefEnabled && this.newTabEnabled && !AboutNewTab.newTabURLOverridden ); }, /** * Create a browser in the right process type. */ _createBrowser(win) { const { gBrowser, gMultiProcessBrowser, gFissionBrowser, BROWSER_NEW_TAB_URL, } = win; let oa = E10SUtils.predictOriginAttributes({ window: win }); let remoteType = E10SUtils.getRemoteTypeForURI( BROWSER_NEW_TAB_URL, gMultiProcessBrowser, gFissionBrowser, E10SUtils.DEFAULT_REMOTE_TYPE, null, oa ); let browser = gBrowser.createBrowser({ isPreloadBrowser: true, remoteType, }); gBrowser.preloadedBrowser = browser; let panel = gBrowser.getPanel(browser); gBrowser.tabpanels.appendChild(panel); return browser; }, /** * Move the contents of a preload browser across to a different window. */ _adoptBrowserFromOtherWindow(window) { let winPrivate = PrivateBrowsingUtils.isWindowPrivate(window); // Grab the least-recently-focused window with a preloaded browser: let oldWin = BrowserWindowTracker.orderedWindows .filter(w => { return ( winPrivate == PrivateBrowsingUtils.isWindowPrivate(w) && w.gBrowser && w.gBrowser.preloadedBrowser ); }) .pop(); if (!oldWin) { return null; } // Don't call getPreloadedBrowser because it'll consume the browser: let oldBrowser = oldWin.gBrowser.preloadedBrowser; oldWin.gBrowser.preloadedBrowser = null; let newBrowser = this._createBrowser(window); oldBrowser.swapBrowsers(newBrowser); newBrowser.permanentKey = oldBrowser.permanentKey; oldWin.gBrowser.getPanel(oldBrowser).remove(); return newBrowser; }, maybeCreatePreloadedBrowser(window) { // If we're not enabled, have already got one, are in a popup window, or the // window is minimized / occluded, don't bother creating a preload browser - // there's no point. if ( !this.enabled || window.gBrowser.preloadedBrowser || !window.toolbar.visible || window.windowState == window.STATE_MINIMIZED || window.isFullyOccluded ) { return; } // Don't bother creating a preload browser if we're not in the top set of windows: let windowPrivate = PrivateBrowsingUtils.isWindowPrivate(window); let countKey = windowPrivate ? "private" : "normal"; let topWindows = BrowserWindowTracker.orderedWindows.filter( w => PrivateBrowsingUtils.isWindowPrivate(w) == windowPrivate ); if (topWindows.indexOf(window) >= this.MAX_COUNT) { return; } // If we're in the top set of windows, and we already have enough preloaded // tabs, don't create yet another one, just steal an existing one: if (this.browserCounts[countKey] >= this.MAX_COUNT) { let browser = this._adoptBrowserFromOtherWindow(window); // We can potentially get null here if we couldn't actually find another // browser to adopt from. This can be the case when there's a mix of // private and non-private windows, for instance. if (browser) { return; } } let browser = this._createBrowser(window); browser.loadURI(window.BROWSER_NEW_TAB_URL, { triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), }); browser.docShellIsActive = false; browser._urlbarFocused = true; // Make sure the preloaded browser is loaded with desired zoom level let tabURI = Services.io.newURI(window.BROWSER_NEW_TAB_URL); window.FullZoom.onLocationChange(tabURI, false, browser); this.browserCounts[countKey]++; }, getPreloadedBrowser(window) { if (!this.enabled) { return null; } // The preloaded browser might be null. let browser = window.gBrowser.preloadedBrowser; // Consume the browser. window.gBrowser.preloadedBrowser = null; // Attach the nsIFormFillController now that we know the browser // will be used. If we do that before and the preloaded browser // won't be consumed until shutdown then we leak a docShell. // Also, we do not need to take care of attaching nsIFormFillControllers // in the case that the browser is remote, as remote browsers take // care of that themselves. if (browser) { let countKey = PrivateBrowsingUtils.isWindowPrivate(window) ? "private" : "normal"; this.browserCounts[countKey]--; browser.setAttribute("preloadedState", "consumed"); browser.setAttribute("autocompletepopup", "PopupAutoComplete"); } return browser; }, removePreloadedBrowser(window) { let browser = this.getPreloadedBrowser(window); if (browser) { window.gBrowser.getPanel(browser).remove(); } }, }; XPCOMUtils.defineLazyPreferenceGetter( NewTabPagePreloading, "prefEnabled", "browser.newtab.preload", true ); XPCOMUtils.defineLazyPreferenceGetter( NewTabPagePreloading, "newTabEnabled", "browser.newtabpage.enabled", true );