FileMigrators.sys.mjs (10159B)
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 const lazy = {}; 6 7 ChromeUtils.defineESModuleGetters(lazy, { 8 BookmarkHTMLUtils: "resource://gre/modules/BookmarkHTMLUtils.sys.mjs", 9 BookmarkJSONUtils: "resource://gre/modules/BookmarkJSONUtils.sys.mjs", 10 LoginCSVImport: "resource://gre/modules/LoginCSVImport.sys.mjs", 11 MigrationWizardConstants: 12 "chrome://browser/content/migration/migration-wizard-constants.mjs", 13 }); 14 15 ChromeUtils.defineLazyGetter(lazy, "gFluentStrings", function () { 16 return new Localization([ 17 "branding/brand.ftl", 18 "browser/migrationWizard.ftl", 19 ]); 20 }); 21 22 /** 23 * Base class for a migration that involves reading a single file off of 24 * the disk that the user picks using a file picker. The file might be 25 * generated by another browser or some other application. 26 */ 27 export class FileMigratorBase { 28 /** 29 * This must be overridden to return a simple string identifier for the 30 * migrator, for example "password-csv". This key is what 31 * is used as an identifier when calling MigrationUtils.getFileMigrator. 32 * 33 * @type {string} 34 */ 35 static get key() { 36 throw new Error("FileMigrator.key must be overridden."); 37 } 38 39 /** 40 * This must be overridden to return a Fluent string ID mapping to the display 41 * name for this migrator. These strings should be defined in migrationWizard.ftl. 42 * 43 * @type {string} 44 */ 45 static get displayNameL10nID() { 46 throw new Error("FileMigrator.displayNameL10nID must be overridden."); 47 } 48 49 /** 50 * This getter should get overridden to return an icon url to represent the 51 * file to be imported from. By default, this will just use the default Favicon 52 * image. 53 * 54 * @type {string} 55 */ 56 static get brandImage() { 57 return "chrome://global/skin/icons/defaultFavicon.svg"; 58 } 59 60 /** 61 * Returns true if the migrator is configured to be enabled. 62 * 63 * @type {boolean} 64 * true if the migrator should be shown in the migration wizard. 65 */ 66 get enabled() { 67 throw new Error("FileMigrator.enabled must be overridden."); 68 } 69 70 /** 71 * This getter should be overridden to return a Fluent string ID for what 72 * the migration wizard header should be while the file migration is 73 * underway. 74 * 75 * @type {string} 76 */ 77 get progressHeaderL10nID() { 78 throw new Error("FileMigrator.progressHeaderL10nID must be overridden."); 79 } 80 81 /** 82 * This getter should be overridden to return a Fluent string ID for what 83 * the migration wizard header should be while the file migration is 84 * done. 85 * 86 * @type {string} 87 */ 88 get successHeaderL10nID() { 89 throw new Error("FileMigrator.progressHeaderL10nID must be overridden."); 90 } 91 92 /** 93 * @typedef {object} FilePickerConfiguration 94 * @property {string} title 95 * The title that should be assigned to the native file picker window. 96 * @property {FilePickerConfigurationFilter[]} filters 97 * One or more extension filters that should be applied to the native 98 * file picker window to make selection easier. 99 */ 100 101 /** 102 * @typedef {object} FilePickerConfigurationFilter 103 * @property {string} title 104 * The title for the filter. Example: "CSV Files" 105 * @property {string} extensionPattern 106 * A matching pattern for the filter. Example: "*.csv" 107 */ 108 109 /** 110 * A subclass of FileMigratorBase will eventually open a native file picker 111 * for the user to select the file from their file system. 112 * 113 * Subclasses need to override this method in order to configure the 114 * native file picker. 115 * 116 * @returns {Promise<FilePickerConfiguration>} 117 */ 118 async getFilePickerConfig() { 119 throw new Error("FileMigrator.getFilePickerConfig must be overridden."); 120 } 121 122 /** 123 * Returns a list of one or more resource types that should appear to be 124 * in progress of migrating while the file migration occurs. Notably, 125 * this does not need to match the resource types that are returned by 126 * `FileMigratorBase.migrate`. 127 * 128 * @type {string[]} 129 * An array of resource types from the 130 * MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES set. 131 */ 132 get displayedResourceTypes() { 133 throw new Error("FileMigrator.displayedResourceTypes must be overridden"); 134 } 135 136 /** 137 * Called to perform the file migration once the user makes a selection 138 * from the native file picker. This will not be called if the user 139 * chooses to cancel the native file picker. 140 * 141 * @param {string} _filePath 142 * The path that the user selected from the native file picker. 143 */ 144 async migrate(_filePath) { 145 throw new Error("FileMigrator.migrate must be overridden."); 146 } 147 } 148 149 /** 150 * A file migrator for importing passwords from CSV or TSV files. CSV 151 * files are more common, so this is what we show as the file type for 152 * the display name, but this FileMigrator accepts both. 153 */ 154 export class PasswordFileMigrator extends FileMigratorBase { 155 static get key() { 156 return "file-password-csv"; 157 } 158 159 static get displayNameL10nID() { 160 return "migration-wizard-migrator-display-name-file-password-csv"; 161 } 162 163 static get brandImage() { 164 return "chrome://branding/content/document.ico"; 165 } 166 167 get enabled() { 168 return true; 169 } 170 171 get displayedResourceTypes() { 172 return [ 173 lazy.MigrationWizardConstants.DISPLAYED_FILE_RESOURCE_TYPES 174 .PASSWORDS_FROM_FILE, 175 ]; 176 } 177 178 get progressHeaderL10nID() { 179 return "migration-passwords-from-file-progress-header"; 180 } 181 182 get successHeaderL10nID() { 183 return "migration-passwords-from-file-success-header"; 184 } 185 186 async getFilePickerConfig() { 187 let [title, csvFilterTitle, tsvFilterTitle] = 188 await lazy.gFluentStrings.formatValues([ 189 { id: "migration-passwords-from-file-picker-title" }, 190 { id: "migration-passwords-from-file-csv-filter-title" }, 191 { id: "migration-passwords-from-file-tsv-filter-title" }, 192 ]); 193 194 return { 195 title, 196 filters: [ 197 { 198 title: csvFilterTitle, 199 extensionPattern: "*.csv", 200 }, 201 { 202 title: tsvFilterTitle, 203 extensionPattern: "*.tsv", 204 }, 205 ], 206 }; 207 } 208 209 async migrate(filePath) { 210 try { 211 let summary = await lazy.LoginCSVImport.importFromCSV(filePath); 212 let newEntries = 0; 213 let updatedEntries = 0; 214 for (let entry of summary) { 215 if (entry.result == "added") { 216 newEntries++; 217 } else if (entry.result == "modified") { 218 updatedEntries++; 219 } 220 } 221 let [newMessage, updatedMessage] = await lazy.gFluentStrings.formatValues( 222 [ 223 { 224 id: "migration-wizard-progress-success-new-passwords", 225 args: { newEntries }, 226 }, 227 { 228 id: "migration-wizard-progress-success-updated-passwords", 229 args: { updatedEntries }, 230 }, 231 ] 232 ); 233 234 Services.prefs.setBoolPref( 235 "browser.migrate.interactions.csvpasswords", 236 true 237 ); 238 239 return { 240 [lazy.MigrationWizardConstants.DISPLAYED_FILE_RESOURCE_TYPES 241 .PASSWORDS_NEW]: newMessage, 242 [lazy.MigrationWizardConstants.DISPLAYED_FILE_RESOURCE_TYPES 243 .PASSWORDS_UPDATED]: updatedMessage, 244 }; 245 } catch (e) { 246 console.error(e); 247 248 let errorMessage = await lazy.gFluentStrings.formatValue( 249 "migration-passwords-from-file-no-valid-data" 250 ); 251 throw new Error(errorMessage); 252 } 253 } 254 } 255 256 /** 257 * A file migrator for importing bookmarks from a HTML or JSON file. 258 * 259 * @class BookmarksFileMigrator 260 * @augments {FileMigratorBase} 261 */ 262 export class BookmarksFileMigrator extends FileMigratorBase { 263 static get key() { 264 return "file-bookmarks"; 265 } 266 267 static get displayNameL10nID() { 268 return "migration-wizard-migrator-display-name-file-bookmarks"; 269 } 270 271 static get brandImage() { 272 return "chrome://branding/content/document.ico"; 273 } 274 275 get enabled() { 276 return Services.prefs.getBoolPref( 277 "browser.migrate.bookmarks-file.enabled", 278 false 279 ); 280 } 281 282 get displayedResourceTypes() { 283 return [ 284 lazy.MigrationWizardConstants.DISPLAYED_FILE_RESOURCE_TYPES 285 .BOOKMARKS_FROM_FILE, 286 ]; 287 } 288 289 get progressHeaderL10nID() { 290 return "migration-bookmarks-from-file-progress-header"; 291 } 292 293 get successHeaderL10nID() { 294 return "migration-bookmarks-from-file-success-header"; 295 } 296 297 async getFilePickerConfig() { 298 let [title, htmlFilterTitle, jsonFilterTitle] = 299 await lazy.gFluentStrings.formatValues([ 300 { id: "migration-bookmarks-from-file-picker-title" }, 301 { id: "migration-bookmarks-from-file-html-filter-title" }, 302 { id: "migration-bookmarks-from-file-json-filter-title" }, 303 ]); 304 305 return { 306 title, 307 filters: [ 308 { 309 title: htmlFilterTitle, 310 extensionPattern: "*.html", 311 }, 312 { 313 title: jsonFilterTitle, 314 extensionPattern: "*.json", 315 }, 316 ], 317 }; 318 } 319 320 async migrate(filePath) { 321 try { 322 let pathCheck = filePath.toLowerCase(); 323 let importedCount; 324 325 if (pathCheck.endsWith("html")) { 326 importedCount = await lazy.BookmarkHTMLUtils.importFromFile(filePath); 327 } else if (pathCheck.endsWith("json") || pathCheck.endsWith("jsonlz4")) { 328 importedCount = await lazy.BookmarkJSONUtils.importFromFile(filePath); 329 } 330 331 if (!importedCount) { 332 // The catch will cause us to show a default error message. 333 throw new Error(); 334 } 335 336 let importedMessage = await lazy.gFluentStrings.formatValue( 337 "migration-wizard-progress-success-new-bookmarks", 338 { 339 newEntries: importedCount, 340 } 341 ); 342 return { 343 [lazy.MigrationWizardConstants.DISPLAYED_FILE_RESOURCE_TYPES 344 .BOOKMARKS_FROM_FILE]: importedMessage, 345 }; 346 } catch (e) { 347 console.error(e); 348 349 let errorMessage = await lazy.gFluentStrings.formatValue( 350 "migration-bookmarks-from-file-no-valid-data" 351 ); 352 throw new Error(errorMessage); 353 } 354 } 355 }