Skip to content

Commit

Permalink
Bug 1926145 - Add profiles menu to menubar. r=niklas,fluent-reviewers…
Browse files Browse the repository at this point in the history
…,desktop-theme-reviewers,dao,frontend-codestyle-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D226412
  • Loading branch information
jaredhirsch committed Nov 13, 2024
1 parent 5e21e28 commit 9a440e0
Show file tree
Hide file tree
Showing 12 changed files with 281 additions and 49 deletions.
1 change: 1 addition & 0 deletions .eslintrc-test-paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ const extraBrowserTestPaths = [
"browser/base/content/test/popupNotifications/",
"browser/base/content/test/popups/",
"browser/base/content/test/privateBrowsing/",
"browser/base/content/test/profiles/",
"browser/base/content/test/protectionsUI/",
"browser/base/content/test/referrer/",
"browser/base/content/test/sanitize/",
Expand Down
2 changes: 0 additions & 2 deletions browser/base/content/appmenu-viewcache.inc.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,6 @@
/>
</panelview>

#ifdef MOZ_SELECTABLE_PROFILES
<panelview id="PanelUI-profiles"
flex="1"
has-custom-header="true">
Expand Down Expand Up @@ -239,7 +238,6 @@
/>
</vbox>
</panelview>
#endif

<panelview id="appMenu-library-recentlyClosedTabs"/>
<panelview id="appMenu-library-recentlyClosedWindows"/>
Expand Down
5 changes: 5 additions & 0 deletions browser/base/content/browser-menubar.inc
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,11 @@
</menupopup>
</menu>

<menu id="profiles-menu" data-l10n-id="menu-profiles" hidden="true">
<menupopup id="menu_ProfilesPopup">
</menupopup>
</menu>

<menu id="tools-menu" data-l10n-id="menu-tools">
<menupopup id="menu_ToolsPopup">
<menuitem id="menu_openDownloads"
Expand Down
3 changes: 3 additions & 0 deletions browser/base/content/browser-menubar.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ document.addEventListener(
case "menu_HelpPopup":
buildHelpMenu();
break;
case "menu_ProfilesPopup":
gProfiles.onPopupShowing(event);
break;
}
});

Expand Down
210 changes: 169 additions & 41 deletions browser/base/content/browser-profiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@

var gProfiles = {
async init() {
this.handleEvent.bind(this);
this.launchProfile.bind(this);
this.toggleProfileButtonVisibility.bind(this);
this.updateView.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.profiles = [];
if (SelectableProfileService.initialized) {
this.profiles = await SelectableProfileService.getAllProfiles();
}

this.bundle = Services.strings.createBundle(
"chrome://browser/locale/browser.properties"
Expand All @@ -21,17 +29,23 @@ var gProfiles = {
"PROFILES_ENABLED",
"browser.profiles.enabled",
false,
this.toggleProfileButtonVisibility.bind(this),
this.toggleProfileMenus,
() => SelectableProfileService?.isEnabled
);

if (!this.PROFILES_ENABLED) {
return;
}
await this.toggleProfileMenus();
},

async toggleProfileMenus() {
let profilesMenu = document.getElementById("profiles-menu");
profilesMenu.hidden = !this.PROFILES_ENABLED;

await this.toggleProfileButtonVisibility();
},

/**
* Draws the app menu toolbarbutton.
*/
async toggleProfileButtonVisibility() {
let profilesButton = PanelMultiView.getViewNode(
document,
Expand All @@ -44,12 +58,12 @@ var gProfiles = {
if (!this.PROFILES_ENABLED) {
document.l10n.setAttributes(profilesButton, "appmenu-profiles");
profilesButton.classList.remove("subviewbutton-iconic");
profilesButton.removeEventListener("command", this);
subview.removeEventListener("command", this);
profilesButton.removeEventListener("command", this.handleCommand);
subview.removeEventListener("command", this.handleCommand);
return;
}
profilesButton.addEventListener("command", this);
subview.addEventListener("command", this);
profilesButton.addEventListener("command", this.handleCommand);
subview.addEventListener("command", this.handleCommand);

// If the feature is preffed on, but we haven't created profiles yet, the
// service will not be initialized.
Expand All @@ -63,7 +77,8 @@ var gProfiles = {
}

let { themeBg, themeFg } = SelectableProfileService.currentProfile.theme;
profilesButton.style.cssText = `--themeBg: ${themeBg}; --themeFg: ${themeFg};`;
profilesButton.style.setProperty("--appmenu-profiles-theme-bg", themeBg);
profilesButton.style.setProperty("--appmenu-profiles-theme-fg", themeFg);

profilesButton.classList.add("subviewbutton-iconic");
profilesButton.setAttribute(
Expand All @@ -77,13 +92,95 @@ var gProfiles = {
);
},

updateView(panel) {
this.populateSubView();
/**
* Draws the menubar panel contents.
*/
onPopupShowing() {
// TODO (bug 1926630) We cannot async fetch the current list of profiles
// because menubar popups do not support async popupshowing callbacks
// (the resulting menu is not rendered correctly on macos).
//
// Our temporary workaround is to use a stale cached copy of the profiles
// list to render synchronously, and update our profiles list async. If the
// profiles datastore has been updated since the popup was last shown, the
// contents of the menu will be stale on the first render, then up-to-date
// after that.
//
// Bug 1926630 will ensure correct menu contents by updating
// `this.profiles` in response to a notification from the
// SelectableProfileService, and we can remove this call then.
SelectableProfileService.getAllProfiles().then(profiles => {
this.profiles = profiles;
});

let menuPopup = document.getElementById("menu_ProfilesPopup");

while (menuPopup.hasChildNodes()) {
menuPopup.firstChild.remove();
}

let profiles = this.profiles;
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");
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(() => {
window.openDialog(
"about:profilemanager",
"_blank",
"centerscreen,chrome,titlebar"
);
});
},

createNewProfile() {
SelectableProfileService.createNewProfile();
},

async updateView(panel) {
await this.populateSubView();
PanelUI.showSubView("PanelUI-profiles", panel);
},

// Note: Not async because the browser-sets.js handler is not async.
// This will be an issue when we add menubar menuitems.
launchProfile(aEvent) {
SelectableProfileService.getProfile(
aEvent.target.getAttribute("profileid")
Expand All @@ -92,32 +189,54 @@ var gProfiles = {
});
},

async handleEvent(aEvent) {
let id = aEvent.target.id;
switch (aEvent.type) {
case "command": {
if (id == "appMenu-profiles-button") {
this.updateView(aEvent.target);
} else if (id == "profiles-appmenu-back-button") {
aEvent.target.closest("panelview").panelMultiView.goBack();
aEvent.target.blur();
} else if (id == "profiles-edit-this-profile-button") {
openTrustedLinkIn("about:editprofile", "tab");
} else if (id == "profiles-manage-profiles-button") {
// TODO: (Bug 1924827) Open in a dialog, not a tab.
openTrustedLinkIn("about:profilemanager", "tab");
} else if (id == "profiles-create-profile-button") {
SelectableProfileService.createNewProfile();
} else if (aEvent.target.classList.contains("profile-item")) {
// moved to a helper to expose to the menubar commands
this.launchProfile(aEvent);
}
handleCommand(aEvent) {
switch (aEvent.target.id) {
/* Appmenu events */
case "appMenu-profiles-button": {
this.updateView(aEvent.target);
break;
}
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 - separated out to simplify telemetry */
case "Profiles:CreateProfile": {
this.createNewProfile();
break;
}
case "Profiles:ManageProfiles": {
this.manageProfiles();
break;
}
case "Profiles:LaunchProfile": {
this.launchProfile(aEvent.sourceEvent);
break;
}
}
/* Appmenu */
if (aEvent.target.classList.contains("profile-item")) {
this.launchProfile(aEvent);
}
},

/**
* Draws the subpanel contents for the app menu.
*/
async populateSubView() {
let profiles = [];
let currentProfile = null;
Expand Down Expand Up @@ -156,14 +275,15 @@ var gProfiles = {
profilesHeader.removeAttribute("style");
editButton.hidden = true;
} else {
profilesHeader.style.backgroundColor = "var(--themeBg)";
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.cssText = `--themeBg: ${themeBg}; --themeFg: ${themeFg};`;
subview.style.setProperty("--appmenu-profiles-theme-bg", themeBg);
subview.style.setProperty("--appmenu-profiles-theme-fg", themeFg);

let headerText = PanelMultiView.getViewNode(
document,
Expand All @@ -175,7 +295,14 @@ var gProfiles = {
document,
"profile-icon-image"
);
currentProfileCard.style.cssText = `--themeFg: ${themeFg}; --themeBg: ${themeBg};`;
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")`;
Expand All @@ -198,7 +325,8 @@ var gProfiles = {
button.setAttribute("label", profile.name);
button.className = "subviewbutton subviewbutton-iconic profile-item";
let { themeFg, themeBg } = profile.theme;
button.style.cssText = `--themeBg: ${themeBg}; --themeFg: ${themeFg};`;
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`
Expand Down
3 changes: 3 additions & 0 deletions browser/base/content/browser-sets.inc
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@
<command id="Browser:ReloadSkipCache" disabled="true">
<observes element="Browser:Reload" attribute="disabled"/>
</command>
<command id="Profiles:CreateProfile" />
<command id="Profiles:ManageProfiles" />
<command id="Profiles:LaunchProfile" />
<command id="Browser:NextTab" />
<command id="Browser:PrevTab" />
<command id="Browser:ShowAllTabs" />
Expand Down
6 changes: 6 additions & 0 deletions browser/base/content/browser-sets.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,12 @@ document.addEventListener(
case "Browser:OpenAboutContainers":
openPreferences("paneContainers");
break;
// deliberate fallthrough
case "Profiles:CreateProfile":
case "Profiles:ManageProfiles":
case "Profiles:LaunchProfile":
gProfiles.handleCommand(event);
break;
case "Tools:Search":
BrowserSearch.webSearch();
break;
Expand Down
6 changes: 6 additions & 0 deletions browser/components/profiles/tests/browser/browser.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@ prefs = [

["browser_activate.js"]

["browser_appmenu_menuitem_updates.js"]
head = "../unit/head.js head.js"

["browser_create_profile_page_test.js"]

["browser_edit_profile_test.js"]

["browser_menubar_profiles.js"]
head = "../unit/head.js head.js"

["browser_notify_changes.js"]
run-if = ["os != 'linux'"] # Linux clients cannot remote themselves.

Expand Down
Loading

0 comments on commit 9a440e0

Please sign in to comment.