Screenshots.sys.mjs (5118B)
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 // We use importESModule here instead of static import so that 6 // the Karma test environment won't choke on this module. This 7 // is because the Karma test environment already stubs out 8 // XPCOMUtils, and overrides importESModule to be a no-op (which 9 // can't be done for a static import statement). 10 11 // eslint-disable-next-line mozilla/use-static-import 12 const { XPCOMUtils } = ChromeUtils.importESModule( 13 "resource://gre/modules/XPCOMUtils.sys.mjs" 14 ); 15 16 const lazy = {}; 17 18 ChromeUtils.defineESModuleGetters(lazy, { 19 BackgroundPageThumbs: "resource://gre/modules/BackgroundPageThumbs.sys.mjs", 20 PageThumbs: "resource://gre/modules/PageThumbs.sys.mjs", 21 PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", 22 }); 23 24 const GREY_10 = "#F9F9FA"; 25 26 XPCOMUtils.defineLazyPreferenceGetter( 27 lazy, 28 "gPrivilegedAboutProcessEnabled", 29 "browser.tabs.remote.separatePrivilegedContentProcess", 30 false 31 ); 32 33 export const Screenshots = { 34 /** 35 * Get a screenshot / thumbnail for a url. Either returns the disk cached 36 * image or initiates a background request for the url. 37 * 38 * @param url {string} The url to get a thumbnail 39 * @return {Promise} Resolves a custom object or null if failed 40 */ 41 async getScreenshotForURL(url) { 42 try { 43 await lazy.BackgroundPageThumbs.captureIfMissing(url, { 44 backgroundColor: GREY_10, 45 }); 46 47 // The privileged about content process is able to use the moz-page-thumb 48 // protocol, so if it's enabled, send that down. 49 if (lazy.gPrivilegedAboutProcessEnabled) { 50 return lazy.PageThumbs.getThumbnailURL(url); 51 } 52 53 // Otherwise, for normal content processes, we fallback to using 54 // Blob URIs for the screenshots. 55 const imgPath = lazy.PageThumbs.getThumbnailPath(url); 56 57 const filePathResponse = await fetch(`file://${imgPath}`); 58 const fileContents = await filePathResponse.blob(); 59 60 // Check if the file is empty, which indicates there isn't actually a 61 // thumbnail, so callers can show a failure state. 62 if (fileContents.size === 0) { 63 return null; 64 } 65 66 return { path: imgPath, data: fileContents }; 67 } catch (err) { 68 console.error(`getScreenshot(${url}) failed:`, err); 69 } 70 71 // We must have failed to get the screenshot, so persist the failure by 72 // storing an empty file. Future calls will then skip requesting and return 73 // failure, so do the same thing here. The empty file should not expire with 74 // the usual filtering process to avoid repeated background requests, which 75 // can cause unwanted high CPU, network and memory usage - Bug 1384094 76 try { 77 await lazy.PageThumbs._store(url, url, null, true); 78 } catch (err) { 79 // Probably failed to create the empty file, but not much more we can do. 80 } 81 return null; 82 }, 83 84 /** 85 * Checks if all the open windows are private browsing windows. If so, we do not 86 * want to collect screenshots. If there exists at least 1 non-private window, 87 * we are ok to collect screenshots. 88 */ 89 _shouldGetScreenshots() { 90 for (let win of Services.wm.getEnumerator("navigator:browser")) { 91 if (!lazy.PrivateBrowsingUtils.isWindowPrivate(win)) { 92 // As soon as we encounter 1 non-private window, screenshots are fair game. 93 return true; 94 } 95 } 96 return false; 97 }, 98 99 /** 100 * Conditionally get a screenshot for a link if there's no existing pending 101 * screenshot. Updates the cached link's desired property with the result. 102 * 103 * @param link {object} Link object to update 104 * @param url {string} Url to get a screenshot of 105 * @param property {string} Name of property on object to set 106 @ @param onScreenshot {function} Callback for when the screenshot loads 107 */ 108 async maybeCacheScreenshot(link, url, property, onScreenshot) { 109 // If there are only private windows open, do not collect screenshots 110 if (!this._shouldGetScreenshots()) { 111 return; 112 } 113 // __sharedCache may not exist yet for links from default top sites that 114 // don't have a default tippy top icon. 115 if (!link.__sharedCache) { 116 link.__sharedCache = { 117 updateLink(prop, val) { 118 link[prop] = val; 119 }, 120 }; 121 } 122 const cache = link.__sharedCache; 123 // Nothing to do if we already have a pending screenshot or 124 // if a previous request failed and returned null. 125 if (cache.fetchingScreenshot || link[property] !== undefined) { 126 return; 127 } 128 129 // Save the promise to the cache so other links get it immediately 130 cache.fetchingScreenshot = this.getScreenshotForURL(url); 131 132 // Clean up now that we got the screenshot 133 const screenshot = await cache.fetchingScreenshot; 134 delete cache.fetchingScreenshot; 135 136 // Update the cache for future links and call back for existing content 137 cache.updateLink(property, screenshot); 138 onScreenshot(screenshot); 139 }, 140 };