/* 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/. */
var gProfiles = {
async init() {
this.copyProfile =
this.copyProfile.bind(
this);
this.createNewProfile =
this.createNewProfile.bind(
this);
this.handleCommand =
this.handleCommand.bind(
this);
this.launchProfile =
this.launchProfile.bind(
this);
this.manageProfiles =
this.manageProfiles.bind(
this);
this.onPopupShowing =
this.onPopupShowing.bind(
this);
this.toggleProfileMenus =
this.toggleProfileMenus.bind(
this);
this.updateView =
this.updateView.bind(
this);
this.bundle = Services.strings.createBundle(
"chrome://browser/locale/browser.properties"
);
this.emptyProfilesButton = PanelMultiView.getViewNode(
document,
"appMenu-empty-profiles-button"
);
this.profilesButton = PanelMultiView.getViewNode(
document,
"appMenu-profiles-button"
);
this.fxaMenuEmptyProfilesButton = PanelMultiView.getViewNode(
document,
"PanelUI-fxa-menu-empty-profiles-button"
);
this.fxaMenuProfilesButton = PanelMultiView.getViewNode(
document,
"PanelUI-fxa-menu-profiles-button"
);
this.subview = PanelMultiView.getViewNode(document,
"PanelUI-profiles");
this.subview.addEventListener(
"command",
this.handleCommand);
PanelUI.mainView.addEventListener(
"ViewShowing", () =>
this._onPanelShowing(
this.profilesButton,
this.emptyProfilesButton)
);
let fxaPanelView = PanelMultiView.getViewNode(document,
"PanelUI-fxa");
fxaPanelView.addEventListener(
"ViewShowing", () =>
this._onPanelShowing(
this.fxaMenuProfilesButton,
this.fxaMenuEmptyProfilesButton
)
);
this.profilesButton.addEventListener(
"command",
this.handleCommand);
this.emptyProfilesButton.addEventListener(
"command",
this.handleCommand);
this.fxaMenuProfilesButton.addEventListener(
"command",
this.handleCommand);
this.fxaMenuEmptyProfilesButton.addEventListener(
"command",
this.handleCommand
);
this.toggleProfileMenus(SelectableProfileService?.isEnabled);
if (SelectableProfileService) {
let listener = (event, isEnabled) =>
this.toggleProfileMenus(isEnabled);
SelectableProfileService.on(
"enableChanged", listener);
window.addEventListener(
"unload", () =>
SelectableProfileService.off(
"enableChanged", listener)
);
}
},
toggleProfileMenus(isEnabled) {
let profilesMenu = document.getElementById(
"profiles-menu");
profilesMenu.hidden = !isEnabled;
},
async _onPanelShowing(profilesButton, emptyProfilesButton) {
if (!SelectableProfileService?.isEnabled) {
emptyProfilesButton.hidden =
true;
profilesButton.hidden =
true;
return;
}
// If the feature is preffed on, but we haven't created profiles yet, the
// service will not be initialized.
let profiles = SelectableProfileService.initialized
? await SelectableProfileService.getAllProfiles()
: [];
if (profiles.length <
2) {
profilesButton.hidden =
true;
emptyProfilesButton.hidden =
false;
return;
}
emptyProfilesButton.hidden =
true;
profilesButton.hidden =
false;
let { themeBg, themeFg } = SelectableProfileService.currentProfile.theme;
profilesButton.style.setProperty(
"--appmenu-profiles-theme-bg", themeBg);
profilesButton.style.setProperty(
"--appmenu-profiles-theme-fg", themeFg);
profilesButton.setAttribute(
"label",
SelectableProfileService.currentProfile.name
);
let avatarURL =
await SelectableProfileService.currentProfile.getAvatarURL(
16);
profilesButton.setAttribute(
"image", avatarURL);
},
/**
* Draws the menubar panel contents.
*/
async onPopupShowing() {
let menuPopup = document.getElementById(
"menu_ProfilesPopup");
let profiles = await SelectableProfileService.getAllProfiles();
let currentProfile = SelectableProfileService.currentProfile;
let insertionPoint = document.getElementById(
"menu_newProfile");
let existingItems = [
...menuPopup.querySelectorAll(
":scope > menuitem[profileid]"),
];
for (let profile of profiles) {
let menuitem = existingItems.shift();
let isNewItem = !menuitem;
if (isNewItem) {
menuitem = document.createXULElement(
"menuitem");
menuitem.classList.add(
"menuitem-iconic",
"menuitem-iconic-profile");
menuitem.setAttribute(
"command",
"Profiles:LaunchProfile");
}
let { themeBg, themeFg } = profile.theme;
menuitem.setAttribute(
"profileid", profile.id);
menuitem.setAttribute(
"image", await profile.getAvatarURL(
48));
menuitem.style.setProperty(
"--menu-profiles-theme-bg", themeBg);
menuitem.style.setProperty(
"--menu-profiles-theme-fg", themeFg);
if (profile.id === currentProfile.id) {
menuitem.classList.add(
"current");
menuitem.setAttribute(
"data-l10n-id",
"menu-profiles-current");
menuitem.setAttribute(
"data-l10n-args",
JSON.stringify({ profileName: profile.name })
);
}
else {
menuitem.classList.remove(
"current");
menuitem.removeAttribute(
"data-l10n-id");
menuitem.removeAttribute(
"data-l10n-args");
menuitem.setAttribute(
"label", profile.name);
}
if (isNewItem) {
menuPopup.insertBefore(menuitem, insertionPoint);
}
}
// If there's any old item to remove, do so now.
for (let remaining of existingItems) {
remaining.remove();
}
},
manageProfiles() {
return SelectableProfileService.maybeSetupDataStore().then(() => {
toOpenWindowByType(
"about:profilemanager",
"about:profilemanager",
"chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar,centerscreen"
);
});
},
copyProfile() {
SelectableProfileService.maybeSetupDataStore().then(() => {
SelectableProfileService.currentProfile.copyProfile();
});
},
createNewProfile(source) {
SelectableProfileService.createNewProfile(
true,
null, source);
},
updateView(target) {
this.populateSubView();
PanelUI.showSubView(
"PanelUI-profiles", target);
},
updateFxAView(target) {
this.populateSubView();
PanelUI.showSubView(
"PanelUI-profiles", target);
},
launchProfile(aEvent) {
SelectableProfileService.getProfile(
aEvent.target.getAttribute(
"profileid")
).then(profile => {
SelectableProfileService.launchInstance(profile);
});
},
async openTabsInProfile(aEvent, tabsToOpen) {
let profile = await SelectableProfileService.getProfile(
aEvent.target.getAttribute(
"profileid")
);
SelectableProfileService.launchInstance(
profile,
tabsToOpen.map(tab => tab.linkedBrowser.currentURI.spec)
);
},
async handleCommand(aEvent) {
switch (aEvent.target.id) {
/* App menu button events */
case "appMenu-profiles-button":
// deliberate fallthrough
case "appMenu-empty-profiles-button": {
this.updateView(aEvent.target);
break;
}
/* FxA menu button events */
case "PanelUI-fxa-menu-empty-profiles-button":
// deliberate fallthrough
case "PanelUI-fxa-menu-profiles-button": {
aEvent.stopPropagation();
this.updateFxAView(aEvent.target);
break;
}
/* Subpanel events that may be triggered in FxA menu or app menu */
case "profiles-appmenu-back-button": {
aEvent.target.closest(
"panelview").panelMultiView.goBack();
aEvent.target.blur();
break;
}
case "profiles-edit-this-profile-button": {
openTrustedLinkIn(
"about:editprofile",
"tab");
break;
}
case "profiles-manage-profiles-button": {
this.manageProfiles();
break;
}
case "profiles-copy-profile-button": {
this.copyProfile();
break;
}
case "profiles-create-profile-button": {
this.createNewProfile(
"profiles-panel");
break;
}
/* Menubar events */
case "Profiles:CreateProfile": {
this.createNewProfile(
"main-menu");
break;
}
case "Profiles:ManageProfiles": {
this.manageProfiles();
break;
}
case "Profiles:LaunchProfile": {
this.launchProfile(aEvent.sourceEvent);
break;
}
case "Profiles:MoveTabsToProfile": {
let tabs;
if (TabContextMenu.contextTab.multiselected) {
tabs = gBrowser.selectedTabs;
}
else {
tabs = [TabContextMenu.contextTab];
}
this.openTabsInProfile(aEvent.sourceEvent, tabs);
break;
}
}
/* Subpanel profile events that may be triggered in FxA menu or app menu */
if (aEvent.target.classList.contains(
"profile-item")) {
this.launchProfile(aEvent);
}
},
/**
* Inserts the subpanel contents for the PanelUI subpanel, which may be shown
* either in the app menu or the FxA toolbar button menu.
*/
async populateSubView() {
let profiles = [];
let currentProfile =
null;
if (SelectableProfileService.initialized) {
profiles = await SelectableProfileService.getAllProfiles();
currentProfile = SelectableProfileService.currentProfile;
}
let subview = PanelMultiView.getViewNode(document,
"PanelUI-profiles");
let backButton = PanelMultiView.getViewNode(
document,
"profiles-appmenu-back-button"
);
backButton.setAttribute(
"aria-label",
this.bundle.GetStringFromName(
"panel.back")
);
backButton.style.fill =
"var(--appmenu-profiles-theme-fg, currentColor)";
let currentProfileCard = PanelMultiView.getViewNode(
document,
"current-profile"
);
currentProfileCard.hidden = !(currentProfile && profiles.length >
1);
let profilesHeader = PanelMultiView.getViewNode(
document,
"PanelUI-profiles-header"
);
let editButton = PanelMultiView.getViewNode(
document,
"profiles-edit-this-profile-button"
);
let profilesList = PanelMultiView.getViewNode(document,
"profiles-list");
// Automatically created by PanelMultiView.
const headerSeparator = profilesHeader.nextElementSibling;
let footerSeparator = PanelMultiView.getViewNode(
document,
"footer-separator"
);
if (!footerSeparator) {
footerSeparator = document.createXULElement(
"toolbarseparator");
footerSeparator.id =
"footer-separator";
}
let createProfileButton = PanelMultiView.getViewNode(
document,
"profiles-create-profile-button"
);
if (!createProfileButton) {
createProfileButton = document.createXULElement(
"toolbarbutton");
createProfileButton.id =
"profiles-create-profile-button";
createProfileButton.classList.add(
"subviewbutton",
"subviewbutton-iconic"
);
createProfileButton.setAttribute(
"data-l10n-id",
"appmenu-create-profile"
);
}
let copyProfileButton = PanelMultiView.getViewNode(
document,
"profiles-copy-profile-button"
);
if (!copyProfileButton) {
copyProfileButton = document.createXULElement(
"toolbarbutton");
copyProfileButton.id =
"profiles-copy-profile-button";
copyProfileButton.classList.add(
"subviewbutton",
"subviewbutton-iconic");
copyProfileButton.setAttribute(
"data-l10n-id",
"appmenu-copy-profile");
}
let manageProfilesButton = PanelMultiView.getViewNode(
document,
"profiles-manage-profiles-button"
);
if (!manageProfilesButton) {
manageProfilesButton = document.createXULElement(
"toolbarbutton");
manageProfilesButton.id =
"profiles-manage-profiles-button";
manageProfilesButton.classList.add(
"subviewbutton",
"panel-subview-footer-button"
);
manageProfilesButton.setAttribute(
"data-l10n-id",
"appmenu-manage-profiles"
);
}
if (profiles.length <
2) {
profilesHeader.removeAttribute(
"style");
editButton.hidden =
true;
headerSeparator.hidden =
false;
footerSeparator.hidden =
true;
const subviewBody = subview.querySelector(
".panel-subview-body");
subview.insertBefore(createProfileButton, subviewBody);
subview.insertBefore(copyProfileButton, subviewBody);
subview.insertBefore(manageProfilesButton, subviewBody);
}
else {
profilesHeader.style.backgroundColor =
"var(--appmenu-profiles-theme-bg)";
profilesHeader.style.color =
"var(--appmenu-profiles-theme-fg)";
editButton.hidden =
false;
}
if (currentProfile && profiles.length >
1) {
let { themeBg, themeFg } = currentProfile.theme;
subview.style.setProperty(
"--appmenu-profiles-theme-bg", themeBg);
subview.style.setProperty(
"--appmenu-profiles-theme-fg", themeFg);
headerSeparator.hidden =
true;
footerSeparator.hidden =
false;
subview.appendChild(footerSeparator);
subview.appendChild(createProfileButton);
subview.appendChild(copyProfileButton);
subview.appendChild(manageProfilesButton);
let headerText = PanelMultiView.getViewNode(
document,
"profiles-header-content"
);
headerText.textContent = currentProfile.name;
let profileIconEl = PanelMultiView.getViewNode(
document,
"profile-icon-image"
);
currentProfileCard.style.setProperty(
"--appmenu-profiles-theme-bg",
themeBg
);
currentProfileCard.style.setProperty(
"--appmenu-profiles-theme-fg",
themeFg
);
profileIconEl.style.listStyleImage = `url(${await currentProfile.getAvatarURL(
80)})
`;
}
let subtitle = PanelMultiView.getViewNode(document, "profiles-subtitle");
subtitle.hidden = profiles.length < 2;
while (profilesList.lastElementChild) {
profilesList.lastElementChild.remove();
}
for (let profile of profiles) {
if (profile.id === SelectableProfileService.currentProfile.id) {
continue;
}
let button = document.createXULElement("toolbarbutton");
button.setAttribute("profileid", profile.id);
button.setAttribute("label", profile.name);
button.className = "subviewbutton subviewbutton-iconic profile-item";
let { themeFg, themeBg } = profile.theme;
button.style.setProperty("--appmenu-profiles-theme-bg", themeBg);
button.style.setProperty("--appmenu-profiles-theme-fg", themeFg);
button.setAttribute("image", await profile.getAvatarURL(16));
profilesList.appendChild(button);
}
},
async populateMoveTabMenu(menuPopup) {
if (!SelectableProfileService.initialized) {
return;
}
const profiles = await SelectableProfileService.getAllProfiles();
const currentProfile = SelectableProfileService.currentProfile;
const separator = document.getElementById("moveTabSeparator");
separator.hidden = profiles.length < 2;
let existingItems = [
...menuPopup.querySelectorAll(":scope > menuitem[profileid]"),
];
for (let profile of profiles) {
if (profile.id === currentProfile.id) {
continue;
}
let menuitem = existingItems.shift();
let isNewItem = !menuitem;
if (isNewItem) {
menuitem = document.createXULElement("menuitem");
menuitem.setAttribute("tbattr", "tabbrowser-multiple-visible");
menuitem.setAttribute("data-l10n-id", "move-to-new-profile");
menuitem.setAttribute("command", "Profiles:MoveTabsToProfile");
}
menuitem.disabled = false;
menuitem.setAttribute("profileid", profile.id);
menuitem.setAttribute(
"data-l10n-args",
JSON.stringify({ profileName: profile.name })
);
if (isNewItem) {
menuPopup.appendChild(menuitem);
}
}
// If there's any old item to remove, do so now.
for (let remaining of existingItems) {
remaining.remove();
}
},
};