browser-thumbnails.js (6483B)
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 * Keeps thumbnails of open web pages up-to-date. 7 */ 8 var gBrowserThumbnails = { 9 /** 10 * Pref that controls whether we can store SSL content on disk 11 */ 12 PREF_DISK_CACHE_SSL: "browser.cache.disk_cache_ssl", 13 14 _captureDelayMS: 1000, 15 16 /** 17 * Used to keep track of disk_cache_ssl preference 18 */ 19 _sslDiskCacheEnabled: null, 20 21 /** 22 * Map of capture() timeouts assigned to their browsers. 23 */ 24 _timeouts: null, 25 26 /** 27 * Top site URLs refresh timer. 28 */ 29 _topSiteURLsRefreshTimer: null, 30 31 /** 32 * List of tab events we want to listen for. 33 */ 34 _tabEvents: ["TabClose", "TabSelect"], 35 36 init: function Thumbnails_init() { 37 gBrowser.addTabsProgressListener(this); 38 Services.prefs.addObserver(this.PREF_DISK_CACHE_SSL, this); 39 40 this._sslDiskCacheEnabled = Services.prefs.getBoolPref( 41 this.PREF_DISK_CACHE_SSL 42 ); 43 44 this._tabEvents.forEach(function (aEvent) { 45 gBrowser.tabContainer.addEventListener(aEvent, this); 46 }, this); 47 48 this._timeouts = new WeakMap(); 49 }, 50 51 uninit: function Thumbnails_uninit() { 52 gBrowser.removeTabsProgressListener(this); 53 Services.prefs.removeObserver(this.PREF_DISK_CACHE_SSL, this); 54 55 if (this._topSiteURLsRefreshTimer) { 56 this._topSiteURLsRefreshTimer.cancel(); 57 this._topSiteURLsRefreshTimer = null; 58 } 59 60 this._tabEvents.forEach(function (aEvent) { 61 gBrowser.tabContainer.removeEventListener(aEvent, this); 62 }, this); 63 }, 64 65 handleEvent: function Thumbnails_handleEvent(aEvent) { 66 switch (aEvent.type) { 67 case "scroll": { 68 let browser = aEvent.currentTarget; 69 if (this._timeouts.has(browser)) { 70 this._delayedCapture(browser); 71 } 72 break; 73 } 74 case "TabSelect": 75 this._delayedCapture(aEvent.target.linkedBrowser); 76 break; 77 case "TabClose": { 78 this._cancelDelayedCapture(aEvent.target.linkedBrowser); 79 break; 80 } 81 } 82 }, 83 84 observe: function Thumbnails_observe(subject, topic, data) { 85 switch (data) { 86 case this.PREF_DISK_CACHE_SSL: 87 this._sslDiskCacheEnabled = Services.prefs.getBoolPref( 88 this.PREF_DISK_CACHE_SSL 89 ); 90 break; 91 } 92 }, 93 94 clearTopSiteURLCache: function Thumbnails_clearTopSiteURLCache() { 95 if (this._topSiteURLsRefreshTimer) { 96 this._topSiteURLsRefreshTimer.cancel(); 97 this._topSiteURLsRefreshTimer = null; 98 } 99 // Delete the defined property 100 delete this._topSiteURLs; 101 ChromeUtils.defineLazyGetter(this, "_topSiteURLs", getTopSiteURLs); 102 }, 103 104 notify: function Thumbnails_notify() { 105 gBrowserThumbnails._topSiteURLsRefreshTimer = null; 106 gBrowserThumbnails.clearTopSiteURLCache(); 107 }, 108 109 /** 110 * State change progress listener for all tabs. 111 */ 112 onStateChange: function Thumbnails_onStateChange( 113 aBrowser, 114 aWebProgress, 115 aRequest, 116 aStateFlags, 117 _aStatus 118 ) { 119 if ( 120 aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && 121 aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK 122 ) { 123 this._delayedCapture(aBrowser); 124 } 125 }, 126 127 async _capture(aBrowser) { 128 // Only capture about:newtab top sites. 129 const topSites = await this._topSiteURLs; 130 if (!aBrowser.currentURI || !topSites.includes(aBrowser.currentURI.spec)) { 131 return; 132 } 133 if (await this._shouldCapture(aBrowser)) { 134 await PageThumbs.captureAndStoreIfStale(aBrowser); 135 } 136 }, 137 138 _delayedCapture: function Thumbnails_delayedCapture(aBrowser) { 139 if (this._timeouts.has(aBrowser)) { 140 this._cancelDelayedCallbacks(aBrowser); 141 } else { 142 aBrowser.addEventListener("scroll", this, true); 143 } 144 145 let idleCallback = () => { 146 this._cancelDelayedCapture(aBrowser); 147 this._capture(aBrowser); 148 }; 149 150 // setTimeout to set a guarantee lower bound for the requestIdleCallback 151 // (and therefore the delayed capture) 152 let timeoutId = setTimeout(() => { 153 let idleCallbackId = requestIdleCallback(idleCallback, { 154 timeout: this._captureDelayMS * 30, 155 }); 156 this._timeouts.set(aBrowser, { isTimeout: false, id: idleCallbackId }); 157 }, this._captureDelayMS); 158 159 this._timeouts.set(aBrowser, { isTimeout: true, id: timeoutId }); 160 }, 161 162 _shouldCapture: async function Thumbnails_shouldCapture(aBrowser) { 163 // Capture only if it's the currently selected tab and not an about: page. 164 if ( 165 aBrowser != gBrowser.selectedBrowser || 166 gBrowser.currentURI.schemeIs("about") 167 ) { 168 return false; 169 } 170 return PageThumbs.shouldStoreThumbnail(aBrowser); 171 }, 172 173 _cancelDelayedCapture: function Thumbnails_cancelDelayedCapture(aBrowser) { 174 if (this._timeouts.has(aBrowser)) { 175 aBrowser.removeEventListener("scroll", this); 176 this._cancelDelayedCallbacks(aBrowser); 177 this._timeouts.delete(aBrowser); 178 } 179 }, 180 181 _cancelDelayedCallbacks: function Thumbnails_cancelDelayedCallbacks( 182 aBrowser 183 ) { 184 let timeoutData = this._timeouts.get(aBrowser); 185 186 if (timeoutData.isTimeout) { 187 clearTimeout(timeoutData.id); 188 } else { 189 // idle callback dispatched 190 window.cancelIdleCallback(timeoutData.id); 191 } 192 }, 193 }; 194 195 async function getTopSiteURLs() { 196 // The _topSiteURLs getter can be expensive to run, but its return value can 197 // change frequently on new profiles, so as a compromise we cache its return 198 // value as a lazy getter for 1 minute every time it's called. 199 gBrowserThumbnails._topSiteURLsRefreshTimer = Cc[ 200 "@mozilla.org/timer;1" 201 ].createInstance(Ci.nsITimer); 202 gBrowserThumbnails._topSiteURLsRefreshTimer.initWithCallback( 203 gBrowserThumbnails, 204 60 * 1000, 205 Ci.nsITimer.TYPE_ONE_SHOT 206 ); 207 let sites = []; 208 // Get both the top sites returned by the query, and also any pinned sites 209 // that the user might have added manually that also need a screenshot. 210 // Also include top sites that don't have rich icons 211 let topSites = await NewTabUtils.activityStreamLinks.getTopSites(); 212 sites.push(...topSites.filter(link => !(link.faviconSize >= 96))); 213 sites.push(...NewTabUtils.pinnedLinks.links); 214 return sites.reduce((urls, link) => { 215 if (link) { 216 urls.push(link.url); 217 } 218 return urls; 219 }, []); 220 } 221 222 ChromeUtils.defineLazyGetter( 223 gBrowserThumbnails, 224 "_topSiteURLs", 225 getTopSiteURLs 226 );