зеркало из https://github.com/mozilla/gecko-dev.git
289 строки
10 KiB
JavaScript
289 строки
10 KiB
JavaScript
/* 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/. */
|
|
"use strict";
|
|
|
|
this.EXPORTED_SYMBOLS = ["ChromeMigrationUtils"];
|
|
|
|
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
|
|
const FILE_INPUT_STREAM_CID = "@mozilla.org/network/file-input-stream;1";
|
|
|
|
Cu.import("resource://gre/modules/AppConstants.jsm");
|
|
Cu.import("resource://gre/modules/FileUtils.jsm");
|
|
Cu.import("resource://gre/modules/NetUtil.jsm");
|
|
Cu.import("resource://gre/modules/osfile.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
this.ChromeMigrationUtils = {
|
|
_chromeUserDataPath: null,
|
|
|
|
_extensionVersionDirectoryNames: {},
|
|
|
|
// The cache for the locale strings.
|
|
// For example, the data could be:
|
|
// {
|
|
// "profile-id-1": {
|
|
// "extension-id-1": {
|
|
// "name": {
|
|
// "message": "Fake App 1"
|
|
// }
|
|
// },
|
|
// }
|
|
_extensionLocaleStrings: {},
|
|
|
|
/**
|
|
* Get all extensions installed in a specific profile.
|
|
* @param {String} profileId - A Chrome user profile ID. For example, "Profile 1".
|
|
* @returns {Array} All installed Chrome extensions information.
|
|
*/
|
|
async getExtensionList(profileId = this.getLastUsedProfileId()) {
|
|
let path = this.getExtensionPath(profileId);
|
|
let iterator = new OS.File.DirectoryIterator(path);
|
|
let extensionList = [];
|
|
await iterator.forEach(async entry => {
|
|
if (entry.isDir) {
|
|
let extensionInformation = await this.getExtensionInformation(entry.name, profileId);
|
|
if (extensionInformation) {
|
|
extensionList.push(extensionInformation);
|
|
}
|
|
}
|
|
}).catch(ex => Cu.reportError(ex));
|
|
return extensionList;
|
|
},
|
|
|
|
/**
|
|
* Get information of a specific Chrome extension.
|
|
* @param {String} extensionId - The extension ID.
|
|
* @param {String} profileId - The user profile's ID.
|
|
* @retruns {Object} The Chrome extension information.
|
|
*/
|
|
async getExtensionInformation(extensionId, profileId = this.getLastUsedProfileId()) {
|
|
let extensionInformation = null;
|
|
try {
|
|
let manifestPath = this.getExtensionPath(profileId);
|
|
manifestPath = OS.Path.join(manifestPath, extensionId);
|
|
// If there are multiple sub-directories in the extension directory,
|
|
// read the files in the latest directory.
|
|
let directories = await this._getSortedByVersionSubDirectoryNames(manifestPath);
|
|
if (!directories[0]) {
|
|
return null;
|
|
}
|
|
|
|
manifestPath = OS.Path.join(manifestPath, directories[0], "manifest.json");
|
|
let manifest = await OS.File.read(manifestPath, { encoding: "utf-8" });
|
|
manifest = JSON.parse(manifest);
|
|
// No app attribute means this is a Chrome extension not a Chrome app.
|
|
if (!manifest.app) {
|
|
const DEFAULT_LOCALE = manifest.default_locale;
|
|
let name = await this._getLocaleString(manifest.name, DEFAULT_LOCALE, extensionId, profileId);
|
|
let description = await this._getLocaleString(manifest.description, DEFAULT_LOCALE, extensionId, profileId);
|
|
if (name) {
|
|
extensionInformation = {
|
|
id: extensionId,
|
|
name,
|
|
description,
|
|
};
|
|
} else {
|
|
throw new Error("Cannot read the Chrome extension's name property.");
|
|
}
|
|
}
|
|
} catch (ex) {
|
|
Cu.reportError(ex);
|
|
}
|
|
return extensionInformation;
|
|
},
|
|
|
|
/**
|
|
* Get the manifest's locale string.
|
|
* @param {String} key - The key of a locale string, for example __MSG_name__.
|
|
* @param {String} locale - The specific language of locale string.
|
|
* @param {String} extensionId - The extension ID.
|
|
* @param {String} profileId - The user profile's ID.
|
|
* @retruns {String} The locale string.
|
|
*/
|
|
async _getLocaleString(key, locale, extensionId, profileId) {
|
|
// Return the key string if it is not a locale key.
|
|
// The key string starts with "__MSG_" and ends with "__".
|
|
// For example, "__MSG_name__".
|
|
// https://developer.chrome.com/apps/i18n
|
|
if (!key.startsWith("__MSG_") || !key.endsWith("__")) {
|
|
return key;
|
|
}
|
|
|
|
let localeString = null;
|
|
try {
|
|
let localeFile;
|
|
if (this._extensionLocaleStrings[profileId] &&
|
|
this._extensionLocaleStrings[profileId][extensionId]) {
|
|
localeFile = this._extensionLocaleStrings[profileId][extensionId];
|
|
} else {
|
|
if (!this._extensionLocaleStrings[profileId]) {
|
|
this._extensionLocaleStrings[profileId] = {};
|
|
}
|
|
let localeFilePath = this.getExtensionPath(profileId);
|
|
localeFilePath = OS.Path.join(localeFilePath, extensionId);
|
|
let directories = await this._getSortedByVersionSubDirectoryNames(localeFilePath);
|
|
// If there are multiple sub-directories in the extension directory,
|
|
// read the files in the latest directory.
|
|
localeFilePath = OS.Path.join(localeFilePath, directories[0], "_locales", locale, "messages.json");
|
|
localeFile = await OS.File.read(localeFilePath, { encoding: "utf-8" });
|
|
localeFile = JSON.parse(localeFile);
|
|
this._extensionLocaleStrings[profileId][extensionId] = localeFile;
|
|
}
|
|
const PREFIX_LENGTH = 6;
|
|
const SUFFIX_LENGTH = 2;
|
|
// Get the locale key from the string with locale prefix and suffix.
|
|
// For example, it will get the "name" sub-string from the "__MSG_name__" string.
|
|
key = key.substring(PREFIX_LENGTH, key.length - SUFFIX_LENGTH);
|
|
if (localeFile[key] && localeFile[key].message) {
|
|
localeString = localeFile[key].message;
|
|
}
|
|
} catch (ex) {
|
|
Cu.reportError(ex);
|
|
}
|
|
return localeString;
|
|
},
|
|
|
|
/**
|
|
* Check that a specific extension is installed or not.
|
|
* @param {String} extensionId - The extension ID.
|
|
* @param {String} profileId - The user profile's ID.
|
|
* @returns {Boolean} Return true if the extension is installed otherwise return false.
|
|
*/
|
|
async isExtensionInstalled(extensionId, profileId = this.getLastUsedProfileId()) {
|
|
let extensionPath = this.getExtensionPath(profileId);
|
|
let isInstalled = await OS.File.exists(OS.Path.join(extensionPath, extensionId));
|
|
return isInstalled;
|
|
},
|
|
|
|
/**
|
|
* Get the last used user profile's ID.
|
|
* @returns {String} The last used user profile's ID.
|
|
*/
|
|
getLastUsedProfileId() {
|
|
let localState = this.getLocalState();
|
|
return localState ? localState.profile.last_used : "Default";
|
|
},
|
|
|
|
/**
|
|
* Get the local state file content.
|
|
* @returns {Object} The JSON-based content.
|
|
*/
|
|
getLocalState() {
|
|
let localStateFile = new FileUtils.File(this.getChromeUserDataPath());
|
|
localStateFile.append("Local State");
|
|
if (!localStateFile.exists())
|
|
throw new Error("Chrome's 'Local State' file does not exist.");
|
|
if (!localStateFile.isReadable())
|
|
throw new Error("Chrome's 'Local State' file could not be read.");
|
|
|
|
let localState = null;
|
|
try {
|
|
let fstream = Cc[FILE_INPUT_STREAM_CID].createInstance(Ci.nsIFileInputStream);
|
|
fstream.init(localStateFile, -1, 0, 0);
|
|
let inputStream = NetUtil.readInputStreamToString(fstream, fstream.available(),
|
|
{ charset: "UTF-8" });
|
|
localState = JSON.parse(inputStream);
|
|
} catch (ex) {
|
|
Cu.reportError(ex);
|
|
throw ex;
|
|
}
|
|
return localState;
|
|
},
|
|
|
|
/**
|
|
* Get the path of Chrome extension directory.
|
|
* @param {String} profileId - The user profile's ID.
|
|
* @returns {String} The path of Chrome extension directory.
|
|
*/
|
|
getExtensionPath(profileId) {
|
|
return OS.Path.join(this.getChromeUserDataPath(), profileId, "Extensions");
|
|
},
|
|
|
|
/**
|
|
* Get the path of the Chrome user data directory.
|
|
* @returns {String} The path of the Chrome user data directory.
|
|
*/
|
|
getChromeUserDataPath() {
|
|
if (!this._chromeUserDataPath) {
|
|
this._chromeUserDataPath = this.getDataPath("Chrome");
|
|
}
|
|
return this._chromeUserDataPath;
|
|
},
|
|
|
|
/**
|
|
* Get the path of an application data directory.
|
|
* @param {String} chromeProjectName - The Chrome project name, e.g. "Chrome", "Chromium" or "Canary".
|
|
* @returns {String} The path of application data directory.
|
|
*/
|
|
getDataPath(chromeProjectName) {
|
|
const SUB_DIRECTORIES = {
|
|
win: {
|
|
Chrome: ["Google", "Chrome"],
|
|
Chromium: ["Chromium"],
|
|
Canary: ["Google", "Chrome SxS"],
|
|
},
|
|
macosx: {
|
|
Chrome: ["Google", "Chrome"],
|
|
Chromium: ["Chromium"],
|
|
Canary: ["Google", "Chrome Canary"],
|
|
},
|
|
linux: {
|
|
Chrome: ["google-chrome"],
|
|
Chromium: ["chromium"],
|
|
// Canary is not available on Linux.
|
|
},
|
|
};
|
|
let dirKey, subfolders;
|
|
subfolders = SUB_DIRECTORIES[AppConstants.platform][chromeProjectName];
|
|
if (!subfolders) {
|
|
return null;
|
|
}
|
|
|
|
if (AppConstants.platform == "win") {
|
|
dirKey = "winLocalAppDataDir";
|
|
subfolders = subfolders.concat(["User Data"]);
|
|
} else if (AppConstants.platform == "macosx") {
|
|
dirKey = "macUserLibDir";
|
|
subfolders = ["Application Support"].concat(subfolders);
|
|
} else {
|
|
dirKey = "homeDir";
|
|
subfolders = [".config"].concat(subfolders);
|
|
}
|
|
subfolders.unshift(OS.Constants.Path[dirKey]);
|
|
return OS.Path.join(...subfolders);
|
|
},
|
|
|
|
/**
|
|
* Get the directory objects sorted by version number.
|
|
* @param {String} path - The path to the extension directory.
|
|
* otherwise return all file/directory object.
|
|
* @returns {Array} The file/directory object array.
|
|
*/
|
|
async _getSortedByVersionSubDirectoryNames(path) {
|
|
if (this._extensionVersionDirectoryNames[path]) {
|
|
return this._extensionVersionDirectoryNames[path];
|
|
}
|
|
|
|
let iterator = new OS.File.DirectoryIterator(path);
|
|
let entries = [];
|
|
await iterator.forEach(async entry => {
|
|
if (entry.isDir) {
|
|
entries.push(entry.name);
|
|
}
|
|
}).catch(ex => {
|
|
Cu.reportError(ex);
|
|
entries = [];
|
|
});
|
|
// The directory name is the version number string of the extension.
|
|
// For example, it could be "1.0_0", "1.0_1", "1.0_2", 1.1_0, 1.1_1, or 1.1_2.
|
|
// The "1.0_1" strings mean that the "1.0_0" directory is existed and you install the version 1.0 again.
|
|
// https://chromium.googlesource.com/chromium/src/+/0b58a813992b539a6b555ad7959adfad744b095a/chrome/common/extensions/extension_file_util_unittest.cc
|
|
entries.sort((a, b) => Services.vc.compare(b, a));
|
|
|
|
this._extensionVersionDirectoryNames[path] = entries;
|
|
return entries;
|
|
},
|
|
};
|