- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 97 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 98 %
- : 99 %
- : 99 %
- : 99 %
- : 99 %
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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import {
SelectableProfileService,
PROFILE_THEMES_MAP,
} from "resource:///modules/profiles/SelectableProfileService.sys.mjs";
import { ProfileAge } from "resource://gre/modules/ProfileAge.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
BackupService: "resource:///modules/backup/BackupService.sys.mjs",
EveryWindow: "resource:///modules/EveryWindow.sys.mjs",
formAutofillStorage: "resource://autofill/FormAutofillStorage.sys.mjs",
LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
PlacesDBUtils: "resource://gre/modules/PlacesDBUtils.sys.mjs",
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
});
/**
* Actor implementation for the profile about pages.
*/
export class ProfilesParent extends JSWindowActorParent {
get tab() {
const gBrowser = this.browsingContext.topChromeWindow.gBrowser;
const tab = gBrowser.getTabForBrowser(this.browsingContext.embedderElement);
return tab;
}
async #getProfileContent(isDark) {
await SelectableProfileService.init();
let currentProfile = SelectableProfileService.currentProfile;
let profileAge = await ProfileAge();
let profiles = await SelectableProfileService.getAllProfiles();
let themes = await this.getSafeForContentThemes(isDark);
return {
currentProfile: await currentProfile.toContentSafeObject(),
isInAutomation: Cu.isInAutomation,
hasDesktopShortcut: currentProfile.hasDesktopShortcut(),
platform: AppConstants.platform,
profiles: await Promise.all(profiles.map(p => p.toContentSafeObject())),
profileCreated: await profileAge.created,
themes,
};
}
async receiveMessage(message) {
let gBrowser = this.browsingContext.topChromeWindow?.gBrowser;
let source = this.browsingContext.embedderElement?.currentURI.displaySpec;
switch (message.name) {
case "Profiles:DeleteProfile": {
if (source === "about:newprofile") {
Glean.profilesNew.closed.record({ value: "delete" });
GleanPings.profiles.submit();
} else if (source === "about:deleteprofile") {
Glean.profilesDelete.confirm.record();
}
// Notify windows that a quit has been requested.
let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
Ci.nsISupportsPRBool
);
Services.obs.notifyObservers(cancelQuit, "quit-application-requested");
if (cancelQuit.data) {
// Something blocked our attempt to quit.
return null;
}
// Since this profile will be deleted, let's make sure to update any prefs
// that depended on its existence
await lazy.BackupService.removeFromEnabledListPref(
SelectableProfileService.currentProfile.id
);
try {
await SelectableProfileService.deleteCurrentProfile();
// Finally, exit.
Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit);
} catch (e) {
// This is expected in tests.
console.error(e);
}
break;
}
case "Profiles:CancelDelete": {
Glean.profilesDelete.cancel.record();
if (gBrowser.tabs.length === 1) {
// If the profiles tab is the only open tab,
// open a new tab first so the browser doesn't close
gBrowser.addTrustedTab("about:newtab");
}
gBrowser.removeTab(this.tab);
break;
}
case "Profiles:GetNewProfileContent": {
Glean.profilesNew.displayed.record();
let isDark = gBrowser.selectedBrowser.ownerGlobal.matchMedia(
"(-moz-system-dark-theme)"
).matches;
return this.#getProfileContent(isDark);
}
case "Profiles:GetEditProfileContent": {
Glean.profilesExisting.displayed.record();
let isDark = gBrowser.selectedBrowser.ownerGlobal.matchMedia(
"(-moz-system-dark-theme)"
).matches;
return this.#getProfileContent(isDark);
}
case "Profiles:MoreThemes": {
if (message.data.source === "about:editprofile") {
Glean.profilesExisting.learnMore.record();
} else if (message.data.source === "about:newprofile") {
Glean.profilesNew.learnMore.record();
}
break;
}
case "Profiles:OpenDeletePage": {
Glean.profilesExisting.deleted.record();
this.browsingContext.embedderElement.loadURI(
Services.io.newURI("about:deleteprofile"),
{
triggeringPrincipal:
Services.scriptSecurityManager.getSystemPrincipal(),
}
);
break;
}
case "Profiles:PageHide": {
if (source === "about:editprofile") {
Glean.profilesExisting.closed.record({ value: "pagehide" });
} else if (source === "about:newprofile") {
Glean.profilesNew.closed.record({ value: "pagehide" });
}
break;
}
case "Profiles:UpdateProfileName": {
if (source === "about:editprofile") {
Glean.profilesExisting.name.record();
} else if (source === "about:newprofile") {
Glean.profilesNew.name.record();
}
let profileObj = message.data;
SelectableProfileService.currentProfile.name = profileObj.name;
break;
}
case "Profiles:SetDesktopShortcut": {
let profile = SelectableProfileService.currentProfile;
let { shouldEnable } = message.data;
if (shouldEnable) {
await profile.ensureDesktopShortcut();
Glean.profilesExisting.shortcut.record({ value: "create" });
} else {
await profile.removeDesktopShortcut();
Glean.profilesExisting.shortcut.record({ value: "delete" });
}
return {
hasDesktopShortcut: profile.hasDesktopShortcut(),
};
}
case "Profiles:GetDeleteProfileContent": {
// Make sure SelectableProfileService is initialized
await SelectableProfileService.init();
Glean.profilesDelete.displayed.record();
let profileObj =
await SelectableProfileService.currentProfile.toContentSafeObject();
let windowCount = lazy.EveryWindow.readyWindows.length;
let tabCount = lazy.EveryWindow.readyWindows
.flatMap(win => win.gBrowser.openTabs.length)
.reduce((total, current) => total + current);
let loginCount = (await lazy.LoginHelper.getAllUserFacingLogins())
.length;
let db = await lazy.PlacesUtils.promiseDBConnection();
let bookmarksQuery = `SELECT count(*) FROM moz_bookmarks b
JOIN moz_bookmarks t ON t.id = b.parent
AND t.parent <> :tags_folder
WHERE b.type = :type_bookmark`;
let bookmarksQueryParams = {
tags_folder: lazy.PlacesUtils.tagsFolderId,
type_bookmark: lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK,
};
let bookmarkCount = (
await db.executeCached(bookmarksQuery, bookmarksQueryParams)
)[0].getResultByIndex(0);
let stats = await lazy.PlacesDBUtils.getEntitiesStatsAndCounts();
let visitCount = stats.find(
item => item.entity == "moz_historyvisits"
).count;
let cookieCount = Services.cookies.cookies.length;
let historyCount = visitCount + cookieCount;
await lazy.formAutofillStorage.initialize();
let autofillCount =
lazy.formAutofillStorage.addresses._data.length +
lazy.formAutofillStorage.creditCards?._data.length;
return {
profile: profileObj,
windowCount,
tabCount,
bookmarkCount,
historyCount,
autofillCount,
loginCount,
};
}
case "Profiles:UpdateProfileAvatar": {
let { avatarOrFile } = message.data;
await SelectableProfileService.currentProfile.setAvatar(avatarOrFile);
let value = SelectableProfileService.currentProfile.hasCustomAvatar
? "custom"
: avatarOrFile;
if (source === "about:editprofile") {
Glean.profilesExisting.avatar.record({ value });
} else if (source === "about:newprofile") {
Glean.profilesNew.avatar.record({ value });
}
let profileObj =
await SelectableProfileService.currentProfile.toContentSafeObject();
return profileObj;
}
case "Profiles:UpdateProfileTheme": {
let themeId = message.data;
// Where the theme was installed from
let telemetryInfo = {
method: "url",
source,
};
await this.enableTheme(themeId, telemetryInfo);
if (source === "about:editprofile") {
Glean.profilesExisting.theme.record({ value: themeId });
} else if (source === "about:newprofile") {
Glean.profilesNew.theme.record({ value: themeId });
}
// The enable theme promise resolves after the
// "lightweight-theme-styling-update" observer so we know the profile
// theme is up to date at this point.
return SelectableProfileService.currentProfile.toContentSafeObject();
}
case "Profiles:CloseProfileTab": {
if (source === "about:editprofile") {
Glean.profilesExisting.closed.record({ value: "done_editing" });
} else if (source === "about:newprofile") {
Glean.profilesNew.closed.record({ value: "done_editing" });
}
if (gBrowser.tabs.length === 1) {
// If the profiles tab is the only open tab,
// open a new tab first so the browser doesn't close
gBrowser.addTrustedTab("about:newtab");
}
gBrowser.removeTab(this.tab);
break;
}
}
return null;
}
async enableTheme(themeId, telemetryInfo) {
await SelectableProfileService.enableTheme(themeId, telemetryInfo);
}
async getSafeForContentThemes(isDark) {
let lightDark = isDark ? "dark" : "light";
let themes = [];
for (let [themeId, themeObj] of PROFILE_THEMES_MAP) {
if (Object.hasOwn(themeObj, "isDark") && themeObj.isDark !== isDark) {
continue;
}
let theme = await lazy.AddonManager.getAddonByID(themeId);
themes.push({
id: themeId,
dataL10nId: themeObj.dataL10nId,
dataL10nTitle: themeObj.dataL10nTitle,
isActive: theme?.isActive ?? false,
...themeObj.colors[lightDark],
useInAutomation: themeObj?.useInAutomation,
});
}
let activeAddons = await lazy.AddonManager.getActiveAddons(["theme"]);
let currentTheme = activeAddons.addons[0];
// Only add the current theme if it's not one of the default 10 themes.
if (!themes.find(t => t.id === currentTheme.id)) {
let safeCurrentTheme = {
id: currentTheme.id,
name: currentTheme.name,
dataL10nTitle: "profiles-custom-theme-title",
isActive: currentTheme.isActive,
chromeColor: SelectableProfileService.currentProfile.theme.themeBg,
toolbarColor: SelectableProfileService.currentProfile.theme.themeFg,
};
themes.push(safeCurrentTheme);
}
return themes;
}
}