/* 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/. */ // This file is loaded into the browser window scope. /* eslint-env mozilla/browser-window */ var gProfiles = { async init() { 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.onAppMenuViewHiding = this.onAppMenuViewHiding.bind(this); this.onAppMenuViewShowing = this.onAppMenuViewShowing.bind(this); this.onFxAMenuViewShowing = this.onFxAMenuViewShowing.bind(this); this.onFxAMenuViewHiding = this.onFxAMenuViewHiding.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" ); XPCOMUtils.defineLazyPreferenceGetter( this, "PROFILES_ENABLED", "browser.profiles.enabled", false, this.toggleProfileMenus, () => SelectableProfileService?.isEnabled ); this.toggleProfileMenus(); }, toggleProfileMenus() { let profilesMenu = document.getElementById("profiles-menu"); profilesMenu.hidden = !this.PROFILES_ENABLED; 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.toggleAppMenuButton(); this.toggleFxAMenuButton(); }, toggleAppMenuButton() { if (!this.PROFILES_ENABLED) { PanelUI.mainView.removeEventListener( "ViewShowing", this.onAppMenuViewShowing ); PanelUI.mainView.removeEventListener( "ViewHiding", this.onAppMenuViewHiding ); } else { PanelUI.mainView.addEventListener( "ViewShowing", this.onAppMenuViewShowing ); PanelUI.mainView.addEventListener("ViewHiding", this.onAppMenuViewHiding); } }, toggleFxAMenuButton() { let fxaPanelView = PanelMultiView.getViewNode(document, "PanelUI-fxa"); if (!this.PROFILES_ENABLED) { fxaPanelView.removeEventListener( "ViewShowing", this.onFxAMenuViewShowing ); fxaPanelView.removeEventListener("ViewHiding", this.onFxAMenuViewHiding); fxaPanelView.removeEventListener("command", this.handleCommand); } else { fxaPanelView.addEventListener("ViewShowing", this.onFxAMenuViewShowing); fxaPanelView.addEventListener("ViewHiding", this.onFxAMenuViewHiding); fxaPanelView.addEventListener("command", this.handleCommand); } }, onAppMenuViewShowing() { this._onPanelShowing(this.profilesButton, this.emptyProfilesButton); }, onFxAMenuViewShowing() { this._onPanelShowing( this.fxaMenuProfilesButton, this.fxaMenuEmptyProfilesButton ); }, async _onPanelShowing(profilesButton, emptyProfilesButton) { if (!this.PROFILES_ENABLED) { profilesButton.hidden = true; emptyProfilesButton.hidden = true; return; } profilesButton.addEventListener("command", this.handleCommand); this.subview.addEventListener("command", this.handleCommand); // 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; emptyProfilesButton.addEventListener("command", this.handleCommand); return; } emptyProfilesButton.hidden = true; profilesButton.hidden = false; profilesButton.addEventListener("command", this.handleCommand); 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 avatar = SelectableProfileService.currentProfile.avatar; profilesButton.setAttribute( "image", `chrome://browser/content/profiles/assets/16_${avatar}.svg` ); }, onAppMenuViewHiding() { this.profilesButton.removeEventListener("command", this.handleCommand); this.emptyProfilesButton.removeEventListener("command", this.handleCommand); // If the user closes the app menu by opening the FxA menu, the FxA showing // handler will run before this code runs. To avoid disconnecting the // subview handlers in the FxA panel, bail out if the FxA panel is visible. if (document.getElementById("PanelUI-fxa")?.getAttribute("visible")) { return; } this.subview.removeEventListener("command", this.handleCommand); }, onFxAMenuViewHiding() { this.fxaMenuProfilesButton.removeEventListener( "command", this.handleCommand ); this.fxaMenuEmptyProfilesButton.removeEventListener( "command", this.handleCommand ); // If the app menu is already open, don't disconnect the subview listeners. // (See related comment inside `onAppMenuViewHiding`.) if (PanelUI.mainView.getAttribute("visible")) { return; } this.subview.removeEventListener("command", this.handleCommand); }, /** * Draws the menubar panel contents. */ async onPopupShowing() { let menuPopup = document.getElementById("menu_ProfilesPopup"); while (menuPopup.hasChildNodes()) { menuPopup.firstChild.remove(); } let profiles = await SelectableProfileService.getAllProfiles(); let currentProfile = SelectableProfileService.currentProfile; for (let profile of profiles) { let menuitem = document.createXULElement("menuitem"); let { themeBg, themeFg } = profile.theme; menuitem.setAttribute("profileid", profile.id); menuitem.setAttribute("command", "Profiles:LaunchProfile"); menuitem.setAttribute("label", profile.name); menuitem.style.setProperty("--menu-profiles-theme-bg", themeBg); menuitem.style.setProperty("--menu-profiles-theme-fg", themeFg); menuitem.style.listStyleImage = `url(chrome://browser/content/profiles/assets/48_${profile.avatar}.svg)`; menuitem.classList.add("menuitem-iconic", "menuitem-iconic-profile"); if (profile.id === currentProfile.id) { menuitem.classList.add("current"); menuitem.setAttribute("type", "checkbox"); menuitem.setAttribute("checked", "true"); } menuPopup.appendChild(menuitem); } let newProfile = document.createXULElement("menuitem"); newProfile.id = "menu_newProfile"; newProfile.setAttribute("command", "Profiles:CreateProfile"); newProfile.setAttribute("data-l10n-id", "menu-profiles-new-profile"); newProfile.style.listStyleImage = "url(chrome://global/skin/icons/plus.svg)"; menuPopup.appendChild(newProfile); let separator = document.createXULElement("menuseparator"); separator.id = "profilesSeparator"; menuPopup.appendChild(separator); let manageProfiles = document.createXULElement("menuitem"); manageProfiles.id = "menu_manageProfiles"; manageProfiles.setAttribute("command", "Profiles:ManageProfiles"); manageProfiles.setAttribute( "data-l10n-id", "menu-profiles-manage-profiles" ); menuPopup.appendChild(manageProfiles); }, manageProfiles() { return SelectableProfileService.maybeSetupDataStore().then(() => { toOpenWindowByType( "about:profilemanager", "about:profilemanager", "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar,centerscreen" ); }); }, createNewProfile() { SelectableProfileService.createNewProfile(); }, async updateView(target) { await this.populateSubView(); PanelUI.showSubView("PanelUI-profiles", target); }, async updateFxAView(target) { await this.populateSubView(); PanelUI.showSubView("PanelUI-profiles", target); }, launchProfile(aEvent) { SelectableProfileService.getProfile( aEvent.target.getAttribute("profileid") ).then(profile => { SelectableProfileService.launchInstance(profile); }); }, 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-create-profile-button": { this.createNewProfile(); break; } /* Menubar events */ case "Profiles:CreateProfile": { this.createNewProfile(); break; } case "Profiles:ManageProfiles": { this.manageProfiles(); break; } case "Profiles:LaunchProfile": { this.launchProfile(aEvent.sourceEvent); 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 backButton = PanelMultiView.getViewNode( document, "profiles-appmenu-back-button" ); backButton.setAttribute( "aria-label", this.bundle.GetStringFromName("panel.back") ); 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" ); if (profiles.length < 2) { profilesHeader.removeAttribute("style"); editButton.hidden = true; } else { profilesHeader.style.backgroundColor = "var(--appmenu-profiles-theme-bg)"; editButton.hidden = false; } if (currentProfile && profiles.length > 1) { let subview = PanelMultiView.getViewNode(document, "PanelUI-profiles"); let { themeBg, themeFg } = currentProfile.theme; subview.style.setProperty("--appmenu-profiles-theme-bg", themeBg); subview.style.setProperty("--appmenu-profiles-theme-fg", themeFg); 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 ); let avatar = currentProfile.avatar; profileIconEl.style.listStyleImage = `url("chrome://browser/content/profiles/assets/80_${avatar}.svg")`; } let subtitle = PanelMultiView.getViewNode(document, "profiles-subtitle"); subtitle.hidden = profiles.length < 2; let profilesList = PanelMultiView.getViewNode(document, "profiles-list"); 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", `chrome://browser/content/profiles/assets/16_${profile.avatar}.svg` ); profilesList.appendChild(button); } }, };