Source code
Revision control
Copy as Markdown
Other Tools
/* 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
import { AsyncShutdown } from "resource://gre/modules/AsyncShutdown.sys.mjs";
const NEW_PROFILE_PREF = "toolkit.profiles.newProfileSubmitted";
const STOREID_PREF = "toolkit.profiles.storeID";
const MS_PER_DAY = 86400000;
export let ProfileMetrics = {
init() {
let isNewProfile = !Services.prefs.getBoolPref(NEW_PROFILE_PREF, false);
let collectPromise = this._collectProfileData(isNewProfile).catch(e => {
console.error("ProfileMetrics: failed to collect profile data", e);
});
if (isNewProfile) {
AsyncShutdown.profileBeforeChange.addBlocker(
"ProfileMetrics: submit new profile ping",
async () => {
await collectPromise;
this._onShutdown();
}
);
}
return collectPromise;
},
_onShutdown() {
if (!Services.prefs.getBoolPref(NEW_PROFILE_PREF, false)) {
GleanPings.newProfile.submit();
Services.prefs.setBoolPref(NEW_PROFILE_PREF, true);
}
},
async _collectProfileData(isNewProfile) {
let currentProfileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
let profileService = Cc[
"@mozilla.org/toolkit/profile-service;1"
].getService(Ci.nsIToolkitProfileService);
let knownStoreIDs = new Set();
let knownProfilePaths = new Set();
let currentStoreID = Services.prefs.getStringPref(STOREID_PREF, "") || null;
for (let profile of profileService.profiles) {
if (profile.storeID) {
knownStoreIDs.add(profile.storeID);
}
let path = profile.rootDir.path;
knownProfilePaths.add(path);
}
Glean.profiles.pathInProfilesIni.set(
knownProfilePaths.has(currentProfileDir.path)
);
if (currentStoreID) {
Glean.profiles.storeIdInProfilesIni.set(
knownStoreIDs.has(currentStoreID)
);
}
let currentProfile = profileService.currentProfile;
if (currentProfile) {
Glean.profiles.storeIdMismatch.set(
currentStoreID != currentProfile.storeID
);
} else {
Glean.profiles.storeIdMismatch.set(false);
}
if (!isNewProfile) {
return;
}
let possibleProfileDirs = new Set(knownProfilePaths);
let defProfRt = Services.dirsvc.get("DefProfRt", Ci.nsIFile);
let entries;
try {
entries = await IOUtils.getChildren(defProfRt.path);
} catch (e) {
entries = [];
}
for (let entryPath of entries) {
let info = await IOUtils.stat(entryPath).catch(() => null);
if (info && info.type == "directory") {
possibleProfileDirs.add(entryPath);
}
}
possibleProfileDirs.delete(currentProfileDir.path);
let allData = await Promise.all(
Array.from(possibleProfileDirs, dir =>
this._gatherProfileData(
dir,
knownStoreIDs,
knownProfilePaths,
currentStoreID
).catch(e => {
console.error(`ProfileMetrics: failed to gather data for ${dir}`, e);
return null;
})
)
);
let otherProfiles = allData.filter(p => !!p);
Glean.profiles.otherProfiles.set(otherProfiles);
},
async _gatherProfileData(
profilePath,
knownStoreIDs,
knownProfilePaths,
currentStoreID
) {
try {
await IOUtils.stat(PathUtils.join(profilePath, "compatibility.ini"));
} catch (e) {
// A missing compatibility.ini indicates an invalid profile so we ignore it.
return null;
}
let stat;
try {
stat = await IOUtils.stat(PathUtils.join(profilePath, "prefs.js"));
} catch (e) {
// A missing prefs.js indicates an invalid profile so we ignore it.
return null;
}
let storeID = await this._extractStoreID(profilePath);
let { isSameInstall, installExists } =
await this._checkInstall(profilePath);
return {
path_in_profiles_ini: knownProfilePaths.has(profilePath),
store_id_in_profiles_ini: knownStoreIDs.has(storeID),
is_current_group: currentStoreID ? storeID == currentStoreID : undefined,
is_same_install: isSameInstall,
install_exists: installExists,
last_used_days: Math.floor((Date.now() - stat.lastModified) / MS_PER_DAY),
};
},
async _extractStoreID(profilePath) {
try {
let prefsContent = await IOUtils.readUTF8(
PathUtils.join(profilePath, "prefs.js")
);
let match = prefsContent.match(
/user_pref\("toolkit\.profiles\.storeID",\s*"([^"]+)"\)/
);
return match ? match[1] : null;
} catch (e) {
return null;
}
},
async _checkInstall(profilePath) {
let compatPath = PathUtils.join(profilePath, "compatibility.ini");
let content = await IOUtils.readUTF8(compatPath);
let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(
Ci.nsIINIParserFactory
);
let parser = factory.createINIParser();
parser.initFromString(content);
let lastPlatformDir = parser.getString("Compatibility", "LastPlatformDir");
let lastDir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
lastDir.initWithPath(lastPlatformDir);
let currentGreDir = Services.dirsvc.get("GreD", Ci.nsIFile);
let isSameInstall = lastDir.equals(currentGreDir);
let installExists = lastDir.exists();
return { isSameInstall, installExists };
},
};