diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 8fc02fd2b802..b428fe9b0989 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -2097,6 +2097,9 @@ pref("sidebar.revamp.round-content-area", false); pref("sidebar.animation.enabled", true); pref("sidebar.animation.duration-ms", 200); pref("sidebar.animation.expand-on-hover.duration-ms", 400); +// The sidebar.main.tools pref cannot be changed. +// Use the sidebar.newTool.migration. pref branch to introduce a new "tool" to the sidebar launcher; +// see https://firefox-source-docs.mozilla.org/browser/components/sidebar/docs/index.html for instructions. pref("sidebar.main.tools", "aichat,syncedtabs,history"); pref("sidebar.verticalTabs", false); pref("sidebar.visibility", "always-show"); diff --git a/browser/components/sidebar/SidebarManager.sys.mjs b/browser/components/sidebar/SidebarManager.sys.mjs index b6f919046e81..5649c3cfa6da 100644 --- a/browser/components/sidebar/SidebarManager.sys.mjs +++ b/browser/components/sidebar/SidebarManager.sys.mjs @@ -7,6 +7,7 @@ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; const BACKUP_STATE_PREF = "sidebar.backupState"; const VISIBILITY_SETTING_PREF = "sidebar.visibility"; +const SIDEBAR_TOOLS = "sidebar.main.tools"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { @@ -31,6 +32,16 @@ XPCOMUtils.defineLazyPreferenceGetter( } ); +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "sidebarRevampEnabled", + "sidebar.revamp", + false, + () => SidebarManager.updateDefaultTools() +); + +XPCOMUtils.defineLazyPreferenceGetter(lazy, "sidebarTools", SIDEBAR_TOOLS); + export const SidebarManager = { /** * Handle startup tasks like telemetry, adding listeners. @@ -78,6 +89,12 @@ export const SidebarManager = { ); }); + Services.prefs.addObserver( + "sidebar.newTool.migration.", + this.updateDefaultTools.bind(this) + ); + this.updateDefaultTools(); + // if there's no user visibility pref, we may need to update it to the default value for the tab orientation const shouldResetVisibility = !Services.prefs.prefHasUserValue( VISIBILITY_SETTING_PREF @@ -101,6 +118,55 @@ export const SidebarManager = { } }, + /** + * Appends any new tools defined on the sidebar.newTool.migration pref branch + * to the sidebar.main.tools pref one time as a way of introducing a new tool + * to the launcher without overwriting what a user had previously customized. + */ + updateDefaultTools() { + if (!lazy.sidebarRevampEnabled) { + return; + } + + let tools = lazy.sidebarTools; + for (const pref of Services.prefs.getChildList( + "sidebar.newTool.migration." + )) { + try { + let options = JSON.parse(Services.prefs.getStringPref(pref)); + let newTool = pref.split(".")[3]; + + // ensure we only add this tool once + if (options?.alreadyShown || lazy.sidebarTools.includes(newTool)) { + continue; + } + + if (options?.visibilityPref) { + // Will only add the tool to the launcher if the panel governing a panels sidebar visibility + // is first enabled + let visibilityPrefValue = Services.prefs.getBoolPref( + options.visibilityPref + ); + if (!visibilityPrefValue) { + Services.prefs.addObserver( + options.visibilityPref, + this.updateDefaultTools.bind(this) + ); + continue; + } + } + tools = tools + "," + newTool; + options.alreadyShown = true; + Services.prefs.setStringPref(pref, JSON.stringify(options)); + } catch (ex) { + console.error("Failed to handle pref " + pref, ex); + } + } + if (tools.length > lazy.sidebarTools.length) { + Services.prefs.setStringPref(SIDEBAR_TOOLS, tools); + } + }, + /** * Provide a system-level "backup" state to be stored for those using "Never * remember history" or "Clear history when browser closes". diff --git a/browser/components/sidebar/docs/index.rst b/browser/components/sidebar/docs/index.rst new file mode 100644 index 000000000000..3919f65be38a --- /dev/null +++ b/browser/components/sidebar/docs/index.rst @@ -0,0 +1,20 @@ +.. _components/sidebar: + +========= +Sidebar +========= + +The new sidebar builds on existing legacy sidebar code treating ``browser-sidebar.js`` as a `SidebarController`. As part of the new sidebar and vertical tabs project, we've added new components including top-level system module `SidebarManager.sys.mjs` and a per window state manager, `SidebarState.sys.ms`. We've added new UI components that use a combination of moz components and custom lit components. The sidebar team maintains the existing synced tabs and history panels. + +Introducing a new panel +~~~~~~~~~~~~~~~~~~~~~~~ + +Every panel that is registered and enabled in ``browser-sidebar.js``` and the ```defaultTools``` map will show as an option in the Customize Sidebar menu (which is a sidebar panel that contains settings). + +The launcher is a container for tools (ie, icons that when clicked open or close the associated panel). Registering a panel - which should be behind a pref until it is ready to be introduced - does not automatically add a new icon to the launcher. + +A tool can be added once for all users by adding it to the designated pref branch ``sidebar.newTool.migration.`` in ``profile/firefox.js``. So an example would be ``pref("sidebar.newTool.migration.bookmarks", '{}')``. The pref suffix (``bookmarks`` in this example) is the ``toolID`` that should match what you added as the value portion of the relevant entry in the ``defaultTools`` map in ``browser-sidebar.js``. It's important to note that if you have a pref governing the visibility of your sidebar panel, it will need to be enabled at the same time in order to be shown in a user's launcher - either via a nimbus rollout or in-tree. + +If you only want to add this item if the pref governing visibility is true, you can pass the pref you want to observe, e.g. ``pref("sidebar.newTool.migration.reviewchecker", '{ "visibilityPref": "browser.shopping.experience2023.integratedSidebar"}')`` where ``browser.shopping.experience2023.integratedSidebar`` is the pref controlling the visibility of the review checker panel. + +In both cases, the tool will be introduced to the launcher one time (appended to a user's customized list of tools) and any customization after that (ie, removing it) takes precedence. If it's not removed, it will persist after that session. diff --git a/browser/components/sidebar/moz.build b/browser/components/sidebar/moz.build index c791205b91b9..30e18ba61950 100644 --- a/browser/components/sidebar/moz.build +++ b/browser/components/sidebar/moz.build @@ -16,3 +16,5 @@ EXTRA_JS_MODULES += [ ] XPCSHELL_TESTS_MANIFESTS += ["tests/unit/xpcshell.toml"] + +SPHINX_TREES["docs"] = "docs" diff --git a/browser/components/sidebar/tests/browser/browser.toml b/browser/components/sidebar/tests/browser/browser.toml index 53b5c8205fdb..dee29fd59c3a 100644 --- a/browser/components/sidebar/tests/browser/browser.toml +++ b/browser/components/sidebar/tests/browser/browser.toml @@ -73,6 +73,8 @@ skip-if = [ "os == 'win' && os_version == '11.26100' && opt", # Bug 1898739 ] +["browser_tools_migration.js"] + ["browser_tools_overflow.js"] ["browser_verticalTabs_widget_placements.js"] diff --git a/browser/components/sidebar/tests/browser/browser_tools_migration.js b/browser/components/sidebar/tests/browser/browser_tools_migration.js new file mode 100644 index 000000000000..4d2810deb912 --- /dev/null +++ b/browser/components/sidebar/tests/browser/browser_tools_migration.js @@ -0,0 +1,124 @@ +/* Any copyright is dedicated to the Public Domain. + https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_setup(async () => { + await SpecialPowers.pushPrefEnv({ + set: [ + ["sidebar.main.tools", "syncedtabs,history"], + ["sidebar.newTool.migration.bookmarks", "{}"], + ["browser.ml.chat.enabled", false], + [ + "sidebar.newTool.migration.aichat", + JSON.stringify({ + visibilityPref: "browser.ml.chat.enabled", + }), + ], + ], + }); +}); + +add_task(async function test_one_time_tool_migration() { + const sidebar = document.querySelector("sidebar-main"); + let tools = Services.prefs.getStringPref("sidebar.main.tools"); + is( + tools.split(",").length, + 3, + "Three tools are in the sidebar.main.tools pref" + ); + is( + sidebar.toolButtons.length, + 3, + "Three default tools are visible in the launcher" + ); + + await toggleSidebarPanel(window, "viewCustomizeSidebar"); + let customizeDocument = window.SidebarController.browser.contentDocument; + const customizeComponent = + customizeDocument.querySelector("sidebar-customize"); + let bookmarksInput = Array.from(customizeComponent.toolInputs).find( + input => input.name === "viewBookmarksSidebar" + ); + ok( + bookmarksInput.checked, + "The bookmarks input is checked in the Customize Sidebar menu." + ); + let prefValue = JSON.parse( + Services.prefs.getStringPref("sidebar.newTool.migration.bookmarks") + ); + bookmarksInput.click(); + + await BrowserTestUtils.waitForMutationCondition( + bookmarksInput, + { attributes: true, attributeFilter: ["checked"] }, + () => !bookmarksInput.checked + ); + + ok( + prefValue.alreadyShown, + "Pref property has been updated after being added to tools." + ); + + is(sidebar.toolButtons.length, 2, "Two tools are now shown in the launcher"); +}); + +add_task(async function test_check_visibility_enabled() { + const sidebar = document.querySelector("sidebar-main"); + let tools = Services.prefs.getStringPref("sidebar.main.tools"); + is( + tools.split(",").length, + 2, + "Two tools are in the sidebar.main.tools pref" + ); + + is(sidebar.toolButtons.length, 2, "Two tools are shown in the launcher"); + + let prefValue = JSON.parse( + Services.prefs.getStringPref("sidebar.newTool.migration.aichat") + ); + + ok(!prefValue.alreadyShown, "aichat pref property was not already shown."); + + // enable aichat panel visibility + await SpecialPowers.pushPrefEnv({ + set: [["browser.ml.chat.enabled", true]], + }); + + let customizeDocument = window.SidebarController.browser.contentDocument; + const customizeComponent = + customizeDocument.querySelector("sidebar-customize"); + + let aichatInput = Array.from(customizeComponent.toolInputs).find( + input => input.name === "viewGenaiChatSidebar" + ); + + await BrowserTestUtils.waitForMutationCondition( + aichatInput, + { attributes: true, attributeFilter: ["checked"] }, + () => aichatInput.checked + ); + + let bookmarksInput = Array.from(customizeComponent.toolInputs).find( + input => input.name === "viewBookmarksSidebar" + ); + ok( + !bookmarksInput.checked, + "The bookmarks input is not checked in the Customize Sidebar menu." + ); + + is( + sidebar.toolButtons.length, + 3, + "Three tools are now shown in the launcher" + ); + + prefValue = JSON.parse( + Services.prefs.getStringPref("sidebar.newTool.migration.aichat") + ); + + ok( + prefValue.alreadyShown, + "aichat pref property is now marked as already shown." + ); +}); diff --git a/browser/docs/index.rst b/browser/docs/index.rst index 64b6dc3c942a..1736bb4ebd13 100644 --- a/browser/docs/index.rst +++ b/browser/docs/index.rst @@ -37,3 +37,4 @@ This is the nascent documentation of the Firefox front-end code. /toolkit/themes/shared/design-system/docs/README.design-tokens.stories /toolkit/themes/shared/design-system/docs/README.json-design-tokens.stories components/backup/docs/index + components/sidebar/docs/index