Bug 862127 - Make migration interfaces more async r=Gijs

In order to clean up sync IO within our profile migrators, we
need to have async interfaces for those parts which are currently
doing sync IO. This converts the sync interfaces and adjusts most
of the call sites (migration.js call site changes are addressed
in a separate patch to break it out a bit).


MozReview-Commit-ID: 2Kcrxco4iYr

--HG--
extra : rebase_source : 53d123c435e43622a999a7e849dddbe1919061e0
This commit is contained in:
Doug Thayer 2018-01-12 09:06:21 -08:00
parent 9ae6a7f57d
commit efdd84d094
9 changed files with 186 additions and 172 deletions

View File

@ -212,83 +212,81 @@ function Qihoo360seProfileMigrator() {
Qihoo360seProfileMigrator.prototype = Object.create(MigratorPrototype);
Object.defineProperty(Qihoo360seProfileMigrator.prototype, "sourceProfiles", {
get() {
if ("__sourceProfiles" in this)
return this.__sourceProfiles;
Qihoo360seProfileMigrator.prototype.getSourceProfiles = function() {
if ("__sourceProfiles" in this)
return this.__sourceProfiles;
if (!this._usersDir) {
this.__sourceProfiles = [];
return this.__sourceProfiles;
if (!this._usersDir) {
this.__sourceProfiles = [];
return this.__sourceProfiles;
}
let profiles = [];
let noLoggedInUser = true;
try {
let loginIni = this._usersDir.clone();
loginIni.append("login.ini");
if (!loginIni.exists()) {
throw new Error("360 Secure Browser's 'login.ini' does not exist.");
}
if (!loginIni.isReadable()) {
throw new Error("360 Secure Browser's 'login.ini' file could not be read.");
}
let profiles = [];
let noLoggedInUser = true;
let loginIniInUtf8 = copyToTempUTF8File(loginIni, "GBK");
let loginIniObj = parseINIStrings(loginIniInUtf8);
try {
let loginIni = this._usersDir.clone();
loginIni.append("login.ini");
if (!loginIni.exists()) {
throw new Error("360 Secure Browser's 'login.ini' does not exist.");
}
if (!loginIni.isReadable()) {
throw new Error("360 Secure Browser's 'login.ini' file could not be read.");
loginIniInUtf8.remove(false);
} catch (ex) {}
let nowLoginEmail = loginIniObj.NowLogin && loginIniObj.NowLogin.email;
/*
* NowLogin section may:
* 1. be missing or without email, before any user logs in.
* 2. represents the current logged in user
* 3. represents the most recent logged in user
*
* In the second case, user represented by NowLogin should be the first
* profile; otherwise the default user should be selected by default.
*/
if (nowLoginEmail) {
if (loginIniObj.NowLogin.IsLogined === "1") {
noLoggedInUser = false;
}
let loginIniInUtf8 = copyToTempUTF8File(loginIni, "GBK");
let loginIniObj = parseINIStrings(loginIniInUtf8);
try {
loginIniInUtf8.remove(false);
} catch (ex) {}
let nowLoginEmail = loginIniObj.NowLogin && loginIniObj.NowLogin.email;
/*
* NowLogin section may:
* 1. be missing or without email, before any user logs in.
* 2. represents the current logged in user
* 3. represents the most recent logged in user
*
* In the second case, user represented by NowLogin should be the first
* profile; otherwise the default user should be selected by default.
*/
if (nowLoginEmail) {
if (loginIniObj.NowLogin.IsLogined === "1") {
noLoggedInUser = false;
}
profiles.push({
id: this._getIdFromConfig(loginIniObj.NowLogin),
name: nowLoginEmail,
});
}
for (let section in loginIniObj) {
if (!loginIniObj[section].email ||
(nowLoginEmail && loginIniObj[section].email == nowLoginEmail)) {
continue;
}
profiles.push({
id: this._getIdFromConfig(loginIniObj[section]),
name: loginIniObj[section].email,
});
}
} catch (e) {
Cu.reportError("Error detecting 360 Secure Browser profiles: " + e);
} finally {
profiles[noLoggedInUser ? "unshift" : "push"]({
id: this._defaultUserPath,
name: "Default",
profiles.push({
id: this._getIdFromConfig(loginIniObj.NowLogin),
name: nowLoginEmail,
});
}
this.__sourceProfiles = profiles.filter(profile => {
let resources = this.getResources(profile);
return resources && resources.length > 0;
for (let section in loginIniObj) {
if (!loginIniObj[section].email ||
(nowLoginEmail && loginIniObj[section].email == nowLoginEmail)) {
continue;
}
profiles.push({
id: this._getIdFromConfig(loginIniObj[section]),
name: loginIniObj[section].email,
});
}
} catch (e) {
Cu.reportError("Error detecting 360 Secure Browser profiles: " + e);
} finally {
profiles[noLoggedInUser ? "unshift" : "push"]({
id: this._defaultUserPath,
name: "Default",
});
return this.__sourceProfiles;
},
});
}
this.__sourceProfiles = profiles.filter(profile => {
let resources = this.getResources(profile);
return resources && resources.length > 0;
});
return this.__sourceProfiles;
};
Qihoo360seProfileMigrator.prototype._getIdFromConfig = function(aConfig) {
return aConfig.UserMd5 || getHash(aConfig.email);
@ -308,12 +306,13 @@ Qihoo360seProfileMigrator.prototype.getResources = function(aProfile) {
return resources.filter(r => r.exists);
};
Qihoo360seProfileMigrator.prototype.getLastUsedDate = function() {
let bookmarksPaths = this.sourceProfiles.map(({id}) => {
Qihoo360seProfileMigrator.prototype.getLastUsedDate = async function() {
let sourceProfiles = await this.getSourceProfiles();
let bookmarksPaths = sourceProfiles.map(({id}) => {
return OS.Path.join(this._usersDir.path, id, kBookmarksFileName);
});
if (!bookmarksPaths.length) {
return Promise.resolve(new Date(0));
return new Date(0);
}
let datePromises = bookmarksPaths.map(path => {
return OS.File.stat(path).catch(() => null).then(info => {

View File

@ -90,17 +90,17 @@ const AutoMigrate = {
* @throws if automatically deciding on migrators/data
* failed for some reason.
*/
migrate(profileStartup, migratorKey, profileToMigrate) {
async migrate(profileStartup, migratorKey, profileToMigrate) {
let histogram = Services.telemetry.getHistogramById(
"FX_STARTUP_MIGRATION_AUTOMATED_IMPORT_PROCESS_SUCCESS");
histogram.add(0);
let {migrator, pickedKey} = this.pickMigrator(migratorKey);
let {migrator, pickedKey} = await this.pickMigrator(migratorKey);
histogram.add(5);
profileToMigrate = this.pickProfile(migrator, profileToMigrate);
profileToMigrate = await this.pickProfile(migrator, profileToMigrate);
histogram.add(10);
let resourceTypes = migrator.getMigrateData(profileToMigrate, profileStartup);
let resourceTypes = await migrator.getMigrateData(profileToMigrate, profileStartup);
if (!(resourceTypes & this.resourceTypesToUse)) {
throw new Error("No usable resources were found for the selected browser!");
}
@ -129,7 +129,7 @@ const AutoMigrate = {
MigrationUtils.initializeUndoData();
Services.obs.addObserver(migrationObserver, "Migration:Ended");
Services.obs.addObserver(migrationObserver, "Migration:ItemError");
migrator.migrate(this.resourceTypesToUse, profileStartup, profileToMigrate);
await migrator.migrate(this.resourceTypesToUse, profileStartup, profileToMigrate);
histogram.add(20);
},
@ -140,7 +140,7 @@ const AutoMigrate = {
* @returns {Object} an object with the migrator to use for migrating, as
* well as the key we eventually ended up using to obtain it.
*/
pickMigrator(migratorKey) {
async pickMigrator(migratorKey) {
if (!migratorKey) {
let defaultKey = MigrationUtils.getMigratorKeyForDefaultBrowser();
if (!defaultKey) {
@ -152,7 +152,7 @@ const AutoMigrate = {
throw new Error("Can't automatically migrate from Firefox.");
}
let migrator = MigrationUtils.getMigrator(migratorKey);
let migrator = await MigrationUtils.getMigrator(migratorKey);
if (!migrator) {
throw new Error("Migrator specified or a default was found, but the migrator object is not available (or has no data).");
}
@ -167,8 +167,8 @@ const AutoMigrate = {
* @returns the profile to migrate, or null if migrating
* from the default profile.
*/
pickProfile(migrator, suggestedId) {
let profiles = migrator.sourceProfiles;
async pickProfile(migrator, suggestedId) {
let profiles = await migrator.getSourceProfiles();
if (profiles && !profiles.length) {
throw new Error("No profile data found to migrate.");
}

View File

@ -140,8 +140,8 @@ ChromeProfileMigrator.prototype.getLastUsedDate =
});
};
Object.defineProperty(ChromeProfileMigrator.prototype, "sourceProfiles", {
get: function Chrome_sourceProfiles() {
ChromeProfileMigrator.prototype.getSourceProfiles =
async function Chrome_getSourceProfiles() {
if ("__sourceProfiles" in this)
return this.__sourceProfiles;
@ -178,11 +178,10 @@ Object.defineProperty(ChromeProfileMigrator.prototype, "sourceProfiles", {
return resources && resources.length > 0;
}, this);
return this.__sourceProfiles;
},
});
};
Object.defineProperty(ChromeProfileMigrator.prototype, "sourceHomePageURL", {
get: function Chrome_sourceHomePageURL() {
ChromeProfileMigrator.prototype.getSourceHomePageURL =
async function Chrome_getSourceHomePageURL() {
let prefsFile = this._chromeUserDataFolder.clone();
prefsFile.append("Preferences");
if (prefsFile.exists()) {
@ -200,8 +199,7 @@ Object.defineProperty(ChromeProfileMigrator.prototype, "sourceHomePageURL", {
}
}
return "";
},
});
};
Object.defineProperty(ChromeProfileMigrator.prototype, "sourceLocked", {
get: function Chrome_sourceLocked() {

View File

@ -374,10 +374,11 @@ EdgeProfileMigrator.prototype.getResources = function() {
return resources.filter(r => r.exists);
};
EdgeProfileMigrator.prototype.getLastUsedDate = function() {
EdgeProfileMigrator.prototype.getLastUsedDate = async function() {
// Don't do this if we don't have a single profile (see the comment for
// sourceProfiles) or if we can't find the database file:
if (this.sourceProfiles !== null || !gEdgeDatabase) {
let sourceProfiles = await this.getSourceProfiles();
if (sourceProfiles !== null || !gEdgeDatabase) {
return Promise.resolve(new Date(0));
}
let logFilePath = OS.Path.join(gEdgeDatabase.parent.path, "LogFiles", "edb.log");
@ -408,10 +409,10 @@ EdgeProfileMigrator.prototype.getLastUsedDate = function() {
* - |[]| to indicate "There are no profiles" (on <=win8.1) which will avoid using this migrator.
* See MigrationUtils.jsm for slightly more info on how sourceProfiles is used.
*/
EdgeProfileMigrator.prototype.__defineGetter__("sourceProfiles", function() {
EdgeProfileMigrator.prototype.getSourceProfiles = function() {
let isWin10OrHigher = AppConstants.isPlatformAndVersionAtLeast("win", "10");
return isWin10OrHigher ? null : [];
});
};
EdgeProfileMigrator.prototype.__defineGetter__("sourceLocked", function() {
// There is an exclusive lock on some databases. Assume they are locked for now.

View File

@ -61,11 +61,9 @@ function sorter(a, b) {
return a.id.toLocaleLowerCase().localeCompare(b.id.toLocaleLowerCase());
}
Object.defineProperty(FirefoxProfileMigrator.prototype, "sourceProfiles", {
get() {
return [...this._getAllProfiles().keys()].map(x => ({id: x, name: x})).sort(sorter);
},
});
FirefoxProfileMigrator.prototype.getSourceProfiles = function() {
return [...this._getAllProfiles().keys()].map(x => ({id: x, name: x})).sort(sorter);
};
FirefoxProfileMigrator.prototype._getFileObject = function(dir, fileName) {
let file = dir.clone();

View File

@ -378,32 +378,30 @@ IEProfileMigrator.prototype.getLastUsedDate = function IE_getLastUsedDate() {
});
};
Object.defineProperty(IEProfileMigrator.prototype, "sourceHomePageURL", {
get: function IE_get_sourceHomePageURL() {
let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
kMainKey, "Default_Page_URL");
let startPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
kMainKey, "Start Page");
// If the user didn't customize the Start Page, he is still on the default
// page, that may be considered the equivalent of our about:home. There's
// no reason to retain it, since it is heavily targeted to IE.
let homepage = startPage != defaultStartPage ? startPage : "";
IEProfileMigrator.prototype.getSourceHomePageURL = function IE_getSourceHomePageURL() {
let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
kMainKey, "Default_Page_URL");
let startPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
kMainKey, "Start Page");
// If the user didn't customize the Start Page, he is still on the default
// page, that may be considered the equivalent of our about:home. There's
// no reason to retain it, since it is heavily targeted to IE.
let homepage = startPage != defaultStartPage ? startPage : "";
// IE7+ supports secondary home pages located in a REG_MULTI_SZ key. These
// are in addition to the Start Page, and no empty entries are possible,
// thus a Start Page is always defined if any of these exists, though it
// may be the default one.
let secondaryPages = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
kMainKey, "Secondary Start Pages");
if (secondaryPages) {
if (homepage)
secondaryPages.unshift(homepage);
homepage = secondaryPages.join("|");
}
// IE7+ supports secondary home pages located in a REG_MULTI_SZ key. These
// are in addition to the Start Page, and no empty entries are possible,
// thus a Start Page is always defined if any of these exists, though it
// may be the default one.
let secondaryPages = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
kMainKey, "Secondary Start Pages");
if (secondaryPages) {
if (homepage)
secondaryPages.unshift(homepage);
homepage = secondaryPages.join("|");
}
return homepage;
},
});
return homepage;
};
IEProfileMigrator.prototype.classDescription = "IE Profile Migrator";
IEProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=ie";

View File

@ -83,7 +83,7 @@ function getMigrationBundle() {
* Here we default for single-profile migrator.
* 5. Implement getResources(aProfile) (see below).
* 6. If the migrator supports reading the home page of the source browser,
* override |sourceHomePageURL| getter.
* override |getSourceHomePageURL| getter.
* 7. For startup-only migrators, override |startupOnlyMigrator|.
*/
this.MigratorPrototype = {
@ -103,7 +103,7 @@ this.MigratorPrototype = {
* For a single-profile source (e.g. safari, ie), this returns null,
* and not an empty array. That is the default implementation.
*/
get sourceProfiles() {
getSourceProfiles() {
return null;
},
@ -206,8 +206,8 @@ this.MigratorPrototype = {
*
* @see nsIBrowserProfileMigrator
*/
getMigrateData: function MP_getMigrateData(aProfile) {
let resources = this._getMaybeCachedResources(aProfile);
getMigrateData: async function MP_getMigrateData(aProfile) {
let resources = await this._getMaybeCachedResources(aProfile);
if (!resources) {
return [];
}
@ -225,8 +225,8 @@ this.MigratorPrototype = {
*
* @see nsIBrowserProfileMigrator
*/
migrate: function MP_migrate(aItems, aStartup, aProfile) {
let resources = this._getMaybeCachedResources(aProfile);
migrate: async function MP_migrate(aItems, aStartup, aProfile) {
let resources = await this._getMaybeCachedResources(aProfile);
if (resources.length == 0)
throw new Error("migrate called for a non-existent source");
@ -411,7 +411,7 @@ this.MigratorPrototype = {
*
* @see nsIBrowserProfileMigrator
*/
get sourceExists() {
async isSourceAvailable() {
if (this.startupOnlyMigrator && !MigrationUtils.isStartupMigration)
return false;
@ -420,9 +420,9 @@ this.MigratorPrototype = {
// profile is available.
let exists = false;
try {
let profiles = this.sourceProfiles;
let profiles = await this.getSourceProfiles();
if (!profiles) {
let resources = this._getMaybeCachedResources("");
let resources = await this._getMaybeCachedResources("");
if (resources && resources.length > 0)
exists = true;
} else {
@ -435,7 +435,7 @@ this.MigratorPrototype = {
},
/** * PRIVATE STUFF - DO NOT OVERRIDE ***/
_getMaybeCachedResources: function PMB__getMaybeCachedResources(aProfile) {
_getMaybeCachedResources: async function PMB__getMaybeCachedResources(aProfile) {
let profileKey = aProfile ? aProfile.id : "";
if (this._resourcesByProfile) {
if (profileKey in this._resourcesByProfile)
@ -443,7 +443,7 @@ this.MigratorPrototype = {
} else {
this._resourcesByProfile = { };
}
this._resourcesByProfile[profileKey] = this.getResources(aProfile);
this._resourcesByProfile[profileKey] = await this.getResources(aProfile);
return this._resourcesByProfile[profileKey];
},
};
@ -663,6 +663,28 @@ this.MigrationUtils = Object.freeze({
return gMigrators;
},
spinResolve: function MU_spinResolve(promise) {
if (!(promise instanceof Promise)) {
return promise;
}
let done = false;
let result = null;
let error = null;
promise.catch(e => {
error = e;
}).then(r => {
result = r;
done = true;
});
Services.tm.spinEventLoopUntil(() => done);
if (error) {
throw error;
} else {
return result;
}
},
/*
* Returns the migrator for the given source, if any data is available
* for this source, or null otherwise.
@ -685,7 +707,7 @@ this.MigrationUtils = Object.freeze({
* @return profile migrator implementing nsIBrowserProfileMigrator, if it can
* import any data, null otherwise.
*/
getMigrator: function MU_getMigrator(aKey) {
getMigrator: async function MU_getMigrator(aKey) {
let migrator = null;
if (this._migrators.has(aKey)) {
migrator = this._migrators.get(aKey);
@ -698,7 +720,7 @@ this.MigrationUtils = Object.freeze({
}
try {
return migrator && migrator.sourceExists ? migrator : null;
return migrator && (await migrator.isSourceAvailable()) ? migrator : null;
} catch (ex) { Cu.reportError(ex); return null; }
},
@ -876,7 +898,8 @@ this.MigrationUtils = Object.freeze({
/**
* Show the migration wizard for startup-migration. This should only be
* called by ProfileMigrator (see ProfileMigrator.js), which implements
* nsIProfileMigrator.
* nsIProfileMigrator. This runs asynchronously if we are running an
* automigration.
*
* @param aProfileStartup
* the nsIProfileStartup instance provided to ProfileMigrator.migrate.
@ -895,6 +918,19 @@ this.MigrationUtils = Object.freeze({
*/
startupMigration:
function MU_startupMigrator(aProfileStartup, aMigratorKey, aProfileToMigrate) {
if (Services.prefs.getBoolPref("browser.migrate.automigrate.enabled", false)) {
this.asyncStartupMigration(aProfileStartup,
aMigratorKey,
aProfileToMigrate);
} else {
this.spinResolve(this.asyncStartupMigration(aProfileStartup,
aMigratorKey,
aProfileToMigrate));
}
},
asyncStartupMigration:
async function MU_asyncStartupMigrator(aProfileStartup, aMigratorKey, aProfileToMigrate) {
if (!aProfileStartup) {
throw new Error("an profile-startup instance is required for startup-migration");
}
@ -902,7 +938,7 @@ this.MigrationUtils = Object.freeze({
let skipSourcePage = false, migrator = null, migratorKey = "";
if (aMigratorKey) {
migrator = this.getMigrator(aMigratorKey);
migrator = await this.getMigrator(aMigratorKey);
if (!migrator) {
// aMigratorKey must point to a valid source, so, if it doesn't
// cleanup and throw.
@ -915,18 +951,19 @@ this.MigrationUtils = Object.freeze({
} else {
let defaultBrowserKey = this.getMigratorKeyForDefaultBrowser();
if (defaultBrowserKey) {
migrator = this.getMigrator(defaultBrowserKey);
migrator = await this.getMigrator(defaultBrowserKey);
if (migrator)
migratorKey = defaultBrowserKey;
}
}
if (!migrator) {
let migrators = await Promise.all(gAvailableMigratorKeys.map(key => this.getMigrator(key)));
// If there's no migrator set so far, ensure that there is at least one
// migrator available before opening the wizard.
// Note that we don't need to check the default browser first, because
// if that one existed we would have used it in the block above this one.
if (!gAvailableMigratorKeys.some(key => !!this.getMigrator(key))) {
if (!migrators.some(m => m)) {
// None of the keys produced a usable migrator, so finish up here:
this.finishMigration();
return;
@ -938,7 +975,7 @@ this.MigrationUtils = Object.freeze({
if (!isRefresh && AutoMigrate.enabled) {
try {
AutoMigrate.migrate(aProfileStartup, migratorKey, aProfileToMigrate);
await AutoMigrate.migrate(aProfileStartup, migratorKey, aProfileToMigrate);
return;
} catch (ex) {
// If automigration failed, continue and show the dialog.

View File

@ -288,24 +288,6 @@ MainPreferencesPropertyList.prototype = {
});
}
},
// Workaround for nsIBrowserProfileMigrator.sourceHomePageURL until
// it's replaced with an async method.
_readSync: function MPPL__readSync() {
if ("_dict" in this)
return this._dict;
let inputStream = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
inputStream.init(this._file, -1, -1, 0);
let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
createInstance(Ci.nsIBinaryInputStream);
binaryStream.setInputStream(inputStream);
let bytes = binaryStream.readByteArray(inputStream.available());
this._dict = PropertyListUtils._readFromArrayBufferSync(
new Uint8Array(bytes).buffer);
return this._dict;
},
};
function SearchStrings(aMainPreferencesPropertyListInstance) {
@ -402,16 +384,14 @@ Object.defineProperty(SafariProfileMigrator.prototype, "mainPreferencesPropertyL
},
});
Object.defineProperty(SafariProfileMigrator.prototype, "sourceHomePageURL", {
get: function get_sourceHomePageURL() {
if (this.mainPreferencesPropertyList) {
let dict = this.mainPreferencesPropertyList._readSync();
if (dict.has("HomePage"))
return dict.get("HomePage");
}
return "";
},
});
SafariProfileMigrator.prototype.getSourceHomePageURL = async function SM_getSourceHomePageURL() {
if (this.mainPreferencesPropertyList) {
let dict = await new Promise(resolve => this.mainPreferencesPropertyList.read(resolve));
if (dict.has("HomePage"))
return dict.get("HomePage");
}
return "";
};
SafariProfileMigrator.prototype.classDescription = "Safari Profile Migrator";
SafariProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=safari";

View File

@ -38,10 +38,10 @@ interface nsIBrowserProfileMigrator : nsISupports
* @param aProfile the profile that we are looking for available data
* to import
* @param aDoingStartup "true" if the profile is not currently being used.
* @return bit field containing profile items (see above)
* @return Promise containing a bit field containing profile items (see above)
* @note a return value of 0 represents no items rather than ALL.
*/
unsigned short getMigrateData(in jsval aProfile, in boolean aDoingStartup);
jsval getMigrateData(in jsval aProfile, in boolean aDoingStartup);
/**
* Get the last time data from this browser was modified
@ -50,23 +50,26 @@ interface nsIBrowserProfileMigrator : nsISupports
jsval getLastUsedDate();
/**
* Whether or not there is any data that can be imported from this
* Get whether or not there is any data that can be imported from this
* browser (i.e. whether or not it is installed, and there exists
* a user profile)
* @return a promise that resolves with a boolean.
*/
readonly attribute boolean sourceExists;
jsval isSourceAvailable();
/**
* An enumeration of available profiles. If the import source does
* not support profiles, this attribute is null.
* @return a promise that resolves with an array of profiles or null.
*/
readonly attribute jsval sourceProfiles;
jsval getSourceProfiles();
/**
* The import source homepage. Returns null if not present/available
* @return a promise that resolves with a string or null.
*/
readonly attribute AUTF8String sourceHomePageURL;
jsval getSourceHomePageURL();
/**