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/. */
5 // This file is loaded into the browser window scope.
6 /* eslint-env mozilla/browser-window */
9 * Keeps thumbnails of open web pages up-to-date.
11 var gBrowserThumbnails = {
13 * Pref that controls whether we can store SSL content on disk
15 PREF_DISK_CACHE_SSL: "browser.cache.disk_cache_ssl",
17 _captureDelayMS: 1000,
20 * Used to keep track of disk_cache_ssl preference
22 _sslDiskCacheEnabled: null,
25 * Map of capture() timeouts assigned to their browsers.
30 * Top site URLs refresh timer.
32 _topSiteURLsRefreshTimer: null,
35 * List of tab events we want to listen for.
37 _tabEvents: ["TabClose", "TabSelect"],
39 init: function Thumbnails_init() {
40 gBrowser.addTabsProgressListener(this);
41 Services.prefs.addObserver(this.PREF_DISK_CACHE_SSL, this);
43 this._sslDiskCacheEnabled = Services.prefs.getBoolPref(
44 this.PREF_DISK_CACHE_SSL
47 this._tabEvents.forEach(function(aEvent) {
48 gBrowser.tabContainer.addEventListener(aEvent, this);
51 this._timeouts = new WeakMap();
54 uninit: function Thumbnails_uninit() {
55 gBrowser.removeTabsProgressListener(this);
56 Services.prefs.removeObserver(this.PREF_DISK_CACHE_SSL, this);
58 if (this._topSiteURLsRefreshTimer) {
59 this._topSiteURLsRefreshTimer.cancel();
60 this._topSiteURLsRefreshTimer = null;
63 this._tabEvents.forEach(function(aEvent) {
64 gBrowser.tabContainer.removeEventListener(aEvent, this);
68 handleEvent: function Thumbnails_handleEvent(aEvent) {
69 switch (aEvent.type) {
71 let browser = aEvent.currentTarget;
72 if (this._timeouts.has(browser)) {
73 this._delayedCapture(browser);
77 this._delayedCapture(aEvent.target.linkedBrowser);
80 this._cancelDelayedCapture(aEvent.target.linkedBrowser);
86 observe: function Thumbnails_observe(subject, topic, data) {
88 case this.PREF_DISK_CACHE_SSL:
89 this._sslDiskCacheEnabled = Services.prefs.getBoolPref(
90 this.PREF_DISK_CACHE_SSL
96 clearTopSiteURLCache: function Thumbnails_clearTopSiteURLCache() {
97 if (this._topSiteURLsRefreshTimer) {
98 this._topSiteURLsRefreshTimer.cancel();
99 this._topSiteURLsRefreshTimer = null;
101 // Delete the defined property
102 delete this._topSiteURLs;
103 XPCOMUtils.defineLazyGetter(this, "_topSiteURLs", getTopSiteURLs);
106 notify: function Thumbnails_notify(timer) {
107 gBrowserThumbnails._topSiteURLsRefreshTimer = null;
108 gBrowserThumbnails.clearTopSiteURLCache();
112 * State change progress listener for all tabs.
114 onStateChange: function Thumbnails_onStateChange(
122 aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
123 aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK
125 this._delayedCapture(aBrowser);
129 async _capture(aBrowser) {
130 // Only capture about:newtab top sites.
131 const topSites = await this._topSiteURLs;
132 if (!aBrowser.currentURI || !topSites.includes(aBrowser.currentURI.spec)) {
135 if (await this._shouldCapture(aBrowser)) {
136 await PageThumbs.captureAndStoreIfStale(aBrowser);
140 _delayedCapture: function Thumbnails_delayedCapture(aBrowser) {
141 if (this._timeouts.has(aBrowser)) {
142 this._cancelDelayedCallbacks(aBrowser);
144 aBrowser.addEventListener("scroll", this, true);
147 let idleCallback = () => {
148 this._cancelDelayedCapture(aBrowser);
149 this._capture(aBrowser);
152 // setTimeout to set a guarantee lower bound for the requestIdleCallback
153 // (and therefore the delayed capture)
154 let timeoutId = setTimeout(() => {
155 let idleCallbackId = requestIdleCallback(idleCallback, {
156 timeout: this._captureDelayMS * 30,
158 this._timeouts.set(aBrowser, { isTimeout: false, id: idleCallbackId });
159 }, this._captureDelayMS);
161 this._timeouts.set(aBrowser, { isTimeout: true, id: timeoutId });
164 _shouldCapture: async function Thumbnails_shouldCapture(aBrowser) {
165 // Capture only if it's the currently selected tab and not an about: page.
167 aBrowser != gBrowser.selectedBrowser ||
168 gBrowser.currentURI.schemeIs("about")
172 return PageThumbs.shouldStoreThumbnail(aBrowser);
175 _cancelDelayedCapture: function Thumbnails_cancelDelayedCapture(aBrowser) {
176 if (this._timeouts.has(aBrowser)) {
177 aBrowser.removeEventListener("scroll", this);
178 this._cancelDelayedCallbacks(aBrowser);
179 this._timeouts.delete(aBrowser);
183 _cancelDelayedCallbacks: function Thumbnails_cancelDelayedCallbacks(
186 let timeoutData = this._timeouts.get(aBrowser);
188 if (timeoutData.isTimeout) {
189 clearTimeout(timeoutData.id);
191 // idle callback dispatched
192 window.cancelIdleCallback(timeoutData.id);
197 async function getTopSiteURLs() {
198 // The _topSiteURLs getter can be expensive to run, but its return value can
199 // change frequently on new profiles, so as a compromise we cache its return
200 // value as a lazy getter for 1 minute every time it's called.
201 gBrowserThumbnails._topSiteURLsRefreshTimer = Cc[
202 "@mozilla.org/timer;1"
203 ].createInstance(Ci.nsITimer);
204 gBrowserThumbnails._topSiteURLsRefreshTimer.initWithCallback(
207 Ci.nsITimer.TYPE_ONE_SHOT
210 // Get both the top sites returned by the query, and also any pinned sites
211 // that the user might have added manually that also need a screenshot.
212 // Also include top sites that don't have rich icons
213 let topSites = await NewTabUtils.activityStreamLinks.getTopSites();
214 sites.push(...topSites.filter(link => !(link.faviconSize >= 96)));
215 sites.push(...NewTabUtils.pinnedLinks.links);
216 return sites.reduce((urls, link) => {
224 XPCOMUtils.defineLazyGetter(gBrowserThumbnails, "_topSiteURLs", getTopSiteURLs);