diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /browser/components/customizableui/content | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/customizableui/content')
6 files changed, 1539 insertions, 0 deletions
diff --git a/browser/components/customizableui/content/.eslintrc.js b/browser/components/customizableui/content/.eslintrc.js new file mode 100644 index 0000000000..43ab18578d --- /dev/null +++ b/browser/components/customizableui/content/.eslintrc.js @@ -0,0 +1,13 @@ +/* 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/. */ + +"use strict"; + +module.exports = { + env: { + "mozilla/browser-window": true, + }, + + plugins: ["mozilla"], +}; diff --git a/browser/components/customizableui/content/customizeMode.inc.xhtml b/browser/components/customizableui/content/customizeMode.inc.xhtml new file mode 100644 index 0000000000..c44cc1bc18 --- /dev/null +++ b/browser/components/customizableui/content/customizeMode.inc.xhtml @@ -0,0 +1,119 @@ +<!-- 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/. --> + +<box id="customization-content-container"> +<box id="customization-palette-container"> + <label id="customization-header" data-l10n-id="customize-mode-menu-and-toolbars-header"></label> + <vbox id="customization-palette" class="customization-palette" hidden="true"/> + <html:div id="customization-pong-arena" hidden="true"/> + <spacer id="customization-spacer"/> +</box> +<vbox id="customization-panel-container"> + <vbox id="customization-panelWrapper"> + <box class="panel-arrowbox"> + <image class="panel-arrow" side="top"/> + </box> + <box class="panel-arrowcontent" side="top" flex="1"> + <vbox id="customization-panelHolder"> + <description id="customization-panelHeader" data-l10n-id="customize-mode-overflow-list-title"></description> + <description id="customization-panelDescription" data-l10n-id="customize-mode-overflow-list-description"></description> + </vbox> + <box class="panel-inner-arrowcontentfooter" hidden="true"/> + </box> + </vbox> +</vbox> +</box> +<hbox id="customization-footer"> +<checkbox id="customization-titlebar-visibility-checkbox" class="customizationmode-checkbox" +# NB: because oncommand fires after click, by the time we've fired, the checkbox binding +# will already have switched the button's state, so this is correct: + oncommand="gCustomizeMode.toggleTitlebar(this.checked)" data-l10n-id="customize-mode-titlebar"/> +<button id="customization-toolbar-visibility-button" class="customizationmode-button" type="menu" data-l10n-id="customize-mode-toolbars"> + <menupopup id="customization-toolbar-menu" onpopupshowing="onViewToolbarsPopupShowing(event)"/> +</button> +<button id="customization-uidensity-button" + data-l10n-id="customize-mode-uidensity" + class="customizationmode-button" + type="menu" + hidden="true"> + <panel type="arrow" id="customization-uidensity-menu" + orient="vertical" + onpopupshowing="gCustomizeMode.onUIDensityMenuShowing();" + position="topleft bottomleft" + flip="none" + role="menu"> + <menuitem id="customization-uidensity-menuitem-compact" + class="menuitem-iconic customization-uidensity-menuitem" + role="menuitemradio" + data-l10n-id="customize-mode-uidensity-menu-compact-unsupported" + tabindex="0" + onfocus="gCustomizeMode.updateUIDensity(this.mode);" + onmouseover="gCustomizeMode.updateUIDensity(this.mode);" + onblur="gCustomizeMode.resetUIDensity();" + onmouseout="gCustomizeMode.resetUIDensity();" + oncommand="gCustomizeMode.setUIDensity(this.mode);"/> + <menuitem id="customization-uidensity-menuitem-normal" + class="menuitem-iconic customization-uidensity-menuitem" + role="menuitemradio" + data-l10n-id="customize-mode-uidensity-menu-normal" + tabindex="0" + onfocus="gCustomizeMode.updateUIDensity(this.mode);" + onmouseover="gCustomizeMode.updateUIDensity(this.mode);" + onblur="gCustomizeMode.resetUIDensity();" + onmouseout="gCustomizeMode.resetUIDensity();" + oncommand="gCustomizeMode.setUIDensity(this.mode);"/> +#ifndef XP_MACOSX + <menuitem id="customization-uidensity-menuitem-touch" + class="menuitem-iconic customization-uidensity-menuitem" + role="menuitemradio" + data-l10n-id="customize-mode-uidensity-menu-touch" + tabindex="0" + onfocus="gCustomizeMode.updateUIDensity(this.mode);" + onmouseover="gCustomizeMode.updateUIDensity(this.mode);" + onblur="gCustomizeMode.resetUIDensity();" + onmouseout="gCustomizeMode.resetUIDensity();" + oncommand="gCustomizeMode.setUIDensity(this.mode);"> + </menuitem> + <spacer hidden="true" id="customization-uidensity-touch-spacer"/> + <checkbox id="customization-uidensity-autotouchmode-checkbox" + hidden="true" + data-l10n-id="customize-mode-uidensity-auto-touch-mode-checkbox" + oncommand="gCustomizeMode.updateAutoTouchMode(this.checked)"/> +#endif + </panel> +</button> +<label is="text-link" + id="customization-lwtheme-link" + data-l10n-id="customize-mode-lwthemes-link" + onclick="gCustomizeMode.openAddonsManagerThemes();" /> + +<button id="whimsy-button" + type="checkbox" + class="customizationmode-button" + oncommand="gCustomizeMode.togglePong(this.checked);" + hidden="true"/> + +<spacer id="customization-footer-spacer"/> +#ifdef XP_MACOSX + <button id="customization-touchbar-button" + class="customizationmode-button" + hidden="true" + oncommand="gCustomizeMode.customizeTouchBar();" + data-l10n-id="customize-mode-touchbar-cmd"/> + <spacer hidden="true" id="customization-touchbar-spacer"/> +#endif +<button id="customization-undo-reset-button" + class="customizationmode-button" + hidden="true" + oncommand="gCustomizeMode.undoReset();" + data-l10n-id="customize-mode-undo-cmd"/> +<button id="customization-reset-button" + oncommand="gCustomizeMode.reset();" + data-l10n-id="customize-mode-restore-defaults" + class="customizationmode-button"/> +<button id="customization-done-button" + oncommand="gCustomizeMode.exit();" + data-l10n-id="customize-mode-done" + class="customizationmode-button"/> +</hbox> diff --git a/browser/components/customizableui/content/jar.mn b/browser/components/customizableui/content/jar.mn new file mode 100644 index 0000000000..08642a640c --- /dev/null +++ b/browser/components/customizableui/content/jar.mn @@ -0,0 +1,6 @@ +# 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/. + +browser.jar: + content/browser/customizableui/panelUI.js diff --git a/browser/components/customizableui/content/moz.build b/browser/components/customizableui/content/moz.build new file mode 100644 index 0000000000..d988c0ff9b --- /dev/null +++ b/browser/components/customizableui/content/moz.build @@ -0,0 +1,7 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +JAR_MANIFESTS += ["jar.mn"] diff --git a/browser/components/customizableui/content/panelUI.inc.xhtml b/browser/components/customizableui/content/panelUI.inc.xhtml new file mode 100644 index 0000000000..8459d64d7b --- /dev/null +++ b/browser/components/customizableui/content/panelUI.inc.xhtml @@ -0,0 +1,325 @@ +<!-- 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/. --> + +<panel id="widget-overflow" + class="panel-no-padding" + role="group" + type="arrow" + noautofocus="true" + position="bottomright topright" + hidden="true"> + <panelmultiview mainViewId="widget-overflow-mainView"> + <panelview id="widget-overflow-mainView" + context="toolbar-context-menu"> + <vbox class="panel-subview-body"> + <vbox id="widget-overflow-list" class="widget-overflow-list" + overflowfortoolbar="nav-bar"/> + <toolbarseparator id="widget-overflow-fixed-separator" hidden="true"/> + <vbox id="widget-overflow-fixed-list" class="widget-overflow-list" hidden="true" /> + </vbox> + <toolbarseparator /> + <toolbarbutton command="cmd_CustomizeToolbars" + id="overflowMenu-customize-button" + class="subviewbutton panel-subview-footer-button" + data-l10n-id="toolbar-overflow-customize-button"/> + </panelview> + </panelmultiview> + <!-- This menu is here because not having it in the menu in which it's used flickers + when hover styles overlap. See https://bugzilla.mozilla.org/show_bug.cgi?id=1378427 . + --> + <menupopup id="customizationPanelItemContextMenu" + onpopupshowing="gCustomizeMode.onPanelContextMenuShowing(event); ToolbarContextMenu.updateExtension(this)"> + <menuitem oncommand="ToolbarContextMenu.openAboutAddonsForContextAction(this.parentElement)" + data-lazy-l10n-id="toolbar-context-menu-manage-extension" + contexttype="toolbaritem" + class="customize-context-manageExtension"/> + <menuitem oncommand="ToolbarContextMenu.removeExtensionForContextAction(this.parentElement)" + data-lazy-l10n-id="toolbar-context-menu-remove-extension" + contexttype="toolbaritem" + class="customize-context-removeExtension"/> + <menuitem oncommand="ToolbarContextMenu.reportExtensionForContextAction(this.parentElement, 'toolbar_context_menu')" + data-lazy-l10n-id="toolbar-context-menu-report-extension" + contexttype="toolbaritem" + class="customize-context-reportExtension"/> + <menuseparator/> + <menuitem oncommand="gCustomizeMode.addToPanel(this.parentNode.triggerNode, 'panelitem-context')" + id="customizationPanelItemContextMenuPin" + data-lazy-l10n-id="toolbar-context-menu-pin-to-overflow-menu" + closemenu="single" + class="customize-context-moveToPanel"/> + <menuitem oncommand="gCustomizeMode.addToToolbar(this.parentNode.triggerNode, 'panelitem-context')" + id="customizationPanelItemContextMenuUnpin" + closemenu="single" + class="customize-context-moveToToolbar" + data-l10n-id="customize-menu-unpin-from-overflowmenu"/> + <menuitem oncommand="gCustomizeMode.removeFromArea(this.parentNode.triggerNode, 'panelitem-context')" + closemenu="single" + class="customize-context-removeFromPanel" + data-lazy-l10n-id="toolbar-context-menu-remove-from-toolbar"/> + <menuseparator/> + <menuitem command="cmd_CustomizeToolbars" + class="viewCustomizeToolbar" + data-lazy-l10n-id="toolbar-context-menu-view-customize-toolbar"/> + </menupopup> +</panel> + +<html:template id="unified-extensions-panel-template"> + <panel id="unified-extensions-panel" + class="panel-no-padding" + role="group" + type="arrow" + noautofocus="true" + position="bottomright topright" + hidden="true"> + <panelmultiview mainViewId="unified-extensions-view"> + <panelview id="unified-extensions-view" + class="cui-widget-panelview" + showheader="true"> + <vbox class="panel-header"> + <html:h1> + <html:span data-l10n-id="unified-extensions-header-title"/> + </html:h1> + </vbox> + + <toolbarseparator /> + + <vbox class="panel-subview-body" context="unified-extensions-context-menu"> + <vbox id="overflowed-extensions-list"> + <!-- overflowed extension buttons from the nav-bar will go here --> + </vbox> + + <vbox id="unified-extensions-area"> + <!-- default area for extension browser action buttons --> + </vbox> + + <vbox class="unified-extensions-list"> + <!-- active visible extensions go here --> + </vbox> + </vbox> + + <toolbarseparator /> + + <toolbarbutton id="unified-extensions-manage-extensions" + class="subviewbutton panel-subview-footer-button unified-extensions-manage-extensions" + data-l10n-id="unified-extensions-manage-extensions" + oncommand="BrowserOpenAddonsMgr('addons://list/extension');" /> + </panelview> + </panelmultiview> + </panel> +</html:template> + +<html:template id="panicButtonNotificationTemplate"> + <panel id="panic-button-success-notification" + type="arrow" + position="bottomright topright" + hidden="true" + role="alert" + orient="vertical"> + <hbox id="panic-button-success-header"> + <image id="panic-button-success-icon" alt=""/> + <vbox> + <description data-l10n-id="panic-button-thankyou-msg1"></description> + <description data-l10n-id="panic-button-thankyou-msg2"></description> + </vbox> + </hbox> + <button id="panic-button-success-closebutton" + data-l10n-id="panic-button-thankyou-button" + oncommand="PanicButtonNotifier.close()"/> + </panel> +</html:template> + +<html:template id="appMenuNotificationTemplate"> + <panel id="appMenu-notification-popup" + class="popup-notification-panel panel-no-padding" + type="arrow" + position="after_start" + flip="slide" + orient="vertical" + noautofocus="true" + noautohide="true" + nopreventnavboxhide="true" + role="alert"> + <popupnotification id="appMenu-update-available-notification" + popupid="update-available" + data-lazy-l10n-id="appmenu-update-available2" + data-l10n-attrs="buttonlabel, buttonaccesskey, secondarybuttonlabel, secondarybuttonaccesskey" + closebuttonhidden="true" + dropmarkerhidden="true" + checkboxhidden="true" + buttonhighlight="true" + hasicon="true" + hidden="true"> + <popupnotificationcontent id="update-available-notification-content" orient="vertical"> + <description id="update-available-description" data-lazy-l10n-id="appmenu-update-available-message2"></description> + </popupnotificationcontent> + </popupnotification> + + <popupnotification id="appMenu-update-manual-notification" + popupid="update-manual" + data-lazy-l10n-id="appmenu-update-manual2" + data-l10n-attrs="buttonlabel, buttonaccesskey, secondarybuttonlabel, secondarybuttonaccesskey" + closebuttonhidden="true" + dropmarkerhidden="true" + checkboxhidden="true" + buttonhighlight="true" + hasicon="true" + hidden="true"> + <popupnotificationcontent id="update-manual-notification-content" orient="vertical"> + <description id="update-manual-description" data-lazy-l10n-id="appmenu-update-manual-message2"></description> + </popupnotificationcontent> + </popupnotification> + + <popupnotification id="appMenu-update-unsupported-notification" + popupid="update-unsupported" + data-lazy-l10n-id="appmenu-update-unsupported2" + data-l10n-attrs="buttonlabel, buttonaccesskey, secondarybuttonlabel, secondarybuttonaccesskey" + closebuttonhidden="true" + dropmarkerhidden="true" + checkboxhidden="true" + buttonhighlight="true" + hasicon="true" + hidden="true"> + <popupnotificationcontent id="update-unsupported-notification-content" orient="vertical"> + <description id="update-unsupported-description" data-lazy-l10n-id="appmenu-update-unsupported-message2"></description> + </popupnotificationcontent> + </popupnotification> + + <popupnotification id="appMenu-update-restart-notification" + popupid="update-restart" + data-lazy-l10n-id="appmenu-update-restart2" + data-l10n-attrs="buttonlabel, buttonaccesskey, secondarybuttonlabel, secondarybuttonaccesskey" + closebuttonhidden="true" + dropmarkerhidden="true" + checkboxhidden="true" + buttonhighlight="true" + hasicon="true" + hidden="true"> + <popupnotificationcontent id="update-restart-notification-content" orient="vertical"> + <description id="update-restart-description" data-lazy-l10n-id="appmenu-update-restart-message2"></description> + </popupnotificationcontent> + </popupnotification> + + <popupnotification id="appMenu-update-other-instance-notification" + popupid="update-other-instance" + data-lazy-l10n-id="appmenu-update-other-instance" + data-l10n-attrs="buttonlabel, buttonaccesskey, secondarybuttonlabel, secondarybuttonaccesskey" + closebuttonhidden="true" + dropmarkerhidden="true" + checkboxhidden="true" + buttonhighlight="true" + hasicon="true" + hidden="true"> + <popupnotificationcontent id="update-other-instance-notification-content" orient="vertical"> + <description id="update-other-instance-description" data-lazy-l10n-id="appmenu-update-other-instance-message"></description> + </popupnotificationcontent> + </popupnotification> + + <popupnotification id="appMenu-addon-installed-notification" + popupid="addon-installed" + closebuttonhidden="true" + secondarybuttonhidden="true" + data-lazy-l10n-id="appmenu-addon-private-browsing-installed2" + data-l10n-attrs="buttonlabel, buttonaccesskey" + dropmarkerhidden="true" + checkboxhidden="true" + buttonhighlight="true" + hidden="true"> + <popupnotificationcontent class="addon-installed-notification-content" orient="vertical"> + <description id="addon-install-description" data-lazy-l10n-id="appmenu-addon-post-install-message3"/> + <checkbox id="addon-incognito-checkbox" + data-lazy-l10n-id="appmenu-addon-post-install-incognito-checkbox"/> + </popupnotificationcontent> + </popupnotification> + </panel> +</html:template> + +<html:template id="customModeWrapper"> + <menupopup id="customizationPaletteItemContextMenu" + onpopupshowing="gCustomizeMode.onPaletteContextMenuShowing(event)"> + <menuitem oncommand="gCustomizeMode.addToToolbar(this.parentNode.triggerNode, 'palette-context')" + class="customize-context-addToToolbar" + data-l10n-id="customize-menu-add-to-toolbar"/> + <menuitem oncommand="gCustomizeMode.addToPanel(this.parentNode.triggerNode, 'palette-context')" + class="customize-context-addToPanel" + data-l10n-id="customize-menu-add-to-overflowmenu"/> + </menupopup> + + <panel id="downloads-button-autohide-panel" + role="group" + type="arrow" + onpopupshown="gCustomizeMode._downloadPanelAutoHideTimeout = setTimeout(() => event.target.hidePopup(), 4000);" + onmouseover="clearTimeout(gCustomizeMode._downloadPanelAutoHideTimeout);" + onmouseout="gCustomizeMode._downloadPanelAutoHideTimeout = setTimeout(() => event.target.hidePopup(), 2000);" + onpopuphidden="clearTimeout(gCustomizeMode._downloadPanelAutoHideTimeout);" + > + <checkbox id="downloads-button-autohide-checkbox" + data-l10n-id="customize-mode-downloads-button-autohide" checked="true" + oncommand="gCustomizeMode.onDownloadsAutoHideChange(event)"/> + </panel> +</html:template> + +<panel id="appMenu-popup" + class="cui-widget-panel panel-no-padding" + role="group" + type="arrow" + hidden="true" + flip="slide" + position="bottomright topright" + noautofocus="true"> + <panelmultiview id="appMenu-multiView" mainViewId="appMenu-protonMainView" + viewCacheId="appMenu-viewCache"> + </panelmultiview> +</panel> + +<html:template id="extensionNotificationTemplate"> + <panel id="extension-notification-panel" + class="popup-notification-panel panel-no-padding" + role="group" + type="arrow" + flip="slide" + position="bottomright topright" + tabspecific="true"> + <popupnotification id="extension-new-tab-notification" + class="extension-controlled-notification" + popupid="extension-new-tab" + hidden="true" + data-lazy-l10n-id="appmenu-new-tab-controlled-changes" + data-l10n-attrs="buttonlabel, buttonaccesskey, secondarybuttonlabel, secondarybuttonaccesskey" + closebuttonhidden="true" + dropmarkerhidden="true" + buttonhighlight="true" + checkboxhidden="true"> + <popupnotificationcontent orient="vertical"> + <description id="extension-new-tab-notification-description"/> + </popupnotificationcontent> + </popupnotification> + <popupnotification id="extension-homepage-notification" + class="extension-controlled-notification" + popupid="extension-homepage" + hidden="true" + data-lazy-l10n-id="appmenu-homepage-controlled-changes" + data-l10n-attrs="buttonlabel, buttonaccesskey, secondarybuttonlabel, secondarybuttonaccesskey" + closebuttonhidden="true" + dropmarkerhidden="true" + buttonhighlight="true" + checkboxhidden="true"> + <popupnotificationcontent orient="vertical"> + <description id="extension-homepage-notification-description"/> + </popupnotificationcontent> + </popupnotification> + <popupnotification id="extension-tab-hide-notification" + class="extension-controlled-notification" + popupid="extension-tab-hide" + hidden="true" + data-lazy-l10n-id="appmenu-tab-hide-controlled" + data-l10n-attrs="buttonlabel, buttonaccesskey, secondarybuttonlabel, secondarybuttonaccesskey" + closebuttonhidden="true" + dropmarkerhidden="true" + checkboxhidden="true"> + <popupnotificationcontent orient="vertical"> + <description id="extension-tab-hide-notification-description"/> + </popupnotificationcontent> + </popupnotification> + </panel> +</html:template> diff --git a/browser/components/customizableui/content/panelUI.js b/browser/components/customizableui/content/panelUI.js new file mode 100644 index 0000000000..cf6461f9fb --- /dev/null +++ b/browser/components/customizableui/content/panelUI.js @@ -0,0 +1,1069 @@ +/* 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/. */ + +ChromeUtils.defineESModuleGetters(this, { + AppMenuNotifications: "resource://gre/modules/AppMenuNotifications.sys.mjs", + NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs", +}); +ChromeUtils.defineModuleGetter( + this, + "PanelMultiView", + "resource:///modules/PanelMultiView.jsm" +); +ChromeUtils.defineModuleGetter( + this, + "ToolbarPanelHub", + "resource://activity-stream/lib/ToolbarPanelHub.jsm" +); + +/** + * Maintains the state and dispatches events for the main menu panel. + */ + +const PanelUI = { + /** Panel events that we listen for. **/ + get kEvents() { + return ["popupshowing", "popupshown", "popuphiding", "popuphidden"]; + }, + /** + * Used for lazily getting and memoizing elements from the document. Lazy + * getters are set in init, and memoizing happens after the first retrieval. + */ + get kElements() { + return { + multiView: "appMenu-multiView", + menuButton: "PanelUI-menu-button", + panel: "appMenu-popup", + overflowFixedList: "widget-overflow-fixed-list", + overflowPanel: "widget-overflow", + navbar: "nav-bar", + }; + }, + + _initialized: false, + _notifications: null, + _notificationPanel: null, + + init(shouldSuppress) { + this._shouldSuppress = shouldSuppress; + this._initElements(); + + this.menuButton.addEventListener("mousedown", this); + this.menuButton.addEventListener("keypress", this); + + Services.obs.addObserver(this, "fullscreen-nav-toolbox"); + Services.obs.addObserver(this, "appMenu-notifications"); + Services.obs.addObserver(this, "show-update-progress"); + + XPCOMUtils.defineLazyPreferenceGetter( + this, + "autoHideToolbarInFullScreen", + "browser.fullscreen.autohide", + false, + (pref, previousValue, newValue) => { + // On OSX, or with autohide preffed off, MozDOMFullscreen is the only + // event we care about, since fullscreen should behave just like non + // fullscreen. Otherwise, we don't want to listen to these because + // we'd just be spamming ourselves with both of them whenever a user + // opened a video. + if (newValue) { + window.removeEventListener("MozDOMFullscreen:Entered", this); + window.removeEventListener("MozDOMFullscreen:Exited", this); + window.addEventListener("fullscreen", this); + } else { + window.addEventListener("MozDOMFullscreen:Entered", this); + window.addEventListener("MozDOMFullscreen:Exited", this); + window.removeEventListener("fullscreen", this); + } + + this.updateNotifications(false); + }, + autoHidePref => autoHidePref && Services.appinfo.OS !== "Darwin" + ); + + if (this.autoHideToolbarInFullScreen) { + window.addEventListener("fullscreen", this); + } else { + window.addEventListener("MozDOMFullscreen:Entered", this); + window.addEventListener("MozDOMFullscreen:Exited", this); + } + + window.addEventListener("activate", this); + CustomizableUI.addListener(this); + + // We do this sync on init because in order to have the overflow button show up + // we need to know whether anything is in the permanent panel area. + this.overflowFixedList.hidden = false; + // Also unhide the separator. We use CSS to hide/show it based on the panel's content. + this.overflowFixedList.previousElementSibling.hidden = false; + CustomizableUI.registerPanelNode( + this.overflowFixedList, + CustomizableUI.AREA_FIXED_OVERFLOW_PANEL + ); + this.updateOverflowStatus(); + + Services.obs.notifyObservers( + null, + "appMenu-notifications-request", + "refresh" + ); + + this._initialized = true; + }, + + _initElements() { + for (let [k, v] of Object.entries(this.kElements)) { + // Need to do fresh let-bindings per iteration + let getKey = k; + let id = v; + this.__defineGetter__(getKey, function() { + delete this[getKey]; + return (this[getKey] = document.getElementById(id)); + }); + } + }, + + _eventListenersAdded: false, + _ensureEventListenersAdded() { + if (this._eventListenersAdded) { + return; + } + this._addEventListeners(); + }, + + _addEventListeners() { + for (let event of this.kEvents) { + this.panel.addEventListener(event, this); + } + + PanelMultiView.getViewNode(document, "PanelUI-helpView").addEventListener( + "ViewShowing", + this._onHelpViewShow + ); + this._eventListenersAdded = true; + }, + + _removeEventListeners() { + for (let event of this.kEvents) { + this.panel.removeEventListener(event, this); + } + PanelMultiView.getViewNode( + document, + "PanelUI-helpView" + ).removeEventListener("ViewShowing", this._onHelpViewShow); + this._eventListenersAdded = false; + }, + + uninit() { + this._removeEventListeners(); + + if (this._notificationPanel) { + for (let event of this.kEvents) { + this.notificationPanel.removeEventListener(event, this); + } + } + + Services.obs.removeObserver(this, "fullscreen-nav-toolbox"); + Services.obs.removeObserver(this, "appMenu-notifications"); + Services.obs.removeObserver(this, "show-update-progress"); + + window.removeEventListener("MozDOMFullscreen:Entered", this); + window.removeEventListener("MozDOMFullscreen:Exited", this); + window.removeEventListener("fullscreen", this); + window.removeEventListener("activate", this); + this.menuButton.removeEventListener("mousedown", this); + this.menuButton.removeEventListener("keypress", this); + CustomizableUI.removeListener(this); + if (this.whatsNewPanel) { + this.whatsNewPanel.removeEventListener("ViewShowing", this); + } + }, + + /** + * Opens the menu panel if it's closed, or closes it if it's + * open. + * + * @param aEvent the event that triggers the toggle. + */ + toggle(aEvent) { + // Don't show the panel if the window is in customization mode, + // since this button doubles as an exit path for the user in this case. + if (document.documentElement.hasAttribute("customizing")) { + return; + } + this._ensureEventListenersAdded(); + if (this.panel.state == "open") { + this.hide(); + } else if (this.panel.state == "closed") { + this.show(aEvent); + } + }, + + /** + * Opens the menu panel. If the event target has a child with the + * toolbarbutton-icon attribute, the panel will be anchored on that child. + * Otherwise, the panel is anchored on the event target itself. + * + * @param aEvent the event (if any) that triggers showing the menu. + */ + show(aEvent) { + this._ensureShortcutsShown(); + (async () => { + await this.ensureReady(); + + if ( + this.panel.state == "open" || + document.documentElement.hasAttribute("customizing") + ) { + return; + } + + let domEvent = null; + if (aEvent && aEvent.type != "command") { + domEvent = aEvent; + } + + let anchor = this._getPanelAnchor(this.menuButton); + await PanelMultiView.openPopup(this.panel, anchor, { + triggerEvent: domEvent, + }); + })().catch(Cu.reportError); + }, + + /** + * If the menu panel is being shown, hide it. + */ + hide() { + if (document.documentElement.hasAttribute("customizing")) { + return; + } + + PanelMultiView.hidePopup(this.panel); + }, + + observe(subject, topic, status) { + switch (topic) { + case "fullscreen-nav-toolbox": + if (this._notifications) { + this.updateNotifications(false); + } + break; + case "appMenu-notifications": + // Don't initialize twice. + if (status == "init" && this._notifications) { + break; + } + this._notifications = AppMenuNotifications.notifications; + this.updateNotifications(true); + break; + case "show-update-progress": + openAboutDialog(); + break; + } + }, + + handleEvent(aEvent) { + // Ignore context menus and menu button menus showing and hiding: + if (aEvent.type.startsWith("popup") && aEvent.target != this.panel) { + return; + } + switch (aEvent.type) { + case "popupshowing": + updateEditUIVisibility(); + // Fall through + case "popupshown": + if (aEvent.type == "popupshown") { + CustomizableUI.addPanelCloseListeners(this.panel); + } + // Fall through + case "popuphiding": + if (aEvent.type == "popuphiding") { + updateEditUIVisibility(); + } + // Fall through + case "popuphidden": + this.updateNotifications(); + this._updatePanelButton(aEvent.target); + if (aEvent.type == "popuphidden") { + CustomizableUI.removePanelCloseListeners(this.panel); + } + break; + case "mousedown": + // On Mac, ctrl-click will send a context menu event from the widget, so + // we don't want to bring up the panel when ctrl key is pressed. + if ( + aEvent.button == 0 && + (AppConstants.platform != "macosx" || !aEvent.ctrlKey) + ) { + this.toggle(aEvent); + } + break; + case "keypress": + if (aEvent.key == " " || aEvent.key == "Enter") { + this.toggle(aEvent); + aEvent.stopPropagation(); + } + break; + case "MozDOMFullscreen:Entered": + case "MozDOMFullscreen:Exited": + case "fullscreen": + case "activate": + this.updateNotifications(); + break; + case "ViewShowing": + if (aEvent.target == this.whatsNewPanel) { + this.onWhatsNewPanelShowing(); + } + break; + } + }, + + get isReady() { + return !!this._isReady; + }, + + get isNotificationPanelOpen() { + let panelState = this.notificationPanel.state; + + return panelState == "showing" || panelState == "open"; + }, + + /** + * Registering the menu panel is done lazily for performance reasons. This + * method is exposed so that CustomizationMode can force panel-readyness in the + * event that customization mode is started before the panel has been opened + * by the user. + * + * @param aCustomizing (optional) set to true if this was called while entering + * customization mode. If that's the case, we trust that customization + * mode will handle calling beginBatchUpdate and endBatchUpdate. + * + * @return a Promise that resolves once the panel is ready to roll. + */ + async ensureReady() { + if (this._isReady) { + return; + } + + await window.delayedStartupPromise; + this._ensureEventListenersAdded(); + this.panel.hidden = false; + this._isReady = true; + }, + + /** + * Switch the panel to the help view if it's not already + * in that view. + */ + showHelpView(aAnchor) { + this._ensureEventListenersAdded(); + this.multiView.showSubView("PanelUI-helpView", aAnchor); + }, + + /** + * Switch the panel to the "More Tools" view. + * + * @param moreTools The panel showing the "More Tools" view. + */ + showMoreToolsPanel(moreTools) { + this.showSubView("appmenu-moreTools", moreTools); + + // Notify DevTools the panel view is showing and need it to populate the + // "Browser Tools" section of the panel. We notify the observer setup by + // DevTools because we want to ensure the same menuitem list is shared + // between both the AppMenu and toolbar button views. + let view = document.getElementById("appmenu-developer-tools-view"); + Services.obs.notifyObservers(view, "web-developer-tools-view-showing"); + }, + + /** + * Shows a subview in the panel with a given ID. + * + * @param aViewId the ID of the subview to show. + * @param aAnchor the element that spawned the subview. + * @param aEvent the event triggering the view showing. + */ + async showSubView(aViewId, aAnchor, aEvent) { + if (aEvent) { + // On Mac, ctrl-click will send a context menu event from the widget, so + // we don't want to bring up the panel when ctrl key is pressed. + if ( + aEvent.type == "mousedown" && + (aEvent.button != 0 || + (AppConstants.platform == "macosx" && aEvent.ctrlKey)) + ) { + return; + } + if ( + aEvent.type == "keypress" && + aEvent.key != " " && + aEvent.key != "Enter" + ) { + return; + } + } + + this._ensureEventListenersAdded(); + + let viewNode = PanelMultiView.getViewNode(document, aViewId); + if (!viewNode) { + Cu.reportError("Could not show panel subview with id: " + aViewId); + return; + } + + if (!aAnchor) { + Cu.reportError( + "Expected an anchor when opening subview with id: " + aViewId + ); + return; + } + + this.ensureWhatsNewInitialized(viewNode); + this.ensurePanicViewInitialized(viewNode); + + let container = aAnchor.closest("panelmultiview"); + if (container && !viewNode.hasAttribute("disallowSubView")) { + container.showSubView(aViewId, aAnchor); + } else if (!aAnchor.open) { + aAnchor.open = true; + + let tempPanel = document.createXULElement("panel"); + tempPanel.setAttribute("type", "arrow"); + tempPanel.setAttribute("id", "customizationui-widget-panel"); + if (viewNode.hasAttribute("neverhidden")) { + tempPanel.setAttribute("neverhidden", "true"); + } + + tempPanel.setAttribute("class", "cui-widget-panel panel-no-padding"); + tempPanel.setAttribute("viewId", aViewId); + if (aAnchor.getAttribute("tabspecific")) { + tempPanel.setAttribute("tabspecific", true); + } + if (aAnchor.getAttribute("locationspecific")) { + tempPanel.setAttribute("locationspecific", true); + } + if (this._disableAnimations) { + tempPanel.setAttribute("animate", "false"); + } + tempPanel.setAttribute("context", ""); + document + .getElementById(CustomizableUI.AREA_NAVBAR) + .appendChild(tempPanel); + + let multiView = document.createXULElement("panelmultiview"); + multiView.setAttribute("id", "customizationui-widget-multiview"); + multiView.setAttribute("viewCacheId", "appMenu-viewCache"); + multiView.setAttribute("mainViewId", viewNode.id); + multiView.appendChild(viewNode); + tempPanel.appendChild(multiView); + viewNode.classList.add("cui-widget-panelview", "PanelUI-subView"); + + let viewShown = false; + let panelRemover = () => { + viewNode.classList.remove("cui-widget-panelview"); + if (viewShown) { + CustomizableUI.removePanelCloseListeners(tempPanel); + tempPanel.removeEventListener("popuphidden", panelRemover); + } + aAnchor.open = false; + + PanelMultiView.removePopup(tempPanel); + }; + + if (aAnchor.parentNode.id == "PersonalToolbar") { + tempPanel.classList.add("bookmarks-toolbar"); + } + + let anchor = this._getPanelAnchor(aAnchor); + + if (aAnchor != anchor && aAnchor.id) { + anchor.setAttribute("consumeanchor", aAnchor.id); + } + + try { + viewShown = await PanelMultiView.openPopup(tempPanel, anchor, { + position: "bottomright topright", + triggerEvent: aEvent, + }); + } catch (ex) { + Cu.reportError(ex); + } + + if (viewShown) { + CustomizableUI.addPanelCloseListeners(tempPanel); + tempPanel.addEventListener("popuphidden", panelRemover); + } else { + panelRemover(); + } + } + }, + + /** + * Sets up the event listener for when the What's New panel is shown. + * + * @param {panelview} panelView The What's New panelview. + */ + ensureWhatsNewInitialized(panelView) { + if (panelView.id != "PanelUI-whatsNew" || panelView._initialized) { + return; + } + + if (!this.whatsNewPanel) { + this.whatsNewPanel = panelView; + } + + panelView._initialized = true; + panelView.addEventListener("ViewShowing", this); + }, + + /** + * Adds FTL before appending the panic view markup to the main DOM. + * + * @param {panelview} panelView The Panic View panelview. + */ + ensurePanicViewInitialized(panelView) { + if (panelView.id != "PanelUI-panicView" || panelView._initialized) { + return; + } + + if (!this.panic) { + this.panic = panelView; + } + + MozXULElement.insertFTLIfNeeded("browser/panicButton.ftl"); + panelView._initialized = true; + }, + + /** + * When the What's New panel is showing, we fetch the messages to show. + */ + onWhatsNewPanelShowing() { + ToolbarPanelHub.renderMessages( + window, + document, + "PanelUI-whatsNew-message-container" + ); + }, + + /** + * NB: The enable- and disableSingleSubviewPanelAnimations methods only + * affect the hiding/showing animations of single-subview panels (tempPanel + * in the showSubView method). + */ + disableSingleSubviewPanelAnimations() { + this._disableAnimations = true; + }, + + enableSingleSubviewPanelAnimations() { + this._disableAnimations = false; + }, + + updateOverflowStatus() { + let hasKids = this.overflowFixedList.hasChildNodes(); + if (hasKids && !this.navbar.hasAttribute("nonemptyoverflow")) { + this.navbar.setAttribute("nonemptyoverflow", "true"); + this.overflowPanel.setAttribute("hasfixeditems", "true"); + } else if (!hasKids && this.navbar.hasAttribute("nonemptyoverflow")) { + PanelMultiView.hidePopup(this.overflowPanel); + this.overflowPanel.removeAttribute("hasfixeditems"); + this.navbar.removeAttribute("nonemptyoverflow"); + } + }, + + onWidgetAfterDOMChange(aNode, aNextNode, aContainer, aWasRemoval) { + if (aContainer == this.overflowFixedList) { + this.updateOverflowStatus(); + } + }, + + onAreaReset(aArea, aContainer) { + if (aContainer == this.overflowFixedList) { + this.updateOverflowStatus(); + } + }, + + /** + * Sets the anchor node into the open or closed state, depending + * on the state of the panel. + */ + _updatePanelButton() { + let { state } = this.panel; + if (state == "open" || state == "showing") { + this.menuButton.open = true; + document.l10n.setAttributes( + this.menuButton, + "appmenu-menu-button-opened2" + ); + } else { + this.menuButton.open = false; + document.l10n.setAttributes( + this.menuButton, + "appmenu-menu-button-closed2" + ); + } + }, + + _onHelpViewShow(aEvent) { + // Call global menu setup function + buildHelpMenu(); + + let helpMenu = document.getElementById("menu_HelpPopup"); + let items = this.getElementsByTagName("vbox")[0]; + let attrs = [ + "command", + "oncommand", + "onclick", + "key", + "disabled", + "accesskey", + "label", + ]; + + // Remove all buttons from the view + while (items.firstChild) { + items.firstChild.remove(); + } + + // Add the current set of menuitems of the Help menu to this view + let menuItems = Array.prototype.slice.call( + helpMenu.getElementsByTagName("menuitem") + ); + let fragment = document.createDocumentFragment(); + for (let node of menuItems) { + if (node.hidden) { + continue; + } + let button = document.createXULElement("toolbarbutton"); + // Copy specific attributes from a menuitem of the Help menu + for (let attrName of attrs) { + if (!node.hasAttribute(attrName)) { + continue; + } + button.setAttribute(attrName, node.getAttribute(attrName)); + } + + // We have AppMenu-specific strings for the Help menu. By convention, + // their localization IDs are set on "appmenu-data-l10n-id" attributes. + let l10nId = node.getAttribute("appmenu-data-l10n-id"); + if (l10nId) { + button.setAttribute("data-l10n-id", l10nId); + } + + if (node.id) { + button.id = "appMenu_" + node.id; + } + + button.classList.add("subviewbutton"); + fragment.appendChild(button); + } + + // The Enterprise Support menu item has a different location than its + // placement in the menubar, so we need to specify it here. + let helpPolicySupport = fragment.querySelector( + "#appMenu_helpPolicySupport" + ); + if (helpPolicySupport) { + fragment.insertBefore( + helpPolicySupport, + fragment.querySelector("#appMenu_menu_HelpPopup_reportPhishingtoolmenu") + .nextSibling + ); + } + + items.appendChild(fragment); + }, + + _hidePopup() { + if (!this._notificationPanel) { + return; + } + + if (this.isNotificationPanelOpen) { + this.notificationPanel.hidePopup(); + } + }, + + /** + * Selects and marks an item by id from the main view. The ids are an array, + * the first in the main view and the later ids in subsequent subviews that + * become marked when the user opens the subview. The subview marking is + * cancelled if a different subview is opened. + */ + async selectAndMarkItem(itemIds) { + // This shouldn't really occur, but return early just in case. + if (document.documentElement.hasAttribute("customizing")) { + return; + } + + // This function was triggered from a button while the menu was + // already open, so the panel should be in the process of hiding. + // Wait for the panel to hide first, then reopen it. + if (this.panel.state == "hiding") { + await new Promise(resolve => { + this.panel.addEventListener("popuphidden", resolve, { once: true }); + }); + } + + if (this.panel.state != "open") { + await new Promise(resolve => { + this.panel.addEventListener("ViewShown", resolve, { once: true }); + this.show(); + }); + } + + let currentView; + + let viewShownCB = event => { + viewHidingCB(); + + if (itemIds.length) { + let subItem = window.document.getElementById(itemIds[0]); + if (event.target.id == subItem?.closest("panelview")?.id) { + Services.tm.dispatchToMainThread(() => { + markItem(event.target); + }); + } else { + itemIds = []; + } + } + }; + + let viewHidingCB = () => { + if (currentView) { + currentView.ignoreMouseMove = false; + } + currentView = null; + }; + + let popupHiddenCB = () => { + viewHidingCB(); + this.panel.removeEventListener("ViewShown", viewShownCB); + }; + + let markItem = viewNode => { + let id = itemIds.shift(); + let item = window.document.getElementById(id); + item.setAttribute("tabindex", "-1"); + + currentView = PanelView.forNode(viewNode); + currentView.selectedElement = item; + currentView.focusSelectedElement(true); + + // Prevent the mouse from changing the highlight temporarily. + // This flag gets removed when the view is hidden or a key + // is pressed. + currentView.ignoreMouseMove = true; + + if (itemIds.length) { + this.panel.addEventListener("ViewShown", viewShownCB, { once: true }); + } + this.panel.addEventListener("ViewHiding", viewHidingCB, { once: true }); + }; + + this.panel.addEventListener("popuphidden", popupHiddenCB, { once: true }); + markItem(this.mainView); + }, + + updateNotifications(notificationsChanged) { + let notifications = this._notifications; + if (!notifications || !notifications.length) { + if (notificationsChanged) { + this._clearAllNotifications(); + this._hidePopup(); + } + return; + } + + if ( + (window.fullScreen && FullScreen.navToolboxHidden) || + document.fullscreenElement || + this._shouldSuppress() + ) { + this._hidePopup(); + return; + } + + let doorhangers = notifications.filter( + n => !n.dismissed && !n.options.badgeOnly + ); + + if (this.panel.state == "showing" || this.panel.state == "open") { + // If the menu is already showing, then we need to dismiss all + // notifications since we don't want their doorhangers competing for + // attention. Don't hide the badge though; it isn't really in competition + // with anything. + doorhangers.forEach(n => { + n.dismissed = true; + if (n.options.onDismissed) { + n.options.onDismissed(window); + } + }); + this._hidePopup(); + if (!notifications[0].options.badgeOnly) { + this._showBannerItem(notifications[0]); + } + } else if (doorhangers.length) { + // Only show the doorhanger if the window is focused and not fullscreen + if ( + (window.fullScreen && this.autoHideToolbarInFullScreen) || + Services.focus.activeWindow !== window + ) { + this._hidePopup(); + this._showBadge(doorhangers[0]); + this._showBannerItem(doorhangers[0]); + } else { + this._clearBadge(); + this._showNotificationPanel(doorhangers[0]); + } + } else { + this._hidePopup(); + this._showBadge(notifications[0]); + this._showBannerItem(notifications[0]); + } + }, + + _showNotificationPanel(notification) { + this._refreshNotificationPanel(notification); + + if (this.isNotificationPanelOpen) { + return; + } + + if (notification.options.beforeShowDoorhanger) { + notification.options.beforeShowDoorhanger(document); + } + + let anchor = this._getPanelAnchor(this.menuButton); + + // Insert Fluent files when needed before notification is opened + MozXULElement.insertFTLIfNeeded("branding/brand.ftl"); + MozXULElement.insertFTLIfNeeded("browser/appMenuNotifications.ftl"); + + // After Fluent files are loaded into document replace data-lazy-l10n-ids with actual ones + document + .getElementById("appMenu-notification-popup") + .querySelectorAll("[data-lazy-l10n-id]") + .forEach(el => { + el.setAttribute("data-l10n-id", el.getAttribute("data-lazy-l10n-id")); + el.removeAttribute("data-lazy-l10n-id"); + }); + + this.notificationPanel.openPopup(anchor, "bottomright topright"); + }, + + _clearNotificationPanel() { + for (let popupnotification of this.notificationPanel.children) { + popupnotification.hidden = true; + popupnotification.notification = null; + } + }, + + _clearAllNotifications() { + this._clearNotificationPanel(); + this._clearBadge(); + this._clearBannerItem(); + }, + + get notificationPanel() { + // Lazy load the panic-button-success-notification panel the first time we need to display it. + if (!this._notificationPanel) { + let template = document.getElementById("appMenuNotificationTemplate"); + template.replaceWith(template.content); + this._notificationPanel = document.getElementById( + "appMenu-notification-popup" + ); + for (let event of this.kEvents) { + this._notificationPanel.addEventListener(event, this); + } + } + return this._notificationPanel; + }, + + get mainView() { + if (!this._mainView) { + this._mainView = PanelMultiView.getViewNode( + document, + "appMenu-protonMainView" + ); + } + return this._mainView; + }, + + get addonNotificationContainer() { + if (!this._addonNotificationContainer) { + this._addonNotificationContainer = PanelMultiView.getViewNode( + document, + "appMenu-proton-addon-banners" + ); + } + + return this._addonNotificationContainer; + }, + + _formatDescriptionMessage(n) { + let text = {}; + let array = n.options.message.split("<>"); + text.start = array[0] || ""; + text.name = n.options.name || ""; + text.end = array[1] || ""; + return text; + }, + + _refreshNotificationPanel(notification) { + this._clearNotificationPanel(); + + let popupnotificationID = this._getPopupId(notification); + let popupnotification = document.getElementById(popupnotificationID); + + popupnotification.setAttribute("id", popupnotificationID); + popupnotification.setAttribute( + "buttoncommand", + "PanelUI._onNotificationButtonEvent(event, 'buttoncommand');" + ); + popupnotification.setAttribute( + "secondarybuttoncommand", + "PanelUI._onNotificationButtonEvent(event, 'secondarybuttoncommand');" + ); + + if (notification.options.message) { + let desc = this._formatDescriptionMessage(notification); + popupnotification.setAttribute("label", desc.start); + popupnotification.setAttribute("name", desc.name); + popupnotification.setAttribute("endlabel", desc.end); + } + if (notification.options.onRefresh) { + notification.options.onRefresh(window); + } + if (notification.options.popupIconURL) { + popupnotification.setAttribute("icon", notification.options.popupIconURL); + popupnotification.setAttribute("hasicon", true); + } + + popupnotification.notification = notification; + popupnotification.show(); + }, + + _showBadge(notification) { + let badgeStatus = this._getBadgeStatus(notification); + this.menuButton.setAttribute("badge-status", badgeStatus); + }, + + // "Banner item" here refers to an item in the hamburger panel menu. They will + // typically show up as a colored row in the panel. + _showBannerItem(notification) { + const supportedIds = [ + "update-downloading", + "update-available", + "update-manual", + "update-unsupported", + "update-restart", + ]; + if (!supportedIds.includes(notification.id)) { + return; + } + + if (!this._panelBannerItem) { + this._panelBannerItem = this.mainView.querySelector(".panel-banner-item"); + } + + let l10nId = "appmenuitem-banner-" + notification.id; + document.l10n.setAttributes(this._panelBannerItem, l10nId); + + this._panelBannerItem.setAttribute("notificationid", notification.id); + this._panelBannerItem.hidden = false; + this._panelBannerItem.notification = notification; + }, + + _clearBadge() { + this.menuButton.removeAttribute("badge-status"); + }, + + _clearBannerItem() { + if (this._panelBannerItem) { + this._panelBannerItem.notification = null; + this._panelBannerItem.hidden = true; + } + }, + + _onNotificationButtonEvent(event, type) { + let notificationEl = getNotificationFromElement(event.originalTarget); + + if (!notificationEl) { + throw new Error( + "PanelUI._onNotificationButtonEvent: couldn't find notification element" + ); + } + + if (!notificationEl.notification) { + throw new Error( + "PanelUI._onNotificationButtonEvent: couldn't find notification" + ); + } + + let notification = notificationEl.notification; + + if (type == "secondarybuttoncommand") { + AppMenuNotifications.callSecondaryAction(window, notification); + } else { + AppMenuNotifications.callMainAction(window, notification, true); + } + }, + + _onBannerItemSelected(event) { + let target = event.originalTarget; + if (!target.notification) { + throw new Error( + "menucommand target has no associated action/notification" + ); + } + + event.stopPropagation(); + AppMenuNotifications.callMainAction(window, target.notification, false); + }, + + _getPopupId(notification) { + return "appMenu-" + notification.id + "-notification"; + }, + + _getBadgeStatus(notification) { + return notification.id; + }, + + _getPanelAnchor(candidate) { + let iconAnchor = candidate.badgeStack || candidate.icon; + return iconAnchor || candidate; + }, + + _ensureShortcutsShown(view = this.mainView) { + if (view.hasAttribute("added-shortcuts")) { + return; + } + view.setAttribute("added-shortcuts", "true"); + for (let button of view.querySelectorAll("toolbarbutton[key]")) { + let keyId = button.getAttribute("key"); + let key = document.getElementById(keyId); + if (!key) { + continue; + } + button.setAttribute("shortcut", ShortcutUtils.prettifyShortcut(key)); + } + }, +}; + +XPCOMUtils.defineConstant(this, "PanelUI", PanelUI); + +/** + * Gets the currently selected locale for display. + * @return the selected locale + */ +function getLocale() { + return Services.locale.appLocaleAsBCP47; +} + +/** + * Given a DOM node inside a <popupnotification>, return the parent <popupnotification>. + */ +function getNotificationFromElement(aElement) { + return aElement.closest("popupnotification"); +} |