forked from mirrors/gecko-dev
		
	 daa9cee106
			
		
	
	
		daa9cee106
		
	
	
	
	
		
			
			Depends on D192536 Differential Revision: https://phabricator.services.mozilla.com/D192537
		
			
				
	
	
		
			1254 lines
		
	
	
	
		
			37 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1254 lines
		
	
	
	
		
			37 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 | |
|  * vim: sw=2 ts=2 sts=2 et */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| const AUTH_TYPE = {
 | |
|   SCHEME_HTML: 0,
 | |
|   SCHEME_BASIC: 1,
 | |
|   SCHEME_DIGEST: 2,
 | |
| };
 | |
| 
 | |
| import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
 | |
| import { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs";
 | |
| import { MigratorBase } from "resource:///modules/MigratorBase.sys.mjs";
 | |
| 
 | |
| const lazy = {};
 | |
| 
 | |
| ChromeUtils.defineESModuleGetters(lazy, {
 | |
|   ChromeMigrationUtils: "resource:///modules/ChromeMigrationUtils.sys.mjs",
 | |
|   FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
 | |
|   NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
 | |
|   PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
 | |
|   Qihoo360seMigrationUtils: "resource:///modules/360seMigrationUtils.sys.mjs",
 | |
|   MigrationWizardConstants:
 | |
|     "chrome://browser/content/migration/migration-wizard-constants.mjs",
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * Converts an array of chrome bookmark objects into one our own places code
 | |
|  * understands.
 | |
|  *
 | |
|  * @param {object[]} items Chrome Bookmark items to be inserted on this parent
 | |
|  * @param {set} bookmarkURLAccumulator Accumulate all imported bookmark urls to be used for importing favicons
 | |
|  * @param {Function} errorAccumulator function that gets called with any errors
 | |
|  *   thrown so we don't drop them on the floor.
 | |
|  * @returns {object[]}
 | |
|  */
 | |
| function convertBookmarks(items, bookmarkURLAccumulator, errorAccumulator) {
 | |
|   let itemsToInsert = [];
 | |
|   for (let item of items) {
 | |
|     try {
 | |
|       if (item.type == "url") {
 | |
|         if (item.url.trim().startsWith("chrome:")) {
 | |
|           // Skip invalid internal URIs. Creating an actual URI always reports
 | |
|           // messages to the console because Gecko has its own concept of how
 | |
|           // chrome:// URIs should be formed, so we avoid doing that.
 | |
|           continue;
 | |
|         }
 | |
|         if (item.url.trim().startsWith("edge:")) {
 | |
|           // Don't import internal Microsoft Edge URIs as they won't resolve within Firefox.
 | |
|           continue;
 | |
|         }
 | |
|         itemsToInsert.push({ url: item.url, title: item.name });
 | |
|         bookmarkURLAccumulator.add({ url: item.url });
 | |
|       } else if (item.type == "folder") {
 | |
|         let folderItem = {
 | |
|           type: lazy.PlacesUtils.bookmarks.TYPE_FOLDER,
 | |
|           title: item.name,
 | |
|         };
 | |
|         folderItem.children = convertBookmarks(
 | |
|           item.children,
 | |
|           bookmarkURLAccumulator,
 | |
|           errorAccumulator
 | |
|         );
 | |
|         itemsToInsert.push(folderItem);
 | |
|       }
 | |
|     } catch (ex) {
 | |
|       console.error(ex);
 | |
|       errorAccumulator(ex);
 | |
|     }
 | |
|   }
 | |
|   return itemsToInsert;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Chrome profile migrator. This can also be used as a parent class for
 | |
|  * migrators for browsers that are variants of Chrome.
 | |
|  */
 | |
| export class ChromeProfileMigrator extends MigratorBase {
 | |
|   /**
 | |
|    * On Ubuntu Linux, when the browser is installed as a Snap package,
 | |
|    * we must request permission to read data from other browsers. We
 | |
|    * make that request by opening up a native file picker in folder
 | |
|    * selection mode and instructing the user to navigate to the folder
 | |
|    * that the other browser's user data resides in.
 | |
|    *
 | |
|    * For Snap packages, this gives the browser read access - but it does
 | |
|    * so through a temporary symlink that does not match the original user
 | |
|    * data path. Effectively, the user data directory is remapped to a
 | |
|    * temporary location on the file system. We record these remaps here,
 | |
|    * keyed on the original data directory.
 | |
|    *
 | |
|    * @type {Map<string, string>}
 | |
|    */
 | |
|   #dataPathRemappings = new Map();
 | |
| 
 | |
|   static get key() {
 | |
|     return "chrome";
 | |
|   }
 | |
| 
 | |
|   static get displayNameL10nID() {
 | |
|     return "migration-wizard-migrator-display-name-chrome";
 | |
|   }
 | |
| 
 | |
|   static get brandImage() {
 | |
|     return "chrome://browser/content/migration/brands/chrome.png";
 | |
|   }
 | |
| 
 | |
|   get _chromeUserDataPathSuffix() {
 | |
|     return "Chrome";
 | |
|   }
 | |
| 
 | |
|   async hasPermissions() {
 | |
|     let dataPath = await this._getChromeUserDataPathIfExists();
 | |
|     if (!dataPath) {
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     let localStatePath = PathUtils.join(dataPath, "Local State");
 | |
|     try {
 | |
|       // Read one byte since on snap we can check existence even without being able
 | |
|       // to read the file.
 | |
|       await IOUtils.read(localStatePath, { maxBytes: 1 });
 | |
|       return true;
 | |
|     } catch (ex) {
 | |
|       console.error("No permissions for local state folder.");
 | |
|     }
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   async getPermissions(win) {
 | |
|     // Get the original path to the user data and ignore any existing remapping.
 | |
|     // This allows us to set a new remapping if the user navigates the platforms
 | |
|     // filepicker to a different directory on a second permission request attempt.
 | |
|     let originalDataPath = await this._getChromeUserDataPathIfExists(
 | |
|       true /* noRemapping */
 | |
|     );
 | |
|     // Keep prompting the user until they pick something that grants us access
 | |
|     // to Chrome's local state directory.
 | |
|     while (!(await this.hasPermissions())) {
 | |
|       let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
 | |
|       fp.init(win?.browsingContext, "", Ci.nsIFilePicker.modeGetFolder);
 | |
|       fp.filterIndex = 1;
 | |
|       // Now wait for the filepicker to open and close. If the user picks
 | |
|       // the local state folder, the OS should grant us read access to everything
 | |
|       // inside, so we don't need to check or do anything else with what's
 | |
|       // returned by the filepicker.
 | |
|       let result = await new Promise(resolve => fp.open(resolve));
 | |
|       // Bail if the user cancels the dialog:
 | |
|       if (result == Ci.nsIFilePicker.returnCancel) {
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       let file = fp.file;
 | |
|       if (file && file.path != originalDataPath) {
 | |
|         this.#dataPathRemappings.set(originalDataPath, file.path);
 | |
|       }
 | |
|     }
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   async canGetPermissions() {
 | |
|     if (
 | |
|       !Services.prefs.getBoolPref(
 | |
|         "browser.migrate.chrome.get_permissions.enabled"
 | |
|       )
 | |
|     ) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     if (await MigrationUtils.canGetPermissionsOnPlatform()) {
 | |
|       let dataPath = await this._getChromeUserDataPathIfExists();
 | |
|       if (dataPath) {
 | |
|         let localStatePath = PathUtils.join(dataPath, "Local State");
 | |
|         if (await IOUtils.exists(localStatePath)) {
 | |
|           return dataPath;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   _keychainServiceName = "Chrome Safe Storage";
 | |
| 
 | |
|   _keychainAccountName = "Chrome";
 | |
| 
 | |
|   /**
 | |
|    * Returns a Promise that resolves to the data path containing the
 | |
|    * Local State and profile directories for this browser.
 | |
|    *
 | |
|    * @param {boolean} [noRemapping=false]
 | |
|    *   Set to true to bypass any remapping that might have occurred on
 | |
|    *   platforms where the data path changes once permission has been
 | |
|    *   granted.
 | |
|    * @returns {Promise<string>}
 | |
|    */
 | |
|   async _getChromeUserDataPathIfExists(noRemapping = false) {
 | |
|     if (this._chromeUserDataPath) {
 | |
|       // Skip looking up any remapping if `noRemapping` was passed. This is
 | |
|       // helpful if the caller needs create a new remapping and overwrite
 | |
|       // an old remapping, as "real" user data path is used as a key for
 | |
|       // the remapping.
 | |
|       if (noRemapping) {
 | |
|         return this._chromeUserDataPath;
 | |
|       }
 | |
| 
 | |
|       let remappedPath = this.#dataPathRemappings.get(this._chromeUserDataPath);
 | |
|       return remappedPath || this._chromeUserDataPath;
 | |
|     }
 | |
|     let path = await lazy.ChromeMigrationUtils.getDataPath(
 | |
|       this._chromeUserDataPathSuffix
 | |
|     );
 | |
|     let exists = path && (await IOUtils.exists(path));
 | |
|     if (exists) {
 | |
|       this._chromeUserDataPath = path;
 | |
|     } else {
 | |
|       this._chromeUserDataPath = null;
 | |
|     }
 | |
|     return this._chromeUserDataPath;
 | |
|   }
 | |
| 
 | |
|   async getResources(aProfile) {
 | |
|     if (!(await this.hasPermissions())) {
 | |
|       return [];
 | |
|     }
 | |
| 
 | |
|     let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
 | |
|     if (chromeUserDataPath) {
 | |
|       let profileFolder = chromeUserDataPath;
 | |
|       if (aProfile) {
 | |
|         profileFolder = PathUtils.join(chromeUserDataPath, aProfile.id);
 | |
|       }
 | |
|       if (await IOUtils.exists(profileFolder)) {
 | |
|         let possibleResourcePromises = [
 | |
|           GetBookmarksResource(profileFolder, this.constructor.key),
 | |
|           GetHistoryResource(profileFolder),
 | |
|           GetFormdataResource(profileFolder),
 | |
|           GetExtensionsResource(aProfile.id, this.constructor.key),
 | |
|         ];
 | |
|         if (lazy.ChromeMigrationUtils.supportsLoginsForPlatform) {
 | |
|           possibleResourcePromises.push(
 | |
|             this._GetPasswordsResource(profileFolder),
 | |
|             this._GetPaymentMethodsResource(profileFolder)
 | |
|           );
 | |
|         }
 | |
| 
 | |
|         // Some of these Promises might reject due to things like database
 | |
|         // corruptions. We absorb those rejections here and filter them
 | |
|         // out so that we only try to import the resources that don't appear
 | |
|         // corrupted.
 | |
|         let possibleResources = await Promise.allSettled(
 | |
|           possibleResourcePromises
 | |
|         );
 | |
|         return possibleResources
 | |
|           .filter(promise => {
 | |
|             return promise.status == "fulfilled" && promise.value !== null;
 | |
|           })
 | |
|           .map(promise => promise.value);
 | |
|       }
 | |
|     }
 | |
|     return [];
 | |
|   }
 | |
| 
 | |
|   async getLastUsedDate() {
 | |
|     let sourceProfiles = await this.getSourceProfiles();
 | |
|     if (!sourceProfiles) {
 | |
|       return new Date(0);
 | |
|     }
 | |
|     let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
 | |
|     if (!chromeUserDataPath) {
 | |
|       return new Date(0);
 | |
|     }
 | |
|     let datePromises = sourceProfiles.map(async profile => {
 | |
|       let basePath = PathUtils.join(chromeUserDataPath, profile.id);
 | |
|       let fileDatePromises = ["Bookmarks", "History", "Cookies"].map(
 | |
|         async leafName => {
 | |
|           let path = PathUtils.join(basePath, leafName);
 | |
|           let info = await IOUtils.stat(path).catch(() => null);
 | |
|           return info ? info.lastModified : 0;
 | |
|         }
 | |
|       );
 | |
|       let dates = await Promise.all(fileDatePromises);
 | |
|       return Math.max(...dates);
 | |
|     });
 | |
|     let datesOuter = await Promise.all(datePromises);
 | |
|     datesOuter.push(0);
 | |
|     return new Date(Math.max(...datesOuter));
 | |
|   }
 | |
| 
 | |
|   async getSourceProfiles() {
 | |
|     if ("__sourceProfiles" in this) {
 | |
|       return this.__sourceProfiles;
 | |
|     }
 | |
| 
 | |
|     let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
 | |
|     if (!chromeUserDataPath) {
 | |
|       return [];
 | |
|     }
 | |
| 
 | |
|     let localState;
 | |
|     let profiles = [];
 | |
|     try {
 | |
|       localState = await lazy.ChromeMigrationUtils.getLocalState(
 | |
|         this._chromeUserDataPathSuffix,
 | |
|         chromeUserDataPath
 | |
|       );
 | |
|       let info_cache = localState.profile.info_cache;
 | |
|       for (let profileFolderName in info_cache) {
 | |
|         profiles.push({
 | |
|           id: profileFolderName,
 | |
|           name: info_cache[profileFolderName].name || profileFolderName,
 | |
|         });
 | |
|       }
 | |
|     } catch (e) {
 | |
|       // Avoid reporting NotFoundErrors from trying to get local state.
 | |
|       if (localState || e.name != "NotFoundError") {
 | |
|         console.error("Error detecting Chrome profiles: ", e);
 | |
|       }
 | |
| 
 | |
|       // If we didn't have permission to read the local state, return the
 | |
|       // empty array. The user might have the opportunity to request
 | |
|       // permission using `hasPermission` and `getPermission`.
 | |
|       if (e.name == "NotAllowedError") {
 | |
|         return [];
 | |
|       }
 | |
| 
 | |
|       // If we weren't able to detect any profiles above, fallback to the Default profile.
 | |
|       let defaultProfilePath = PathUtils.join(chromeUserDataPath, "Default");
 | |
|       if (await IOUtils.exists(defaultProfilePath)) {
 | |
|         profiles = [
 | |
|           {
 | |
|             id: "Default",
 | |
|             name: "Default",
 | |
|           },
 | |
|         ];
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     let profileResources = await Promise.all(
 | |
|       profiles.map(async profile => ({
 | |
|         profile,
 | |
|         resources: await this.getResources(profile),
 | |
|       }))
 | |
|     );
 | |
| 
 | |
|     // Only list profiles from which any data can be imported
 | |
|     this.__sourceProfiles = profileResources
 | |
|       .filter(({ resources }) => {
 | |
|         return resources && !!resources.length;
 | |
|       }, this)
 | |
|       .map(({ profile }) => profile);
 | |
|     return this.__sourceProfiles;
 | |
|   }
 | |
| 
 | |
|   async _GetPasswordsResource(aProfileFolder) {
 | |
|     let loginPath = PathUtils.join(aProfileFolder, "Login Data");
 | |
|     if (!(await IOUtils.exists(loginPath))) {
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     let tempFilePath = null;
 | |
|     if (MigrationUtils.IS_LINUX_SNAP_PACKAGE) {
 | |
|       tempFilePath = await IOUtils.createUniqueFile(
 | |
|         PathUtils.tempDir,
 | |
|         "Login Data"
 | |
|       );
 | |
|       await IOUtils.copy(loginPath, tempFilePath);
 | |
|       loginPath = tempFilePath;
 | |
|     }
 | |
| 
 | |
|     let {
 | |
|       _chromeUserDataPathSuffix,
 | |
|       _keychainServiceName,
 | |
|       _keychainAccountName,
 | |
|       _keychainMockPassphrase = null,
 | |
|     } = this;
 | |
| 
 | |
|     let countQuery = `SELECT COUNT(*) FROM logins WHERE blacklisted_by_user = 0`;
 | |
| 
 | |
|     let countRows = await MigrationUtils.getRowsFromDBWithoutLocks(
 | |
|       loginPath,
 | |
|       "Chrome passwords",
 | |
|       countQuery
 | |
|     );
 | |
| 
 | |
|     if (!countRows[0].getResultByName("COUNT(*)")) {
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     return {
 | |
|       type: MigrationUtils.resourceTypes.PASSWORDS,
 | |
| 
 | |
|       async migrate(aCallback) {
 | |
|         let rows = await MigrationUtils.getRowsFromDBWithoutLocks(
 | |
|           loginPath,
 | |
|           "Chrome passwords",
 | |
|           `SELECT origin_url, action_url, username_element, username_value,
 | |
|           password_element, password_value, signon_realm, scheme, date_created,
 | |
|           times_used FROM logins WHERE blacklisted_by_user = 0`
 | |
|         )
 | |
|           .catch(ex => {
 | |
|             console.error(ex);
 | |
|             aCallback(false);
 | |
|           })
 | |
|           .finally(() => {
 | |
|             return tempFilePath && IOUtils.remove(tempFilePath);
 | |
|           });
 | |
| 
 | |
|         // If the promise was rejected we will have already called aCallback,
 | |
|         // so we can just return here.
 | |
|         if (!rows) {
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         // If there are no relevant rows, return before initializing crypto and
 | |
|         // thus prompting for Keychain access on macOS.
 | |
|         if (!rows.length) {
 | |
|           aCallback(true);
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         let crypto;
 | |
|         try {
 | |
|           if (AppConstants.platform == "win") {
 | |
|             let { ChromeWindowsLoginCrypto } = ChromeUtils.importESModule(
 | |
|               "resource:///modules/ChromeWindowsLoginCrypto.sys.mjs"
 | |
|             );
 | |
|             crypto = new ChromeWindowsLoginCrypto(_chromeUserDataPathSuffix);
 | |
|           } else if (AppConstants.platform == "macosx") {
 | |
|             let { ChromeMacOSLoginCrypto } = ChromeUtils.importESModule(
 | |
|               "resource:///modules/ChromeMacOSLoginCrypto.sys.mjs"
 | |
|             );
 | |
|             crypto = new ChromeMacOSLoginCrypto(
 | |
|               _keychainServiceName,
 | |
|               _keychainAccountName,
 | |
|               _keychainMockPassphrase
 | |
|             );
 | |
|           } else {
 | |
|             aCallback(false);
 | |
|             return;
 | |
|           }
 | |
|         } catch (ex) {
 | |
|           // Handle the user canceling Keychain access or other OSCrypto errors.
 | |
|           console.error(ex);
 | |
|           aCallback(false);
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         let logins = [];
 | |
|         let fallbackCreationDate = new Date();
 | |
|         for (let row of rows) {
 | |
|           try {
 | |
|             let origin_url = lazy.NetUtil.newURI(
 | |
|               row.getResultByName("origin_url")
 | |
|             );
 | |
|             // Ignore entries for non-http(s)/ftp URLs because we likely can't
 | |
|             // use them anyway.
 | |
|             const kValidSchemes = new Set(["https", "http", "ftp"]);
 | |
|             if (!kValidSchemes.has(origin_url.scheme)) {
 | |
|               continue;
 | |
|             }
 | |
|             let loginInfo = {
 | |
|               username: row.getResultByName("username_value"),
 | |
|               password: await crypto.decryptData(
 | |
|                 row.getResultByName("password_value"),
 | |
|                 null
 | |
|               ),
 | |
|               origin: origin_url.prePath,
 | |
|               formActionOrigin: null,
 | |
|               httpRealm: null,
 | |
|               usernameElement: row.getResultByName("username_element"),
 | |
|               passwordElement: row.getResultByName("password_element"),
 | |
|               timeCreated: lazy.ChromeMigrationUtils.chromeTimeToDate(
 | |
|                 row.getResultByName("date_created") + 0,
 | |
|                 fallbackCreationDate
 | |
|               ).getTime(),
 | |
|               timesUsed: row.getResultByName("times_used") + 0,
 | |
|             };
 | |
| 
 | |
|             switch (row.getResultByName("scheme")) {
 | |
|               case AUTH_TYPE.SCHEME_HTML:
 | |
|                 let action_url = row.getResultByName("action_url");
 | |
|                 if (!action_url) {
 | |
|                   // If there is no action_url, store the wildcard "" value.
 | |
|                   // See the `formActionOrigin` IDL comments.
 | |
|                   loginInfo.formActionOrigin = "";
 | |
|                   break;
 | |
|                 }
 | |
|                 let action_uri = lazy.NetUtil.newURI(action_url);
 | |
|                 if (!kValidSchemes.has(action_uri.scheme)) {
 | |
|                   continue; // This continues the outer for loop.
 | |
|                 }
 | |
|                 loginInfo.formActionOrigin = action_uri.prePath;
 | |
|                 break;
 | |
|               case AUTH_TYPE.SCHEME_BASIC:
 | |
|               case AUTH_TYPE.SCHEME_DIGEST:
 | |
|                 // signon_realm format is URIrealm, so we need remove URI
 | |
|                 loginInfo.httpRealm = row
 | |
|                   .getResultByName("signon_realm")
 | |
|                   .substring(loginInfo.origin.length + 1);
 | |
|                 break;
 | |
|               default:
 | |
|                 throw new Error(
 | |
|                   "Login data scheme type not supported: " +
 | |
|                     row.getResultByName("scheme")
 | |
|                 );
 | |
|             }
 | |
|             logins.push(loginInfo);
 | |
|           } catch (e) {
 | |
|             console.error(e);
 | |
|           }
 | |
|         }
 | |
|         try {
 | |
|           if (logins.length) {
 | |
|             await MigrationUtils.insertLoginsWrapper(logins);
 | |
|           }
 | |
|         } catch (e) {
 | |
|           console.error(e);
 | |
|         }
 | |
|         if (crypto.finalize) {
 | |
|           crypto.finalize();
 | |
|         }
 | |
|         aCallback(true);
 | |
|       },
 | |
|     };
 | |
|   }
 | |
|   async _GetPaymentMethodsResource(aProfileFolder) {
 | |
|     if (
 | |
|       !Services.prefs.getBoolPref(
 | |
|         "browser.migrate.chrome.payment_methods.enabled",
 | |
|         false
 | |
|       )
 | |
|     ) {
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     let paymentMethodsPath = PathUtils.join(aProfileFolder, "Web Data");
 | |
| 
 | |
|     if (!(await IOUtils.exists(paymentMethodsPath))) {
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     let tempFilePath = null;
 | |
|     if (MigrationUtils.IS_LINUX_SNAP_PACKAGE) {
 | |
|       tempFilePath = await IOUtils.createUniqueFile(
 | |
|         PathUtils.tempDir,
 | |
|         "Web Data"
 | |
|       );
 | |
|       await IOUtils.copy(paymentMethodsPath, tempFilePath);
 | |
|       paymentMethodsPath = tempFilePath;
 | |
|     }
 | |
| 
 | |
|     let rows = await MigrationUtils.getRowsFromDBWithoutLocks(
 | |
|       paymentMethodsPath,
 | |
|       "Chrome Credit Cards",
 | |
|       "SELECT name_on_card, card_number_encrypted, expiration_month, expiration_year FROM credit_cards"
 | |
|     )
 | |
|       .catch(ex => {
 | |
|         console.error(ex);
 | |
|       })
 | |
|       .finally(() => {
 | |
|         return tempFilePath && IOUtils.remove(tempFilePath);
 | |
|       });
 | |
| 
 | |
|     if (!rows?.length) {
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     let {
 | |
|       _chromeUserDataPathSuffix,
 | |
|       _keychainServiceName,
 | |
|       _keychainAccountName,
 | |
|       _keychainMockPassphrase = null,
 | |
|     } = this;
 | |
| 
 | |
|     return {
 | |
|       type: MigrationUtils.resourceTypes.PAYMENT_METHODS,
 | |
| 
 | |
|       async migrate(aCallback) {
 | |
|         let crypto;
 | |
|         try {
 | |
|           if (AppConstants.platform == "win") {
 | |
|             let { ChromeWindowsLoginCrypto } = ChromeUtils.importESModule(
 | |
|               "resource:///modules/ChromeWindowsLoginCrypto.sys.mjs"
 | |
|             );
 | |
|             crypto = new ChromeWindowsLoginCrypto(_chromeUserDataPathSuffix);
 | |
|           } else if (AppConstants.platform == "macosx") {
 | |
|             let { ChromeMacOSLoginCrypto } = ChromeUtils.importESModule(
 | |
|               "resource:///modules/ChromeMacOSLoginCrypto.sys.mjs"
 | |
|             );
 | |
|             crypto = new ChromeMacOSLoginCrypto(
 | |
|               _keychainServiceName,
 | |
|               _keychainAccountName,
 | |
|               _keychainMockPassphrase
 | |
|             );
 | |
|           } else {
 | |
|             aCallback(false);
 | |
|             return;
 | |
|           }
 | |
|         } catch (ex) {
 | |
|           // Handle the user canceling Keychain access or other OSCrypto errors.
 | |
|           console.error(ex);
 | |
|           aCallback(false);
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         let cards = [];
 | |
|         for (let row of rows) {
 | |
|           cards.push({
 | |
|             "cc-name": row.getResultByName("name_on_card"),
 | |
|             "cc-number": await crypto.decryptData(
 | |
|               row.getResultByName("card_number_encrypted"),
 | |
|               null
 | |
|             ),
 | |
|             "cc-exp-month": parseInt(
 | |
|               row.getResultByName("expiration_month"),
 | |
|               10
 | |
|             ),
 | |
|             "cc-exp-year": parseInt(row.getResultByName("expiration_year"), 10),
 | |
|           });
 | |
|         }
 | |
| 
 | |
|         await MigrationUtils.insertCreditCardsWrapper(cards);
 | |
|         aCallback(true);
 | |
|       },
 | |
|     };
 | |
|   }
 | |
| }
 | |
| 
 | |
| async function GetBookmarksResource(aProfileFolder, aBrowserKey) {
 | |
|   let bookmarksPath = PathUtils.join(aProfileFolder, "Bookmarks");
 | |
|   let faviconsPath = PathUtils.join(aProfileFolder, "Favicons");
 | |
| 
 | |
|   if (aBrowserKey === "chromium-360se") {
 | |
|     let localState = {};
 | |
|     try {
 | |
|       localState = await lazy.ChromeMigrationUtils.getLocalState("360 SE");
 | |
|     } catch (ex) {
 | |
|       console.error(ex);
 | |
|     }
 | |
| 
 | |
|     let alternativeBookmarks =
 | |
|       await lazy.Qihoo360seMigrationUtils.getAlternativeBookmarks({
 | |
|         bookmarksPath,
 | |
|         localState,
 | |
|       });
 | |
|     if (alternativeBookmarks.resource) {
 | |
|       return alternativeBookmarks.resource;
 | |
|     }
 | |
| 
 | |
|     bookmarksPath = alternativeBookmarks.path;
 | |
|   }
 | |
| 
 | |
|   if (!(await IOUtils.exists(bookmarksPath))) {
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   let tempFilePath = null;
 | |
|   if (MigrationUtils.IS_LINUX_SNAP_PACKAGE) {
 | |
|     tempFilePath = await IOUtils.createUniqueFile(
 | |
|       PathUtils.tempDir,
 | |
|       "Favicons"
 | |
|     );
 | |
|     await IOUtils.copy(faviconsPath, tempFilePath);
 | |
|     faviconsPath = tempFilePath;
 | |
|   }
 | |
| 
 | |
|   // check to read JSON bookmarks structure and see if any bookmarks exist else return null
 | |
|   // Parse Chrome bookmark file that is JSON format
 | |
|   let bookmarkJSON = await IOUtils.readJSON(bookmarksPath);
 | |
|   let other = bookmarkJSON.roots.other.children.length;
 | |
|   let bookmarkBar = bookmarkJSON.roots.bookmark_bar.children.length;
 | |
|   let synced = bookmarkJSON.roots.synced.children.length;
 | |
| 
 | |
|   if (!other && !bookmarkBar && !synced) {
 | |
|     return null;
 | |
|   }
 | |
|   return {
 | |
|     type: MigrationUtils.resourceTypes.BOOKMARKS,
 | |
| 
 | |
|     migrate(aCallback) {
 | |
|       return (async function () {
 | |
|         let gotErrors = false;
 | |
|         let errorGatherer = function () {
 | |
|           gotErrors = true;
 | |
|         };
 | |
| 
 | |
|         let faviconRows = [];
 | |
|         try {
 | |
|           faviconRows = await MigrationUtils.getRowsFromDBWithoutLocks(
 | |
|             faviconsPath,
 | |
|             "Chrome Bookmark Favicons",
 | |
|             `select fav.id, fav.url, map.page_url, bit.image_data FROM favicons as fav
 | |
|               INNER JOIN favicon_bitmaps bit ON (fav.id = bit.icon_id)
 | |
|               INNER JOIN icon_mapping map ON (map.icon_id = bit.icon_id)`
 | |
|           );
 | |
|         } catch (ex) {
 | |
|           console.error(ex);
 | |
|         } finally {
 | |
|           if (tempFilePath) {
 | |
|             await IOUtils.remove(tempFilePath);
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         // Create Hashmap for favicons
 | |
|         let faviconMap = new Map();
 | |
|         for (let faviconRow of faviconRows) {
 | |
|           // First, try to normalize the URI:
 | |
|           try {
 | |
|             let uri = lazy.NetUtil.newURI(
 | |
|               faviconRow.getResultByName("page_url")
 | |
|             );
 | |
|             faviconMap.set(uri.spec, {
 | |
|               faviconData: faviconRow.getResultByName("image_data"),
 | |
|               uri,
 | |
|             });
 | |
|           } catch (e) {
 | |
|             // Couldn't parse the URI, so just skip it.
 | |
|             continue;
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         let roots = bookmarkJSON.roots;
 | |
|         let bookmarkURLAccumulator = new Set();
 | |
| 
 | |
|         // Importing bookmark bar items
 | |
|         if (roots.bookmark_bar.children && roots.bookmark_bar.children.length) {
 | |
|           // Toolbar
 | |
|           let parentGuid = lazy.PlacesUtils.bookmarks.toolbarGuid;
 | |
|           let bookmarks = convertBookmarks(
 | |
|             roots.bookmark_bar.children,
 | |
|             bookmarkURLAccumulator,
 | |
|             errorGatherer
 | |
|           );
 | |
|           await MigrationUtils.insertManyBookmarksWrapper(
 | |
|             bookmarks,
 | |
|             parentGuid
 | |
|           );
 | |
|         }
 | |
| 
 | |
|         // Importing Other Bookmarks items
 | |
|         if (roots.other.children && roots.other.children.length) {
 | |
|           // Other Bookmarks
 | |
|           let parentGuid = lazy.PlacesUtils.bookmarks.unfiledGuid;
 | |
|           let bookmarks = convertBookmarks(
 | |
|             roots.other.children,
 | |
|             bookmarkURLAccumulator,
 | |
|             errorGatherer
 | |
|           );
 | |
|           await MigrationUtils.insertManyBookmarksWrapper(
 | |
|             bookmarks,
 | |
|             parentGuid
 | |
|           );
 | |
|         }
 | |
| 
 | |
|         // Importing synced Bookmarks items
 | |
|         if (roots.synced.children && roots.synced.children.length) {
 | |
|           // Synced  Bookmarks
 | |
|           let parentGuid = lazy.PlacesUtils.bookmarks.unfiledGuid;
 | |
|           let bookmarks = convertBookmarks(
 | |
|             roots.synced.children,
 | |
|             bookmarkURLAccumulator,
 | |
|             errorGatherer
 | |
|           );
 | |
|           await MigrationUtils.insertManyBookmarksWrapper(
 | |
|             bookmarks,
 | |
|             parentGuid
 | |
|           );
 | |
|         }
 | |
| 
 | |
|         // Find all favicons with associated bookmarks
 | |
|         let favicons = [];
 | |
|         for (let bookmark of bookmarkURLAccumulator) {
 | |
|           try {
 | |
|             let uri = lazy.NetUtil.newURI(bookmark.url);
 | |
|             let favicon = faviconMap.get(uri.spec);
 | |
|             if (favicon) {
 | |
|               favicons.push(favicon);
 | |
|             }
 | |
|           } catch (e) {
 | |
|             // Couldn't parse the bookmark URI, so just skip
 | |
|             continue;
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         // Import Bookmark Favicons
 | |
|         MigrationUtils.insertManyFavicons(favicons).catch(console.error);
 | |
| 
 | |
|         if (gotErrors) {
 | |
|           throw new Error("The migration included errors.");
 | |
|         }
 | |
|       })().then(
 | |
|         () => aCallback(true),
 | |
|         () => aCallback(false)
 | |
|       );
 | |
|     },
 | |
|   };
 | |
| }
 | |
| 
 | |
| async function GetHistoryResource(aProfileFolder) {
 | |
|   let historyPath = PathUtils.join(aProfileFolder, "History");
 | |
|   if (!(await IOUtils.exists(historyPath))) {
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   let tempFilePath = null;
 | |
|   if (MigrationUtils.IS_LINUX_SNAP_PACKAGE) {
 | |
|     tempFilePath = await IOUtils.createUniqueFile(PathUtils.tempDir, "History");
 | |
|     await IOUtils.copy(historyPath, tempFilePath);
 | |
|     historyPath = tempFilePath;
 | |
|   }
 | |
| 
 | |
|   let countQuery = "SELECT COUNT(*) FROM urls WHERE hidden = 0";
 | |
| 
 | |
|   let countRows = await MigrationUtils.getRowsFromDBWithoutLocks(
 | |
|     historyPath,
 | |
|     "Chrome history",
 | |
|     countQuery
 | |
|   );
 | |
|   if (!countRows[0].getResultByName("COUNT(*)")) {
 | |
|     return null;
 | |
|   }
 | |
|   return {
 | |
|     type: MigrationUtils.resourceTypes.HISTORY,
 | |
| 
 | |
|     migrate(aCallback) {
 | |
|       (async function () {
 | |
|         const LIMIT = Services.prefs.getIntPref(
 | |
|           "browser.migrate.chrome.history.limit"
 | |
|         );
 | |
| 
 | |
|         let query =
 | |
|           "SELECT url, title, last_visit_time, typed_count FROM urls WHERE hidden = 0";
 | |
|         let maxAge = lazy.ChromeMigrationUtils.dateToChromeTime(
 | |
|           Date.now() - MigrationUtils.HISTORY_MAX_AGE_IN_MILLISECONDS
 | |
|         );
 | |
|         query += " AND last_visit_time > " + maxAge;
 | |
| 
 | |
|         if (LIMIT) {
 | |
|           query += " ORDER BY last_visit_time DESC LIMIT " + LIMIT;
 | |
|         }
 | |
| 
 | |
|         let rows;
 | |
|         try {
 | |
|           rows = await MigrationUtils.getRowsFromDBWithoutLocks(
 | |
|             historyPath,
 | |
|             "Chrome history",
 | |
|             query
 | |
|           );
 | |
|         } finally {
 | |
|           if (tempFilePath) {
 | |
|             await IOUtils.remove(tempFilePath);
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         let pageInfos = [];
 | |
|         let fallbackVisitDate = new Date();
 | |
|         for (let row of rows) {
 | |
|           try {
 | |
|             // if having typed_count, we changes transition type to typed.
 | |
|             let transition = lazy.PlacesUtils.history.TRANSITIONS.LINK;
 | |
|             if (row.getResultByName("typed_count") > 0) {
 | |
|               transition = lazy.PlacesUtils.history.TRANSITIONS.TYPED;
 | |
|             }
 | |
| 
 | |
|             pageInfos.push({
 | |
|               title: row.getResultByName("title"),
 | |
|               url: new URL(row.getResultByName("url")),
 | |
|               visits: [
 | |
|                 {
 | |
|                   transition,
 | |
|                   date: lazy.ChromeMigrationUtils.chromeTimeToDate(
 | |
|                     row.getResultByName("last_visit_time"),
 | |
|                     fallbackVisitDate
 | |
|                   ),
 | |
|                 },
 | |
|               ],
 | |
|             });
 | |
|           } catch (e) {
 | |
|             console.error(e);
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         if (pageInfos.length) {
 | |
|           await MigrationUtils.insertVisitsWrapper(pageInfos);
 | |
|         }
 | |
|       })().then(
 | |
|         () => {
 | |
|           aCallback(true);
 | |
|         },
 | |
|         ex => {
 | |
|           console.error(ex);
 | |
|           aCallback(false);
 | |
|         }
 | |
|       );
 | |
|     },
 | |
|   };
 | |
| }
 | |
| 
 | |
| async function GetFormdataResource(aProfileFolder) {
 | |
|   let formdataPath = PathUtils.join(aProfileFolder, "Web Data");
 | |
|   if (!(await IOUtils.exists(formdataPath))) {
 | |
|     return null;
 | |
|   }
 | |
|   let countQuery = "SELECT COUNT(*) FROM autofill";
 | |
| 
 | |
|   let tempFilePath = null;
 | |
|   if (MigrationUtils.IS_LINUX_SNAP_PACKAGE) {
 | |
|     tempFilePath = await IOUtils.createUniqueFile(
 | |
|       PathUtils.tempDir,
 | |
|       "Web Data"
 | |
|     );
 | |
|     await IOUtils.copy(formdataPath, tempFilePath);
 | |
|     formdataPath = tempFilePath;
 | |
|   }
 | |
| 
 | |
|   let countRows = await MigrationUtils.getRowsFromDBWithoutLocks(
 | |
|     formdataPath,
 | |
|     "Chrome formdata",
 | |
|     countQuery
 | |
|   );
 | |
|   if (!countRows[0].getResultByName("COUNT(*)")) {
 | |
|     return null;
 | |
|   }
 | |
|   return {
 | |
|     type: MigrationUtils.resourceTypes.FORMDATA,
 | |
| 
 | |
|     async migrate(aCallback) {
 | |
|       let query =
 | |
|         "SELECT name, value, count, date_created, date_last_used FROM autofill";
 | |
|       let rows;
 | |
| 
 | |
|       try {
 | |
|         rows = await MigrationUtils.getRowsFromDBWithoutLocks(
 | |
|           formdataPath,
 | |
|           "Chrome formdata",
 | |
|           query
 | |
|         );
 | |
|       } finally {
 | |
|         if (tempFilePath) {
 | |
|           await IOUtils.remove(tempFilePath);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       let addOps = [];
 | |
|       for (let row of rows) {
 | |
|         try {
 | |
|           let fieldname = row.getResultByName("name");
 | |
|           let value = row.getResultByName("value");
 | |
|           if (fieldname && value) {
 | |
|             addOps.push({
 | |
|               op: "add",
 | |
|               fieldname,
 | |
|               value,
 | |
|               timesUsed: row.getResultByName("count"),
 | |
|               firstUsed: row.getResultByName("date_created") * 1000,
 | |
|               lastUsed: row.getResultByName("date_last_used") * 1000,
 | |
|             });
 | |
|           }
 | |
|         } catch (e) {
 | |
|           console.error(e);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       try {
 | |
|         await lazy.FormHistory.update(addOps);
 | |
|       } catch (e) {
 | |
|         console.error(e);
 | |
|         aCallback(false);
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       aCallback(true);
 | |
|     },
 | |
|   };
 | |
| }
 | |
| 
 | |
| async function GetExtensionsResource(aProfileId, aBrowserKey = "chrome") {
 | |
|   if (
 | |
|     !Services.prefs.getBoolPref(
 | |
|       "browser.migrate.chrome.extensions.enabled",
 | |
|       false
 | |
|     )
 | |
|   ) {
 | |
|     return null;
 | |
|   }
 | |
|   let extensions = await lazy.ChromeMigrationUtils.getExtensionList(aProfileId);
 | |
|   if (!extensions.length || aBrowserKey !== "chrome") {
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   return {
 | |
|     type: MigrationUtils.resourceTypes.EXTENSIONS,
 | |
|     async migrate(callback) {
 | |
|       let ids = extensions.map(extension => extension.id);
 | |
|       let [progressValue, importedExtensions] =
 | |
|         await MigrationUtils.installExtensionsWrapper(aBrowserKey, ids);
 | |
|       let details = {
 | |
|         progressValue,
 | |
|         totalExtensions: extensions,
 | |
|         importedExtensions,
 | |
|       };
 | |
|       if (
 | |
|         progressValue == lazy.MigrationWizardConstants.PROGRESS_VALUE.INFO ||
 | |
|         progressValue == lazy.MigrationWizardConstants.PROGRESS_VALUE.SUCCESS
 | |
|       ) {
 | |
|         callback(true, details);
 | |
|       } else {
 | |
|         callback(false);
 | |
|       }
 | |
|     },
 | |
|   };
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Chromium migrator
 | |
|  */
 | |
| export class ChromiumProfileMigrator extends ChromeProfileMigrator {
 | |
|   static get key() {
 | |
|     return "chromium";
 | |
|   }
 | |
| 
 | |
|   static get displayNameL10nID() {
 | |
|     return "migration-wizard-migrator-display-name-chromium";
 | |
|   }
 | |
| 
 | |
|   static get brandImage() {
 | |
|     return "chrome://browser/content/migration/brands/chromium.png";
 | |
|   }
 | |
| 
 | |
|   _chromeUserDataPathSuffix = "Chromium";
 | |
|   _keychainServiceName = "Chromium Safe Storage";
 | |
|   _keychainAccountName = "Chromium";
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Chrome Canary
 | |
|  * Not available on Linux
 | |
|  */
 | |
| export class CanaryProfileMigrator extends ChromeProfileMigrator {
 | |
|   static get key() {
 | |
|     return "canary";
 | |
|   }
 | |
| 
 | |
|   static get displayNameL10nID() {
 | |
|     return "migration-wizard-migrator-display-name-canary";
 | |
|   }
 | |
| 
 | |
|   static get brandImage() {
 | |
|     return "chrome://browser/content/migration/brands/canary.png";
 | |
|   }
 | |
| 
 | |
|   get _chromeUserDataPathSuffix() {
 | |
|     return "Canary";
 | |
|   }
 | |
| 
 | |
|   get _keychainServiceName() {
 | |
|     return "Chromium Safe Storage";
 | |
|   }
 | |
| 
 | |
|   get _keychainAccountName() {
 | |
|     return "Chromium";
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Chrome Dev - Linux only (not available in Mac and Windows)
 | |
|  */
 | |
| export class ChromeDevMigrator extends ChromeProfileMigrator {
 | |
|   static get key() {
 | |
|     return "chrome-dev";
 | |
|   }
 | |
| 
 | |
|   static get displayNameL10nID() {
 | |
|     return "migration-wizard-migrator-display-name-chrome-dev";
 | |
|   }
 | |
| 
 | |
|   _chromeUserDataPathSuffix = "Chrome Dev";
 | |
|   _keychainServiceName = "Chromium Safe Storage";
 | |
|   _keychainAccountName = "Chromium";
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Chrome Beta migrator
 | |
|  */
 | |
| export class ChromeBetaMigrator extends ChromeProfileMigrator {
 | |
|   static get key() {
 | |
|     return "chrome-beta";
 | |
|   }
 | |
| 
 | |
|   static get displayNameL10nID() {
 | |
|     return "migration-wizard-migrator-display-name-chrome-beta";
 | |
|   }
 | |
| 
 | |
|   _chromeUserDataPathSuffix = "Chrome Beta";
 | |
|   _keychainServiceName = "Chromium Safe Storage";
 | |
|   _keychainAccountName = "Chromium";
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Brave migrator
 | |
|  */
 | |
| export class BraveProfileMigrator extends ChromeProfileMigrator {
 | |
|   static get key() {
 | |
|     return "brave";
 | |
|   }
 | |
| 
 | |
|   static get displayNameL10nID() {
 | |
|     return "migration-wizard-migrator-display-name-brave";
 | |
|   }
 | |
| 
 | |
|   static get brandImage() {
 | |
|     return "chrome://browser/content/migration/brands/brave.png";
 | |
|   }
 | |
| 
 | |
|   _chromeUserDataPathSuffix = "Brave";
 | |
|   _keychainServiceName = "Brave Browser Safe Storage";
 | |
|   _keychainAccountName = "Brave Browser";
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Edge (Chromium-based) migrator
 | |
|  */
 | |
| export class ChromiumEdgeMigrator extends ChromeProfileMigrator {
 | |
|   static get key() {
 | |
|     return "chromium-edge";
 | |
|   }
 | |
| 
 | |
|   static get displayNameL10nID() {
 | |
|     return "migration-wizard-migrator-display-name-chromium-edge";
 | |
|   }
 | |
| 
 | |
|   static get brandImage() {
 | |
|     return "chrome://browser/content/migration/brands/edge.png";
 | |
|   }
 | |
| 
 | |
|   _chromeUserDataPathSuffix = "Edge";
 | |
|   _keychainServiceName = "Microsoft Edge Safe Storage";
 | |
|   _keychainAccountName = "Microsoft Edge";
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Edge Beta (Chromium-based) migrator
 | |
|  */
 | |
| export class ChromiumEdgeBetaMigrator extends ChromeProfileMigrator {
 | |
|   static get key() {
 | |
|     return "chromium-edge-beta";
 | |
|   }
 | |
| 
 | |
|   static get displayNameL10nID() {
 | |
|     return "migration-wizard-migrator-display-name-chromium-edge-beta";
 | |
|   }
 | |
| 
 | |
|   static get brandImage() {
 | |
|     return "chrome://browser/content/migration/brands/edgebeta.png";
 | |
|   }
 | |
| 
 | |
|   _chromeUserDataPathSuffix = "Edge Beta";
 | |
|   _keychainServiceName = "Microsoft Edge Safe Storage";
 | |
|   _keychainAccountName = "Microsoft Edge";
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Chromium 360 migrator
 | |
|  */
 | |
| export class Chromium360seMigrator extends ChromeProfileMigrator {
 | |
|   static get key() {
 | |
|     return "chromium-360se";
 | |
|   }
 | |
| 
 | |
|   static get displayNameL10nID() {
 | |
|     return "migration-wizard-migrator-display-name-chromium-360se";
 | |
|   }
 | |
| 
 | |
|   static get brandImage() {
 | |
|     return "chrome://browser/content/migration/brands/360.png";
 | |
|   }
 | |
| 
 | |
|   _chromeUserDataPathSuffix = "360 SE";
 | |
|   _keychainServiceName = "Microsoft Edge Safe Storage";
 | |
|   _keychainAccountName = "Microsoft Edge";
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Opera migrator
 | |
|  */
 | |
| export class OperaProfileMigrator extends ChromeProfileMigrator {
 | |
|   static get key() {
 | |
|     return "opera";
 | |
|   }
 | |
| 
 | |
|   static get displayNameL10nID() {
 | |
|     return "migration-wizard-migrator-display-name-opera";
 | |
|   }
 | |
| 
 | |
|   static get brandImage() {
 | |
|     return "chrome://browser/content/migration/brands/opera.png";
 | |
|   }
 | |
| 
 | |
|   _chromeUserDataPathSuffix = "Opera";
 | |
|   _keychainServiceName = "Opera Safe Storage";
 | |
|   _keychainAccountName = "Opera";
 | |
| 
 | |
|   getSourceProfiles() {
 | |
|     return null;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Opera GX migrator
 | |
|  */
 | |
| export class OperaGXProfileMigrator extends ChromeProfileMigrator {
 | |
|   static get key() {
 | |
|     return "opera-gx";
 | |
|   }
 | |
| 
 | |
|   static get displayNameL10nID() {
 | |
|     return "migration-wizard-migrator-display-name-opera-gx";
 | |
|   }
 | |
| 
 | |
|   static get brandImage() {
 | |
|     return "chrome://browser/content/migration/brands/operagx.png";
 | |
|   }
 | |
| 
 | |
|   _chromeUserDataPathSuffix = "Opera GX";
 | |
|   _keychainServiceName = "Opera Safe Storage";
 | |
|   _keychainAccountName = "Opera";
 | |
| 
 | |
|   getSourceProfiles() {
 | |
|     return null;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Vivaldi migrator
 | |
|  */
 | |
| export class VivaldiProfileMigrator extends ChromeProfileMigrator {
 | |
|   static get key() {
 | |
|     return "vivaldi";
 | |
|   }
 | |
| 
 | |
|   static get displayNameL10nID() {
 | |
|     return "migration-wizard-migrator-display-name-vivaldi";
 | |
|   }
 | |
| 
 | |
|   static get brandImage() {
 | |
|     return "chrome://browser/content/migration/brands/vivaldi.png";
 | |
|   }
 | |
| 
 | |
|   _chromeUserDataPathSuffix = "Vivaldi";
 | |
|   _keychainServiceName = "Vivaldi Safe Storage";
 | |
|   _keychainAccountName = "Vivaldi";
 | |
| }
 |