TaskbarTabsPin.sys.mjs (8130B)
1 /* vim: se cin sw=2 ts=2 et filetype=javascript : 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 let lazy = {}; 7 8 ChromeUtils.defineESModuleGetters(lazy, { 9 ShellService: "moz-src:///browser/components/shell/ShellService.sys.mjs", 10 TaskbarTabsUtils: "resource:///modules/taskbartabs/TaskbarTabsUtils.sys.mjs", 11 }); 12 13 ChromeUtils.defineLazyGetter(lazy, "logConsole", () => { 14 return console.createInstance({ 15 prefix: "TaskbarTabs", 16 maxLogLevel: "Warn", 17 }); 18 }); 19 20 /** 21 * Provides functionality to pin and unping Taskbar Tabs. 22 */ 23 export const TaskbarTabsPin = { 24 /** 25 * Pins the provided Taskbar Tab to the taskbar. 26 * 27 * @param {TaskbarTab} aTaskbarTab - A Taskbar Tab to pin to the taskbar. 28 * @param {TaskbarTabsRegistry} aRegistry - The registry to track pin resources with. 29 * @returns {Promise} Resolves once finished. 30 */ 31 async pinTaskbarTab(aTaskbarTab, aRegistry) { 32 lazy.logConsole.info("Pinning Taskbar Tab to the taskbar."); 33 34 try { 35 let iconPath = await createTaskbarIconFromFavicon(aTaskbarTab); 36 37 let shortcut = await createShortcut(aTaskbarTab, iconPath, aRegistry); 38 39 await lazy.ShellService.pinShortcutToTaskbar( 40 aTaskbarTab.id, 41 "Programs", 42 shortcut 43 ); 44 Glean.webApp.pin.record({ result: "Success" }); 45 } catch (e) { 46 lazy.logConsole.error(`An error occurred while pinning: ${e.message}`); 47 Glean.webApp.pin.record({ result: e.name ?? "Unknown exception" }); 48 } 49 }, 50 51 /** 52 * Unpins the provided Taskbar Tab from the taskbar. 53 * 54 * @param {TaskbarTab} aTaskbarTab - The Taskbar Tab to unpin from the taskbar. 55 * @param {TaskbarTabsRegistry} aRegistry - remove pinned resource tracking from. 56 * @returns {Promise} Resolves once finished. 57 */ 58 async unpinTaskbarTab(aTaskbarTab, aRegistry) { 59 let unpinError = null; 60 let removalError = null; 61 62 try { 63 lazy.logConsole.info("Unpinning Taskbar Tab from the taskbar."); 64 65 let { relativePath } = await generateShortcutInfo(aTaskbarTab); 66 67 try { 68 lazy.ShellService.unpinShortcutFromTaskbar("Programs", relativePath); 69 } catch (e) { 70 lazy.logConsole.error(`Failed to unpin shortcut: ${e.message}`); 71 unpinError = e; 72 } 73 74 let iconFile = getIconFile(aTaskbarTab); 75 76 lazy.logConsole.debug(`Deleting ${relativePath}`); 77 lazy.logConsole.debug(`Deleting ${iconFile.path}`); 78 79 await Promise.all([ 80 lazy.ShellService.deleteShortcut("Programs", relativePath).then(() => { 81 // Only update if that didn't throw an error. 82 aRegistry.patchTaskbarTab(aTaskbarTab, { 83 shortcutRelativePath: null, 84 }); 85 }), 86 IOUtils.remove(iconFile.path), 87 ]); 88 } catch (e) { 89 lazy.logConsole.error( 90 `An error occurred while uninstalling: ${e.message}` 91 ); 92 removalError = e; 93 } 94 95 const message = e => (e ? (e.name ?? "Unknown exception") : "Success"); 96 Glean.webApp.unpin.record({ 97 result: message(unpinError), 98 removal_result: message(removalError), 99 }); 100 }, 101 102 /** 103 * Gets a Localization object to use when creating shortcuts. 104 * 105 * @returns {Localization} An object to access localized strings. 106 */ 107 _getLocalization() { 108 return new Localization(["branding/brand.ftl", "browser/taskbartabs.ftl"]); 109 }, 110 }; 111 112 /** 113 * Fetches the favicon from the provided Taskbar Tab's start url, and saves it 114 * to an icon file. 115 * 116 * @param {TaskbarTab} aTaskbarTab - The Taskbar Tab to generate an icon file for. 117 * @returns {Promise<nsIFile>} The created icon file. 118 */ 119 async function createTaskbarIconFromFavicon(aTaskbarTab) { 120 lazy.logConsole.info("Creating Taskbar Tabs shortcut icon."); 121 122 let url = Services.io.newURI(aTaskbarTab.startUrl); 123 let imgContainer = await lazy.TaskbarTabsUtils.getFavicon(url); 124 125 let iconFile = getIconFile(aTaskbarTab); 126 127 lazy.logConsole.debug(`Using icon path: ${iconFile.path}`); 128 129 await IOUtils.makeDirectory(iconFile.parent.path); 130 131 await lazy.ShellService.createWindowsIcon(iconFile, imgContainer); 132 133 return iconFile; 134 } 135 136 /** 137 * Creates a shortcut that opens Firefox with relevant Taskbar Tabs flags. 138 * 139 * @param {TaskbarTab} aTaskbarTab - The Taskbar Tab to generate a shortcut for. 140 * @param {nsIFile} aFileIcon - The icon file to use for the shortcut. 141 * @param {TaskbarTabsRegistry} aRegistry - The registry used to save the shortcut path. 142 * @returns {Promise<string>} The path to the created shortcut. 143 */ 144 async function createShortcut(aTaskbarTab, aFileIcon, aRegistry) { 145 lazy.logConsole.info("Creating Taskbar Tabs shortcut."); 146 147 let { relativePath, description } = await generateShortcutInfo(aTaskbarTab); 148 lazy.logConsole.debug( 149 `Using shortcut path relative to Programs folder: ${relativePath}` 150 ); 151 152 let targetfile = Services.dirsvc.get("XREExeF", Ci.nsIFile); 153 let profileFolder = Services.dirsvc.get("ProfD", Ci.nsIFile); 154 155 await lazy.ShellService.createShortcut( 156 targetfile, 157 [ 158 "-taskbar-tab", 159 aTaskbarTab.id, 160 "-new-window", 161 aTaskbarTab.startUrl, // In case Taskbar Tabs is disabled, provide fallback url. 162 "-profile", 163 profileFolder.path, 164 "-container", 165 aTaskbarTab.userContextId, 166 ], 167 description, 168 aFileIcon, 169 0, 170 aTaskbarTab.id, // AUMID 171 "Programs", 172 relativePath 173 ); 174 175 // Only update if that didn't throw an error. 176 aRegistry.patchTaskbarTab(aTaskbarTab, { 177 shortcutRelativePath: relativePath, 178 }); 179 180 return relativePath; 181 } 182 183 /** 184 * Gets the path to the shortcut relative to the Start Menu folder, 185 * as well as the description that should be attached to the shortcut. 186 * 187 * @param {TaskbarTab} aTaskbarTab - The Taskbar Tab to get the path of. 188 * @returns {Promise<{description: string, relativePath: string}>} The description 189 * and relative path of the shortcut. 190 */ 191 async function generateShortcutInfo(aTaskbarTab) { 192 const l10n = TaskbarTabsPin._getLocalization(); 193 194 let basename = sanitizeFilename(aTaskbarTab.name + ".lnk"); 195 let dirname = await l10n.formatValue("taskbar-tab-shortcut-folder"); 196 dirname = sanitizeFilename(dirname, { allowDirectoryNames: true }); 197 198 const description = await l10n.formatValue( 199 "taskbar-tab-shortcut-description", 200 { name: aTaskbarTab.name } 201 ); 202 203 return { 204 description, 205 relativePath: dirname + "\\" + basename, 206 }; 207 } 208 209 /** 210 * Cleans up the filename so it can be saved safely. This means replacing invalid names 211 * (e.g. DOS devices) with others, or replacing invalid characters (e.g. asterisks on 212 * Windows) with underscores. 213 * 214 * @param {string} aWantedName - The name to validate and sanitize. Make sure 215 * that aWantedName has an extension if it will be saved with one. 216 * @param {object} aOptions - Options to affect the sanitization. 217 * @param {boolean} aOptions.allowDirectoryNames - Indicates that the name will be used 218 * as a directory. If so, the validation rules may be slightly more lax. 219 * @returns {string} The sanitized name. 220 */ 221 function sanitizeFilename(aWantedName, { allowDirectoryNames = false } = {}) { 222 const mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); 223 224 let flags = 225 Ci.nsIMIMEService.VALIDATE_SANITIZE_ONLY | 226 Ci.nsIMIMEService.VALIDATE_DONT_COLLAPSE_WHITESPACE | 227 // Don't add .download to the name if it ends with .lnk. 228 Ci.nsIMIMEService.VALIDATE_ALLOW_INVALID_FILENAMES; 229 230 if (allowDirectoryNames) { 231 flags |= Ci.nsIMIMEService.VALIDATE_ALLOW_DIRECTORY_NAMES; 232 } 233 234 return mimeService.validateFileNameForSaving(aWantedName, "", flags); 235 } 236 237 /** 238 * Generates a file path to use for the Taskbar Tab icon file. 239 * 240 * @param {TaskbarTab} aTaskbarTab - A Taskbar Tab to generate an icon file path for. 241 * @returns {nsIFile} The icon file path for the Taskbar Tab. 242 */ 243 function getIconFile(aTaskbarTab) { 244 let iconPath = lazy.TaskbarTabsUtils.getTaskbarTabsFolder(); 245 iconPath.append("icons"); 246 iconPath.append(aTaskbarTab.id + ".ico"); 247 return iconPath; 248 }