499 lines
16 KiB
JavaScript
499 lines
16 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/. */
|
|
|
|
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]"),
|
|
];
|
|
|
|
// Place the profile items immediately after the separator, repositioning
|
|
// reused ones too. Anchoring keeps them in their declared slot regardless of
|
|
// any later items in the popup (e.g. Select All Tabs in the alt layout).
|
|
let anchor = separator;
|
|
for (let profile of profiles) {
|
|
if (profile.id === currentProfile.id) {
|
|
continue;
|
|
}
|
|
|
|
let menuitem = existingItems.shift();
|
|
if (!menuitem) {
|
|
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 })
|
|
);
|
|
|
|
anchor.after(menuitem);
|
|
anchor = menuitem;
|
|
}
|
|
// If there's any old item to remove, do so now.
|
|
for (let remaining of existingItems) {
|
|
remaining.remove();
|
|
}
|
|
},
|
|
};
|