diff options
Diffstat (limited to 'browser/base/content/browser-thumbnails.js')
-rw-r--r-- | browser/base/content/browser-thumbnails.js | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/browser/base/content/browser-thumbnails.js b/browser/base/content/browser-thumbnails.js new file mode 100644 index 0000000000..3456d14b3b --- /dev/null +++ b/browser/base/content/browser-thumbnails.js @@ -0,0 +1,224 @@ +/* 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 file is loaded into the browser window scope. +/* eslint-env mozilla/browser-window */ + +/** + * Keeps thumbnails of open web pages up-to-date. + */ +var gBrowserThumbnails = { + /** + * Pref that controls whether we can store SSL content on disk + */ + PREF_DISK_CACHE_SSL: "browser.cache.disk_cache_ssl", + + _captureDelayMS: 1000, + + /** + * Used to keep track of disk_cache_ssl preference + */ + _sslDiskCacheEnabled: null, + + /** + * Map of capture() timeouts assigned to their browsers. + */ + _timeouts: null, + + /** + * Top site URLs refresh timer. + */ + _topSiteURLsRefreshTimer: null, + + /** + * List of tab events we want to listen for. + */ + _tabEvents: ["TabClose", "TabSelect"], + + init: function Thumbnails_init() { + gBrowser.addTabsProgressListener(this); + Services.prefs.addObserver(this.PREF_DISK_CACHE_SSL, this); + + this._sslDiskCacheEnabled = Services.prefs.getBoolPref( + this.PREF_DISK_CACHE_SSL + ); + + this._tabEvents.forEach(function(aEvent) { + gBrowser.tabContainer.addEventListener(aEvent, this); + }, this); + + this._timeouts = new WeakMap(); + }, + + uninit: function Thumbnails_uninit() { + gBrowser.removeTabsProgressListener(this); + Services.prefs.removeObserver(this.PREF_DISK_CACHE_SSL, this); + + if (this._topSiteURLsRefreshTimer) { + this._topSiteURLsRefreshTimer.cancel(); + this._topSiteURLsRefreshTimer = null; + } + + this._tabEvents.forEach(function(aEvent) { + gBrowser.tabContainer.removeEventListener(aEvent, this); + }, this); + }, + + handleEvent: function Thumbnails_handleEvent(aEvent) { + switch (aEvent.type) { + case "scroll": + let browser = aEvent.currentTarget; + if (this._timeouts.has(browser)) { + this._delayedCapture(browser); + } + break; + case "TabSelect": + this._delayedCapture(aEvent.target.linkedBrowser); + break; + case "TabClose": { + this._cancelDelayedCapture(aEvent.target.linkedBrowser); + break; + } + } + }, + + observe: function Thumbnails_observe(subject, topic, data) { + switch (data) { + case this.PREF_DISK_CACHE_SSL: + this._sslDiskCacheEnabled = Services.prefs.getBoolPref( + this.PREF_DISK_CACHE_SSL + ); + break; + } + }, + + clearTopSiteURLCache: function Thumbnails_clearTopSiteURLCache() { + if (this._topSiteURLsRefreshTimer) { + this._topSiteURLsRefreshTimer.cancel(); + this._topSiteURLsRefreshTimer = null; + } + // Delete the defined property + delete this._topSiteURLs; + XPCOMUtils.defineLazyGetter(this, "_topSiteURLs", getTopSiteURLs); + }, + + notify: function Thumbnails_notify(timer) { + gBrowserThumbnails._topSiteURLsRefreshTimer = null; + gBrowserThumbnails.clearTopSiteURLCache(); + }, + + /** + * State change progress listener for all tabs. + */ + onStateChange: function Thumbnails_onStateChange( + aBrowser, + aWebProgress, + aRequest, + aStateFlags, + aStatus + ) { + if ( + aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK + ) { + this._delayedCapture(aBrowser); + } + }, + + async _capture(aBrowser) { + // Only capture about:newtab top sites. + const topSites = await this._topSiteURLs; + if (!aBrowser.currentURI || !topSites.includes(aBrowser.currentURI.spec)) { + return; + } + if (await this._shouldCapture(aBrowser)) { + await PageThumbs.captureAndStoreIfStale(aBrowser); + } + }, + + _delayedCapture: function Thumbnails_delayedCapture(aBrowser) { + if (this._timeouts.has(aBrowser)) { + this._cancelDelayedCallbacks(aBrowser); + } else { + aBrowser.addEventListener("scroll", this, true); + } + + let idleCallback = () => { + this._cancelDelayedCapture(aBrowser); + this._capture(aBrowser); + }; + + // setTimeout to set a guarantee lower bound for the requestIdleCallback + // (and therefore the delayed capture) + let timeoutId = setTimeout(() => { + let idleCallbackId = requestIdleCallback(idleCallback, { + timeout: this._captureDelayMS * 30, + }); + this._timeouts.set(aBrowser, { isTimeout: false, id: idleCallbackId }); + }, this._captureDelayMS); + + this._timeouts.set(aBrowser, { isTimeout: true, id: timeoutId }); + }, + + _shouldCapture: async function Thumbnails_shouldCapture(aBrowser) { + // Capture only if it's the currently selected tab and not an about: page. + if ( + aBrowser != gBrowser.selectedBrowser || + gBrowser.currentURI.schemeIs("about") + ) { + return false; + } + return PageThumbs.shouldStoreThumbnail(aBrowser); + }, + + _cancelDelayedCapture: function Thumbnails_cancelDelayedCapture(aBrowser) { + if (this._timeouts.has(aBrowser)) { + aBrowser.removeEventListener("scroll", this); + this._cancelDelayedCallbacks(aBrowser); + this._timeouts.delete(aBrowser); + } + }, + + _cancelDelayedCallbacks: function Thumbnails_cancelDelayedCallbacks( + aBrowser + ) { + let timeoutData = this._timeouts.get(aBrowser); + + if (timeoutData.isTimeout) { + clearTimeout(timeoutData.id); + } else { + // idle callback dispatched + window.cancelIdleCallback(timeoutData.id); + } + }, +}; + +async function getTopSiteURLs() { + // The _topSiteURLs getter can be expensive to run, but its return value can + // change frequently on new profiles, so as a compromise we cache its return + // value as a lazy getter for 1 minute every time it's called. + gBrowserThumbnails._topSiteURLsRefreshTimer = Cc[ + "@mozilla.org/timer;1" + ].createInstance(Ci.nsITimer); + gBrowserThumbnails._topSiteURLsRefreshTimer.initWithCallback( + gBrowserThumbnails, + 60 * 1000, + Ci.nsITimer.TYPE_ONE_SHOT + ); + let sites = []; + // Get both the top sites returned by the query, and also any pinned sites + // that the user might have added manually that also need a screenshot. + // Also include top sites that don't have rich icons + let topSites = await NewTabUtils.activityStreamLinks.getTopSites(); + sites.push(...topSites.filter(link => !(link.faviconSize >= 96))); + sites.push(...NewTabUtils.pinnedLinks.links); + return sites.reduce((urls, link) => { + if (link) { + urls.push(link.url); + } + return urls; + }, []); +} + +XPCOMUtils.defineLazyGetter(gBrowserThumbnails, "_topSiteURLs", getTopSiteURLs); |