summaryrefslogtreecommitdiffstats
path: root/browser/components/customizableui/content
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/customizableui/content')
-rw-r--r--browser/components/customizableui/content/customizeMode.inc.xul82
-rw-r--r--browser/components/customizableui/content/jar.mn10
-rw-r--r--browser/components/customizableui/content/moz.build7
-rw-r--r--browser/components/customizableui/content/panelUI.css31
-rw-r--r--browser/components/customizableui/content/panelUI.inc.xul407
-rw-r--r--browser/components/customizableui/content/panelUI.js558
-rw-r--r--browser/components/customizableui/content/panelUI.xml509
-rw-r--r--browser/components/customizableui/content/toolbar.xml618
8 files changed, 2222 insertions, 0 deletions
diff --git a/browser/components/customizableui/content/customizeMode.inc.xul b/browser/components/customizableui/content/customizeMode.inc.xul
new file mode 100644
index 000000000..b665630a2
--- /dev/null
+++ b/browser/components/customizableui/content/customizeMode.inc.xul
@@ -0,0 +1,82 @@
+<!-- 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/. -->
+
+<hbox id="customization-container" flex="1" hidden="true">
+ <vbox flex="1" id="customization-palette-container">
+ <label id="customization-header">
+ &customizeMode.menuAndToolbars.header2;
+ </label>
+ <hbox id="customization-empty" hidden="true">
+ <label>&customizeMode.menuAndToolbars.empty;</label>
+ <label onclick="BrowserOpenAddonsMgr('addons://discover/');"
+ onkeypress="BrowserOpenAddonsMgr('addons://discover/');"
+ id="customization-more-tools"
+ class="text-link">
+ &customizeMode.menuAndToolbars.emptyLink;
+ </label>
+ </hbox>
+ <vbox id="customization-palette" class="customization-palette"/>
+ <spacer id="customization-spacer"/>
+ <hbox id="customization-footer">
+#ifdef CAN_DRAW_IN_TITLEBAR
+ <button id="customization-titlebar-visibility-button" class="customizationmode-button"
+ label="&customizeMode.titlebar;" type="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.hasAttribute('checked'))"/>
+#endif
+ <button id="customization-toolbar-visibility-button" label="&customizeMode.toolbars;" class="customizationmode-button" type="menu">
+ <menupopup id="customization-toolbar-menu" onpopupshowing="onViewToolbarsPopupShowing(event)"/>
+ </button>
+ <button id="customization-lwtheme-button" label="&customizeMode.lwthemes;" class="customizationmode-button" type="menu">
+ <panel type="arrow" id="customization-lwtheme-menu"
+ onpopupshowing="gCustomizeMode.onLWThemesMenuShowing(event);"
+ position="topcenter bottomleft"
+ flip="none"
+ role="menu">
+ <label id="customization-lwtheme-menu-header" value="&customizeMode.lwthemes.myThemes;"/>
+ <label id="customization-lwtheme-menu-recommended" value="&customizeMode.lwthemes.recommended;"/>
+ <hbox id="customization-lwtheme-menu-footer">
+ <toolbarbutton class="customization-lwtheme-menu-footeritem"
+ label="&customizeMode.lwthemes.menuManage;"
+ accesskey="&customizeMode.lwthemes.menuManage.accessKey;"
+ tabindex="0"
+ oncommand="gCustomizeMode.openAddonsManagerThemes(event);"/>
+ <toolbarbutton class="customization-lwtheme-menu-footeritem"
+ label="&customizeMode.lwthemes.menuGetMore;"
+ accesskey="&customizeMode.lwthemes.menuGetMore.accessKey;"
+ tabindex="0"
+ oncommand="gCustomizeMode.getMoreThemes(event);"/>
+ </hbox>
+ </panel>
+ </button>
+
+ <spacer id="customization-footer-spacer"/>
+ <button id="customization-undo-reset-button"
+ class="customizationmode-button"
+ hidden="true"
+ oncommand="gCustomizeMode.undoReset();"
+ label="&undoCmd.label;"/>
+ <button id="customization-reset-button"
+ oncommand="gCustomizeMode.reset();"
+ label="&customizeMode.restoreDefaults;"
+ class="customizationmode-button"/>
+ </hbox>
+ </vbox>
+ <vbox id="customization-panel-container">
+ <vbox id="customization-panelWrapper">
+ <html:style html:type="text/html" scoped="scoped">
+ @import url(chrome://global/skin/popup.css);
+ </html:style>
+ <box class="panel-arrowbox">
+ <box flex="1"/>
+ <image class="panel-arrow" side="top"/>
+ </box>
+ <box class="panel-arrowcontent" side="top" flex="1">
+ <hbox id="customization-panelHolder"/>
+ <box class="panel-inner-arrowcontentfooter" hidden="true"/>
+ </box>
+ </vbox>
+ </vbox>
+</hbox>
diff --git a/browser/components/customizableui/content/jar.mn b/browser/components/customizableui/content/jar.mn
new file mode 100644
index 000000000..05c0112cd
--- /dev/null
+++ b/browser/components/customizableui/content/jar.mn
@@ -0,0 +1,10 @@
+# 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.css
+ content/browser/customizableui/panelUI.js
+ content/browser/customizableui/panelUI.xml
+ content/browser/customizableui/toolbar.xml
+
diff --git a/browser/components/customizableui/content/moz.build b/browser/components/customizableui/content/moz.build
new file mode 100644
index 000000000..eb4454d28
--- /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'] \ No newline at end of file
diff --git a/browser/components/customizableui/content/panelUI.css b/browser/components/customizableui/content/panelUI.css
new file mode 100644
index 000000000..ba44636f1
--- /dev/null
+++ b/browser/components/customizableui/content/panelUI.css
@@ -0,0 +1,31 @@
+/* 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-viewstack[viewtype="main"] > .panel-clickcapturer {
+ pointer-events: none;
+}
+
+.panel-mainview,
+.panel-viewcontainer,
+.panel-viewstack {
+ overflow: hidden;
+}
+
+.panel-viewstack {
+ position: relative;
+}
+
+.panel-subviews {
+ -moz-stack-sizing: ignore;
+ transform: translateX(0);
+ overflow-y: auto;
+}
+
+.panel-subviews[panelopen] {
+ transition: transform var(--panelui-subview-transition-duration);
+}
+
+.panel-viewcontainer[panelopen]:-moz-any(:not([viewtype="main"]),[transitioning="true"]) {
+ transition: height var(--panelui-subview-transition-duration);
+}
diff --git a/browser/components/customizableui/content/panelUI.inc.xul b/browser/components/customizableui/content/panelUI.inc.xul
new file mode 100644
index 000000000..1b8fc0236
--- /dev/null
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -0,0 +1,407 @@
+<!-- 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="PanelUI-popup"
+ role="group"
+ type="arrow"
+ hidden="true"
+ flip="slide"
+ position="bottomcenter topright"
+ noautofocus="true">
+ <panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView">
+ <panelview id="PanelUI-mainView" context="customizationPanelContextMenu">
+ <vbox id="PanelUI-contents-scroller">
+ <vbox id="PanelUI-contents" class="panelUI-grid"/>
+ </vbox>
+
+ <footer id="PanelUI-footer">
+ <toolbarbutton id="PanelUI-update-status"
+ oncommand="gMenuButtonUpdateBadge.onMenuPanelCommand(event);"
+ wrap="true"
+ hidden="true"/>
+ <hbox id="PanelUI-footer-fxa">
+ <hbox id="PanelUI-fxa-status"
+ defaultlabel="&fxaSignIn.label;"
+ signedinTooltiptext="&fxaSignedIn.tooltip;"
+ tooltiptext="&fxaSignedIn.tooltip;"
+ errorlabel="&fxaSignInError.label;"
+ unverifiedlabel="&fxaUnverified.label;"
+ onclick="if (event.which == 1) gFxAccounts.onMenuPanelCommand();">
+ <image id="PanelUI-fxa-avatar"/>
+ <toolbarbutton id="PanelUI-fxa-label"
+ fxabrandname="&syncBrand.fxAccount.label;"/>
+ </hbox>
+ <toolbarseparator/>
+ <toolbarbutton id="PanelUI-fxa-icon"
+ oncommand="gSyncUI.doSync();"
+ closemenu="none">
+ <observes element="sync-status" attribute="syncstatus"/>
+ <observes element="sync-status" attribute="tooltiptext"/>
+ </toolbarbutton>
+ </hbox>
+
+ <hbox id="PanelUI-footer-inner">
+ <toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;"
+ exitLabel="&appMenuCustomizeExit.label;"
+ tooltiptext="&appMenuCustomize.tooltip;"
+ exitTooltiptext="&appMenuCustomizeExit.tooltip;"
+ closemenu="none"
+ oncommand="gCustomizeMode.toggle();"/>
+ <toolbarseparator/>
+ <toolbarbutton id="PanelUI-help" label="&helpMenu.label;"
+ closemenu="none"
+ tooltiptext="&appMenuHelp.tooltip;"
+ oncommand="PanelUI.showHelpView(this);"/>
+ <toolbarseparator/>
+ <toolbarbutton id="PanelUI-quit"
+#ifdef XP_WIN
+ label="&quitApplicationCmdWin2.label;"
+ tooltiptext="&quitApplicationCmdWin2.tooltip;"
+#else
+#ifdef XP_MACOSX
+ label="&quitApplicationCmdMac2.label;"
+#else
+ label="&quitApplicationCmd.label;"
+#endif
+#endif
+ command="cmd_quitApplication"/>
+ </hbox>
+ </footer>
+ </panelview>
+
+ <panelview id="PanelUI-history" flex="1">
+ <label value="&appMenuHistory.label;" class="panel-subview-header"/>
+ <vbox class="panel-subview-body">
+ <toolbarbutton id="appMenuViewHistorySidebar"
+ label="&appMenuHistory.viewSidebar.label;"
+ type="checkbox"
+ class="subviewbutton"
+ key="key_gotoHistory"
+ oncommand="SidebarUI.toggle('viewHistorySidebar'); PanelUI.hide();">
+ <observes element="viewHistorySidebar" attribute="checked"/>
+ </toolbarbutton>
+ <toolbarbutton id="appMenuClearRecentHistory"
+ label="&appMenuHistory.clearRecent.label;"
+ class="subviewbutton"
+ command="Tools:Sanitize"/>
+ <toolbarbutton id="appMenuRestoreLastSession"
+ label="&appMenuHistory.restoreSession.label;"
+ class="subviewbutton"
+ command="Browser:RestoreLastSession"/>
+ <menuseparator id="PanelUI-recentlyClosedTabs-separator"/>
+ <vbox id="PanelUI-recentlyClosedTabs" tooltip="bhTooltip"/>
+ <menuseparator id="PanelUI-recentlyClosedWindows-separator"/>
+ <vbox id="PanelUI-recentlyClosedWindows" tooltip="bhTooltip"/>
+ <menuseparator id="PanelUI-historyItems-separator"/>
+ <vbox id="PanelUI-historyItems" tooltip="bhTooltip"/>
+ </vbox>
+ <toolbarbutton id="PanelUI-historyMore"
+ class="panel-subview-footer subviewbutton"
+ label="&appMenuHistory.showAll.label;"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('History'); CustomizableUI.hidePanelForNode(this);"/>
+ </panelview>
+
+ <panelview id="PanelUI-remotetabs" flex="1" class="PanelUI-subView">
+ <label value="&appMenuRemoteTabs.label;" class="panel-subview-header"/>
+ <vbox class="panel-subview-body">
+ <!-- this widget has 3 boxes in the body, but only 1 is ever visible -->
+ <!-- When Sync is ready to sync -->
+ <vbox id="PanelUI-remotetabs-main" observes="sync-syncnow-state">
+ <vbox id="PanelUI-remotetabs-buttons">
+ <toolbarbutton id="PanelUI-remotetabs-view-sidebar"
+ class="subviewbutton"
+ observes="viewTabsSidebar"
+ label="&appMenuRemoteTabs.sidebar.label;"/>
+ <toolbarbutton id="PanelUI-remotetabs-syncnow"
+ observes="sync-status"
+ class="subviewbutton"
+ oncommand="gSyncUI.doSync();"
+ closemenu="none"/>
+ <menuseparator id="PanelUI-remotetabs-separator"/>
+ </vbox>
+ <deck id="PanelUI-remotetabs-deck">
+ <!-- Sync is ready to Sync and the "tabs" engine is enabled -->
+ <vbox id="PanelUI-remotetabs-tabspane">
+ <vbox id="PanelUI-remotetabs-tabslist"
+ notabsforclientlabel="&appMenuRemoteTabs.notabs.label;"
+ />
+ </vbox>
+ <!-- Sync is ready to Sync but the "tabs" engine isn't enabled-->
+ <hbox id="PanelUI-remotetabs-tabsdisabledpane" pack="center" flex="1">
+ <vbox class="PanelUI-remotetabs-instruction-box">
+ <hbox pack="center">
+ <image class="fxaSyncIllustration" alt=""/>
+ </hbox>
+ <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.tabsnotsyncing.label;</label>
+ <hbox pack="center">
+ <toolbarbutton class="PanelUI-remotetabs-prefs-button"
+ label="&appMenuRemoteTabs.openprefs.label;"
+ oncommand="gSyncUI.openSetup(null, 'synced-tabs');"/>
+ </hbox>
+ </vbox>
+ </hbox>
+ <!-- Sync is ready to Sync but we are still fetching the tabs to show -->
+ <vbox id="PanelUI-remotetabs-fetching">
+ <!-- Show intentionally blank panel, see bug 1239845 -->
+ </vbox>
+ <!-- Sync has only 1 (ie, this) device connected -->
+ <hbox id="PanelUI-remotetabs-nodevicespane" pack="center" flex="1">
+ <vbox class="PanelUI-remotetabs-instruction-box">
+ <hbox pack="center">
+ <image class="fxaSyncIllustration" alt=""/>
+ </hbox>
+ <label class="PanelUI-remotetabs-instruction-title">&appMenuRemoteTabs.noclients.title;</label>
+ <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.noclients.subtitle;</label>
+ <!-- The inner HTML for PanelUI-remotetabs-mobile-promo is built at runtime -->
+ <label id="PanelUI-remotetabs-mobile-promo" fxAccountsBrand="&syncBrand.fxAccount.label;"/>
+ </vbox>
+ </hbox>
+ </deck>
+ </vbox>
+ <!-- a box to ensure contained boxes are centered horizonally -->
+ <hbox pack="center" flex="1">
+ <!-- When Sync is not configured -->
+ <vbox id="PanelUI-remotetabs-setupsync"
+ flex="1"
+ align="center"
+ class="PanelUI-remotetabs-instruction-box"
+ observes="sync-setup-state">
+ <image class="fxaSyncIllustration" alt=""/>
+ <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
+ <toolbarbutton class="PanelUI-remotetabs-prefs-button"
+ label="&appMenuRemoteTabs.signin.label;"
+ oncommand="gSyncUI.openSetup(null, 'synced-tabs');"/>
+ </vbox>
+ <!-- When Sync needs re-authentication. This uses the exact same messaging
+ as "Sync is not configured" but remains a separate box so we get
+ the goodness of observing broadcasters to manage the hidden states -->
+ <vbox id="PanelUI-remotetabs-reauthsync"
+ flex="1"
+ align="center"
+ class="PanelUI-remotetabs-instruction-box"
+ observes="sync-reauth-state">
+ <image class="fxaSyncIllustration" alt=""/>
+ <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
+ <toolbarbutton class="PanelUI-remotetabs-prefs-button"
+ label="&appMenuRemoteTabs.signin.label;"
+ oncommand="gSyncUI.openSetup(null, 'synced-tabs');"/>
+ </vbox>
+ </hbox>
+ </vbox>
+ </panelview>
+
+ <panelview id="PanelUI-bookmarks" flex="1" class="PanelUI-subView">
+ <label value="&bookmarksMenu.label;" class="panel-subview-header"/>
+ <vbox class="panel-subview-body">
+ <toolbarbutton id="panelMenuBookmarkThisPage"
+ class="subviewbutton"
+ observes="bookmarkThisPageBroadcaster"
+ command="Browser:AddBookmarkAs"
+ onclick="PanelUI.hide();"/>
+ <toolbarseparator/>
+ <toolbarbutton id="panelMenu_viewBookmarksSidebar"
+ label="&viewBookmarksSidebar2.label;"
+ class="subviewbutton"
+ key="viewBookmarksSidebarKb"
+ oncommand="SidebarUI.toggle('viewBookmarksSidebar'); PanelUI.hide();">
+ <observes element="viewBookmarksSidebar" attribute="checked"/>
+ </toolbarbutton>
+ <toolbarbutton id="panelMenu_viewBookmarksToolbar"
+ label="&viewBookmarksToolbar.label;"
+ type="checkbox"
+ toolbarId="PersonalToolbar"
+ class="subviewbutton"
+ oncommand="onViewToolbarCommand(event); PanelUI.hide();"/>
+ <toolbarseparator/>
+ <toolbarbutton id="panelMenu_bookmarksToolbar"
+ label="&personalbarCmd.label;"
+ class="subviewbutton cui-withicon"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('BookmarksToolbar'); PanelUI.hide();"/>
+ <toolbarbutton id="panelMenu_unsortedBookmarks"
+ label="&otherBookmarksCmd.label;"
+ class="subviewbutton cui-withicon"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks'); PanelUI.hide();"/>
+ <toolbarseparator class="small-separator"/>
+ <toolbaritem id="panelMenu_bookmarksMenu"
+ orient="vertical"
+ smoothscroll="false"
+ onclick="if (event.button == 1) BookmarkingUI.onPanelMenuViewCommand(event, this._placesView);"
+ oncommand="BookmarkingUI.onPanelMenuViewCommand(event, this._placesView);"
+ flatList="true"
+ tooltip="bhTooltip">
+ <!-- bookmarks menu items will go here -->
+ </toolbaritem>
+ </vbox>
+ <toolbarbutton id="panelMenu_showAllBookmarks"
+ label="&showAllBookmarks2.label;"
+ class="subviewbutton panel-subview-footer"
+ command="Browser:ShowAllBookmarks"
+ onclick="PanelUI.hide();"/>
+ </panelview>
+
+ <panelview id="PanelUI-socialapi" flex="1"/>
+
+ <panelview id="PanelUI-feeds" flex="1" oncommand="FeedHandler.subscribeToFeed(null, event);">
+ <label value="&feedsMenu2.label;" class="panel-subview-header"/>
+ </panelview>
+
+ <panelview id="PanelUI-containers" flex="1">
+ <label value="&containersMenu.label;" class="panel-subview-header"/>
+ <vbox id="PanelUI-containersItems"/>
+ </panelview>
+
+ <panelview id="PanelUI-helpView" flex="1" class="PanelUI-subView">
+ <label value="&helpMenu.label;" class="panel-subview-header"/>
+ <vbox id="PanelUI-helpItems" class="panel-subview-body"/>
+ </panelview>
+
+ <panelview id="PanelUI-developer" flex="1">
+ <label value="&webDeveloperMenu.label;" class="panel-subview-header"/>
+ <vbox id="PanelUI-developerItems" class="panel-subview-body"/>
+ </panelview>
+
+ <panelview id="PanelUI-sidebar" flex="1">
+ <label value="&appMenuSidebars.label;" class="panel-subview-header"/>
+ <vbox id="PanelUI-sidebarItems" class="panel-subview-body"/>
+ </panelview>
+
+ <panelview id="PanelUI-characterEncodingView" flex="1">
+ <label value="&charsetMenu2.label;" class="panel-subview-header"/>
+ <vbox class="panel-subview-body">
+ <vbox id="PanelUI-characterEncodingView-pinned"
+ class="PanelUI-characterEncodingView-list"/>
+ <toolbarseparator/>
+ <vbox id="PanelUI-characterEncodingView-charsets"
+ class="PanelUI-characterEncodingView-list"/>
+ <toolbarseparator/>
+ <vbox>
+ <label id="PanelUI-characterEncodingView-autodetect-label"/>
+ <vbox id="PanelUI-characterEncodingView-autodetect"
+ class="PanelUI-characterEncodingView-list"/>
+ </vbox>
+ </vbox>
+ </panelview>
+
+ <panelview id="PanelUI-panicView" flex="1">
+ <vbox class="panel-subview-body">
+ <hbox id="PanelUI-panic-timeframe">
+ <image id="PanelUI-panic-timeframe-icon" alt=""/>
+ <vbox flex="1">
+ <hbox id="PanelUI-panic-header">
+ <image id="PanelUI-panic-timeframe-icon-small" alt=""/>
+ <description id="PanelUI-panic-mainDesc" flex="1">&panicButton.view.mainTimeframeDesc;</description>
+ </hbox>
+ <radiogroup id="PanelUI-panic-timeSpan" aria-labelledby="PanelUI-panic-mainDesc" closemenu="none">
+ <radio id="PanelUI-panic-5min" label="&panicButton.view.5min;" selected="true"
+ value="5" class="subviewradio"/>
+ <radio id="PanelUI-panic-2hr" label="&panicButton.view.2hr;"
+ value="2" class="subviewradio"/>
+ <radio id="PanelUI-panic-day" label="&panicButton.view.day;"
+ value="6" class="subviewradio"/>
+ </radiogroup>
+ </vbox>
+ </hbox>
+ <vbox id="PanelUI-panic-explanations">
+ <label id="PanelUI-panic-actionlist-main-label">&panicButton.view.mainActionDesc;</label>
+
+ <label id="PanelUI-panic-actionlist-windows" class="PanelUI-panic-actionlist">&panicButton.view.deleteTabsAndWindows;</label>
+ <label id="PanelUI-panic-actionlist-cookies" class="PanelUI-panic-actionlist">&panicButton.view.deleteCookies;</label>
+ <label id="PanelUI-panic-actionlist-history" class="PanelUI-panic-actionlist">&panicButton.view.deleteHistory;</label>
+ <label id="PanelUI-panic-actionlist-newwindow" class="PanelUI-panic-actionlist">&panicButton.view.openNewWindow;</label>
+
+ <label id="PanelUI-panic-warning">&panicButton.view.undoWarning;</label>
+ </vbox>
+ <button id="PanelUI-panic-view-button"
+ label="&panicButton.view.forgetButton;"/>
+ </vbox>
+ </panelview>
+
+ </panelmultiview>
+ <!-- These menupopups are located here to prevent flickering,
+ see bug 492960 comment 20. -->
+ <menupopup id="customizationPanelItemContextMenu">
+ <menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode)"
+ closemenu="single"
+ class="customize-context-moveToToolbar"
+ accesskey="&customizeMenu.moveToToolbar.accesskey;"
+ label="&customizeMenu.moveToToolbar.label;"/>
+ <menuitem oncommand="gCustomizeMode.removeFromArea(document.popupNode)"
+ closemenu="single"
+ class="customize-context-removeFromPanel"
+ accesskey="&customizeMenu.removeFromMenu.accesskey;"
+ label="&customizeMenu.removeFromMenu.label;"/>
+ <menuseparator/>
+ <menuitem command="cmd_CustomizeToolbars"
+ class="viewCustomizeToolbar"
+ accesskey="&viewCustomizeToolbar.accesskey;"
+ label="&viewCustomizeToolbar.label;"/>
+ </menupopup>
+
+ <menupopup id="customizationPaletteItemContextMenu">
+ <menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode)"
+ class="customize-context-addToToolbar"
+ accesskey="&customizeMenu.addToToolbar.accesskey;"
+ label="&customizeMenu.addToToolbar.label;"/>
+ <menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"
+ class="customize-context-addToPanel"
+ accesskey="&customizeMenu.addToPanel.accesskey;"
+ label="&customizeMenu.addToPanel.label;"/>
+ </menupopup>
+
+ <menupopup id="customizationPanelContextMenu">
+ <menuitem command="cmd_CustomizeToolbars"
+ accesskey="&customizeMenu.addMoreItems.accesskey;"
+ label="&customizeMenu.addMoreItems.label;"/>
+ </menupopup>
+</panel>
+
+<panel id="widget-overflow"
+ role="group"
+ type="arrow"
+ noautofocus="true"
+ context="toolbar-context-menu"
+ position="bottomcenter topright"
+ hidden="true">
+ <vbox id="widget-overflow-scroller">
+ <vbox id="widget-overflow-list" class="widget-overflow-list"
+ overflowfortoolbar="nav-bar"/>
+ </vbox>
+</panel>
+
+<panel id="customization-tipPanel"
+ type="arrow"
+ flip="none"
+ side="left"
+ position="leftcenter topright"
+ noautohide="true"
+ hidden="true">
+ <hbox class="customization-tipPanel-wrapper">
+ <vbox class="customization-tipPanel-infoBox"/>
+ <vbox class="customization-tipPanel-content" flex="1">
+ <description class="customization-tipPanel-contentMessage"/>
+ <image class="customization-tipPanel-contentImage"/>
+ </vbox>
+ <vbox pack="start" align="end" class="customization-tipPanel-closeBox">
+ <toolbarbutton oncommand="gCustomizeMode.hideTip()" class="close-icon"/>
+ </vbox>
+ </hbox>
+</panel>
+
+<panel id="panic-button-success-notification"
+ type="arrow"
+ position="bottomcenter topright"
+ hidden="true"
+ role="alert"
+ orient="vertical">
+ <hbox id="panic-button-success-header">
+ <image id="panic-button-success-icon" alt=""/>
+ <vbox>
+ <description>&panicButton.thankyou.msg1;</description>
+ <description>&panicButton.thankyou.msg2;</description>
+ </vbox>
+ </hbox>
+ <button label="&panicButton.thankyou.buttonlabel;"
+ id="panic-button-success-closebutton"
+ oncommand="PanicButtonNotifier.close()"/>
+</panel>
diff --git a/browser/components/customizableui/content/panelUI.js b/browser/components/customizableui/content/panelUI.js
new file mode 100644
index 000000000..66fa0c184
--- /dev/null
+++ b/browser/components/customizableui/content/panelUI.js
@@ -0,0 +1,558 @@
+/* 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/. */
+
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+ "resource:///modules/CustomizableUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ScrollbarSampler",
+ "resource:///modules/ScrollbarSampler.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
+ "resource://gre/modules/ShortcutUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.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 {
+ contents: "PanelUI-contents",
+ mainView: "PanelUI-mainView",
+ multiView: "PanelUI-multiView",
+ helpView: "PanelUI-helpView",
+ menuButton: "PanelUI-menu-button",
+ panel: "PanelUI-popup",
+ scroller: "PanelUI-contents-scroller"
+ };
+ },
+
+ _initialized: false,
+ init: function() {
+ 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);
+ });
+ }
+
+ this.menuButton.addEventListener("mousedown", this);
+ this.menuButton.addEventListener("keypress", this);
+ this._overlayScrollListenerBoundFn = this._overlayScrollListener.bind(this);
+ window.matchMedia("(-moz-overlay-scrollbars)").addListener(this._overlayScrollListenerBoundFn);
+ CustomizableUI.addListener(this);
+ this._initialized = true;
+ },
+
+ _eventListenersAdded: false,
+ _ensureEventListenersAdded: function() {
+ if (this._eventListenersAdded)
+ return;
+ this._addEventListeners();
+ },
+
+ _addEventListeners: function() {
+ for (let event of this.kEvents) {
+ this.panel.addEventListener(event, this);
+ }
+
+ this.helpView.addEventListener("ViewShowing", this._onHelpViewShow, false);
+ this._eventListenersAdded = true;
+ },
+
+ uninit: function() {
+ for (let event of this.kEvents) {
+ this.panel.removeEventListener(event, this);
+ }
+ this.helpView.removeEventListener("ViewShowing", this._onHelpViewShow);
+ this.menuButton.removeEventListener("mousedown", this);
+ this.menuButton.removeEventListener("keypress", this);
+ window.matchMedia("(-moz-overlay-scrollbars)").removeListener(this._overlayScrollListenerBoundFn);
+ CustomizableUI.removeListener(this);
+ this._overlayScrollListenerBoundFn = null;
+ },
+
+ /**
+ * Customize mode extracts the mainView and puts it somewhere else while the
+ * user customizes. Upon completion, this function can be called to put the
+ * panel back to where it belongs in normal browsing mode.
+ *
+ * @param aMainView
+ * The mainView node to put back into place.
+ */
+ setMainView: function(aMainView) {
+ this._ensureEventListenersAdded();
+ this.multiView.setMainView(aMainView);
+ },
+
+ /**
+ * Opens the menu panel if it's closed, or closes it if it's
+ * open.
+ *
+ * @param aEvent the event that triggers the toggle.
+ */
+ toggle: function(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: function(aEvent) {
+ return new Promise(resolve => {
+ this.ensureReady().then(() => {
+ if (this.panel.state == "open" ||
+ document.documentElement.hasAttribute("customizing")) {
+ resolve();
+ return;
+ }
+
+ let editControlPlacement = CustomizableUI.getPlacementOfWidget("edit-controls");
+ if (editControlPlacement && editControlPlacement.area == CustomizableUI.AREA_PANEL) {
+ updateEditUIVisibility();
+ }
+
+ let personalBookmarksPlacement = CustomizableUI.getPlacementOfWidget("personal-bookmarks");
+ if (personalBookmarksPlacement &&
+ personalBookmarksPlacement.area == CustomizableUI.AREA_PANEL) {
+ PlacesToolbarHelper.customizeChange();
+ }
+
+ let anchor;
+ if (!aEvent ||
+ aEvent.type == "command") {
+ anchor = this.menuButton;
+ } else {
+ anchor = aEvent.target;
+ }
+
+ this.panel.addEventListener("popupshown", function onPopupShown() {
+ this.removeEventListener("popupshown", onPopupShown);
+ resolve();
+ });
+
+ let iconAnchor =
+ document.getAnonymousElementByAttribute(anchor, "class",
+ "toolbarbutton-icon");
+ this.panel.openPopup(iconAnchor || anchor);
+ }, (reason) => {
+ console.error("Error showing the PanelUI menu", reason);
+ });
+ });
+ },
+
+ /**
+ * If the menu panel is being shown, hide it.
+ */
+ hide: function() {
+ if (document.documentElement.hasAttribute("customizing")) {
+ return;
+ }
+
+ this.panel.hidePopup();
+ },
+
+ handleEvent: function(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":
+ this._adjustLabelsForAutoHyphens();
+ // Fall through
+ case "popupshown":
+ // Fall through
+ case "popuphiding":
+ // Fall through
+ case "popuphidden":
+ this._updatePanelButton(aEvent.target);
+ break;
+ case "mousedown":
+ if (aEvent.button == 0)
+ this.toggle(aEvent);
+ break;
+ case "keypress":
+ this.toggle(aEvent);
+ break;
+ }
+ },
+
+ get isReady() {
+ return !!this._isReady;
+ },
+
+ /**
+ * 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.
+ */
+ ensureReady: function(aCustomizing=false) {
+ if (this._readyPromise) {
+ return this._readyPromise;
+ }
+ this._readyPromise = Task.spawn(function*() {
+ if (!this._initialized) {
+ yield new Promise(resolve => {
+ let delayedStartupObserver = (aSubject, aTopic, aData) => {
+ if (aSubject == window) {
+ Services.obs.removeObserver(delayedStartupObserver, "browser-delayed-startup-finished");
+ resolve();
+ }
+ };
+ Services.obs.addObserver(delayedStartupObserver, "browser-delayed-startup-finished", false);
+ });
+ }
+
+ this.contents.setAttributeNS("http://www.w3.org/XML/1998/namespace", "lang",
+ getLocale());
+ if (!this._scrollWidth) {
+ // In order to properly center the contents of the panel, while ensuring
+ // that we have enough space on either side to show a scrollbar, we have to
+ // do a bit of hackery. In particular, we calculate a new width for the
+ // scroller, based on the system scrollbar width.
+ this._scrollWidth =
+ (yield ScrollbarSampler.getSystemScrollbarWidth()) + "px";
+ let cstyle = window.getComputedStyle(this.scroller);
+ let widthStr = cstyle.width;
+ // Get the calculated padding on the left and right sides of
+ // the scroller too. We'll use that in our final calculation so
+ // that if a scrollbar appears, we don't have the contents right
+ // up against the edge of the scroller.
+ let paddingLeft = cstyle.paddingLeft;
+ let paddingRight = cstyle.paddingRight;
+ let calcStr = [widthStr, this._scrollWidth,
+ paddingLeft, paddingRight].join(" + ");
+ this.scroller.style.width = "calc(" + calcStr + ")";
+ }
+
+ if (aCustomizing) {
+ CustomizableUI.registerMenuPanel(this.contents);
+ } else {
+ this.beginBatchUpdate();
+ try {
+ CustomizableUI.registerMenuPanel(this.contents);
+ } finally {
+ this.endBatchUpdate();
+ }
+ }
+ this._updateQuitTooltip();
+ this.panel.hidden = false;
+ this._isReady = true;
+ }.bind(this)).then(null, Cu.reportError);
+
+ return this._readyPromise;
+ },
+
+ /**
+ * Switch the panel to the main view if it's not already
+ * in that view.
+ */
+ showMainView: function() {
+ this._ensureEventListenersAdded();
+ this.multiView.showMainView();
+ },
+
+ /**
+ * Switch the panel to the help view if it's not already
+ * in that view.
+ */
+ showHelpView: function(aAnchor) {
+ this._ensureEventListenersAdded();
+ this.multiView.showSubView("PanelUI-helpView", aAnchor);
+ },
+
+ /**
+ * 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 aPlacementArea the CustomizableUI area that aAnchor is in.
+ */
+ showSubView: Task.async(function*(aViewId, aAnchor, aPlacementArea) {
+ this._ensureEventListenersAdded();
+ let viewNode = document.getElementById(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;
+ }
+
+ if (aPlacementArea == CustomizableUI.AREA_PANEL) {
+ this.multiView.showSubView(aViewId, aAnchor);
+ } else if (!aAnchor.open) {
+ aAnchor.open = true;
+
+ let tempPanel = document.createElement("panel");
+ tempPanel.setAttribute("type", "arrow");
+ tempPanel.setAttribute("id", "customizationui-widget-panel");
+ tempPanel.setAttribute("class", "cui-widget-panel");
+ tempPanel.setAttribute("viewId", aViewId);
+ if (aAnchor.getAttribute("tabspecific")) {
+ tempPanel.setAttribute("tabspecific", true);
+ }
+ if (this._disableAnimations) {
+ tempPanel.setAttribute("animate", "false");
+ }
+ tempPanel.setAttribute("context", "");
+ document.getElementById(CustomizableUI.AREA_NAVBAR).appendChild(tempPanel);
+ // If the view has a footer, set a convenience class on the panel.
+ tempPanel.classList.toggle("cui-widget-panelWithFooter",
+ viewNode.querySelector(".panel-subview-footer"));
+
+ let multiView = document.createElement("panelmultiview");
+ multiView.setAttribute("id", "customizationui-widget-multiview");
+ multiView.setAttribute("nosubviews", "true");
+ tempPanel.appendChild(multiView);
+ multiView.setAttribute("mainViewIsSubView", "true");
+ multiView.setMainView(viewNode);
+ viewNode.classList.add("cui-widget-panelview");
+
+ let viewShown = false;
+ let panelRemover = () => {
+ viewNode.classList.remove("cui-widget-panelview");
+ if (viewShown) {
+ CustomizableUI.removePanelCloseListeners(tempPanel);
+ tempPanel.removeEventListener("popuphidden", panelRemover);
+
+ let evt = new CustomEvent("ViewHiding", {detail: viewNode});
+ viewNode.dispatchEvent(evt);
+ }
+ aAnchor.open = false;
+
+ this.multiView.appendChild(viewNode);
+ tempPanel.remove();
+ };
+
+ // Emit the ViewShowing event so that the widget definition has a chance
+ // to lazily populate the subview with things.
+ let detail = {
+ blockers: new Set(),
+ addBlocker(aPromise) {
+ this.blockers.add(aPromise);
+ },
+ };
+
+ let evt = new CustomEvent("ViewShowing", { bubbles: true, cancelable: true, detail });
+ viewNode.dispatchEvent(evt);
+
+ let cancel = evt.defaultPrevented;
+ if (detail.blockers.size) {
+ try {
+ let results = yield Promise.all(detail.blockers);
+ cancel = cancel || results.some(val => val === false);
+ } catch (e) {
+ Components.utils.reportError(e);
+ cancel = true;
+ }
+ }
+
+ if (cancel) {
+ panelRemover();
+ return;
+ }
+
+ viewShown = true;
+ CustomizableUI.addPanelCloseListeners(tempPanel);
+ tempPanel.addEventListener("popuphidden", panelRemover);
+
+ let iconAnchor =
+ document.getAnonymousElementByAttribute(aAnchor, "class",
+ "toolbarbutton-icon");
+
+ if (iconAnchor && aAnchor.id) {
+ iconAnchor.setAttribute("consumeanchor", aAnchor.id);
+ }
+ tempPanel.openPopup(iconAnchor || aAnchor, "bottomcenter topright");
+ }
+ }),
+
+ /**
+ * NB: The enable- and disableSingleSubviewPanelAnimations methods only
+ * affect the hiding/showing animations of single-subview panels (tempPanel
+ * in the showSubView method).
+ */
+ disableSingleSubviewPanelAnimations: function() {
+ this._disableAnimations = true;
+ },
+
+ enableSingleSubviewPanelAnimations: function() {
+ this._disableAnimations = false;
+ },
+
+ onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer, aWasRemoval) {
+ if (aContainer != this.contents) {
+ return;
+ }
+ if (aWasRemoval) {
+ aNode.removeAttribute("auto-hyphens");
+ }
+ },
+
+ onWidgetBeforeDOMChange: function(aNode, aNextNode, aContainer, aIsRemoval) {
+ if (aContainer != this.contents) {
+ return;
+ }
+ if (!aIsRemoval &&
+ (this.panel.state == "open" ||
+ document.documentElement.hasAttribute("customizing"))) {
+ this._adjustLabelsForAutoHyphens(aNode);
+ }
+ },
+
+ /**
+ * Signal that we're about to make a lot of changes to the contents of the
+ * panels all at once. For performance, we ignore the mutations.
+ */
+ beginBatchUpdate: function() {
+ this._ensureEventListenersAdded();
+ this.multiView.ignoreMutations = true;
+ },
+
+ /**
+ * Signal that we're done making bulk changes to the panel. We now pay
+ * attention to mutations. This automatically synchronizes the multiview
+ * container with whichever view is displayed if the panel is open.
+ */
+ endBatchUpdate: function(aReason) {
+ this._ensureEventListenersAdded();
+ this.multiView.ignoreMutations = false;
+ },
+
+ _adjustLabelsForAutoHyphens: function(aNode) {
+ let toolbarButtons = aNode ? [aNode] :
+ this.contents.querySelectorAll(".toolbarbutton-1");
+ for (let node of toolbarButtons) {
+ let label = node.getAttribute("label");
+ if (!label) {
+ continue;
+ }
+ if (label.includes("\u00ad")) {
+ node.setAttribute("auto-hyphens", "off");
+ } else {
+ node.removeAttribute("auto-hyphens");
+ }
+ }
+ },
+
+ /**
+ * Sets the anchor node into the open or closed state, depending
+ * on the state of the panel.
+ */
+ _updatePanelButton: function() {
+ this.menuButton.open = this.panel.state == "open" ||
+ this.panel.state == "showing";
+ },
+
+ _onHelpViewShow: function(aEvent) {
+ // Call global menu setup function
+ buildHelpMenu();
+
+ let helpMenu = document.getElementById("menu_HelpPopup");
+ let items = this.getElementsByTagName("vbox")[0];
+ let attrs = ["oncommand", "onclick", "label", "key", "disabled"];
+ let NSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ // Remove all buttons from the view
+ while (items.firstChild) {
+ items.removeChild(items.firstChild);
+ }
+
+ // 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.createElementNS(NSXUL, "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));
+ }
+ button.setAttribute("class", "subviewbutton");
+ fragment.appendChild(button);
+ }
+ items.appendChild(fragment);
+ },
+
+ _updateQuitTooltip: function() {
+ if (AppConstants.platform == "win") {
+ return;
+ }
+
+ let tooltipId = AppConstants.platform == "macosx" ?
+ "quit-button.tooltiptext.mac" :
+ "quit-button.tooltiptext.linux2";
+
+ let brands = Services.strings.createBundle("chrome://branding/locale/brand.properties");
+ let stringArgs = [brands.GetStringFromName("brandShortName")];
+
+ let key = document.getElementById("key_quitApplication");
+ stringArgs.push(ShortcutUtils.prettifyShortcut(key));
+ let tooltipString = CustomizableUI.getLocalizedProperty({x: tooltipId}, "x", stringArgs);
+ let quitButton = document.getElementById("PanelUI-quit");
+ quitButton.setAttribute("tooltiptext", tooltipString);
+ },
+
+ _overlayScrollListenerBoundFn: null,
+ _overlayScrollListener: function(aMQL) {
+ ScrollbarSampler.resetSystemScrollbarWidth();
+ this._scrollWidth = null;
+ },
+};
+
+XPCOMUtils.defineConstant(this, "PanelUI", PanelUI);
+
+/**
+ * Gets the currently selected locale for display.
+ * @return the selected locale or "en-US" if none is selected
+ */
+function getLocale() {
+ try {
+ let chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIXULChromeRegistry);
+ return chromeRegistry.getSelectedLocale("browser");
+ } catch (ex) {
+ return "en-US";
+ }
+}
diff --git a/browser/components/customizableui/content/panelUI.xml b/browser/components/customizableui/content/panelUI.xml
new file mode 100644
index 000000000..6893bd8ff
--- /dev/null
+++ b/browser/components/customizableui/content/panelUI.xml
@@ -0,0 +1,509 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<bindings id="browserPanelUIBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="panelmultiview">
+ <resources>
+ <stylesheet src="chrome://browser/content/customizableui/panelUI.css"/>
+ </resources>
+ <content>
+ <xul:box anonid="viewContainer" class="panel-viewcontainer" xbl:inherits="panelopen,viewtype,transitioning">
+ <xul:stack anonid="viewStack" xbl:inherits="viewtype,transitioning" viewtype="main" class="panel-viewstack">
+ <xul:vbox anonid="mainViewContainer" class="panel-mainview" xbl:inherits="viewtype"/>
+
+ <!-- Used to capture click events over the PanelUI-mainView if we're in
+ subview mode. That way, any click on the PanelUI-mainView causes us
+ to revert to the mainView mode, whereupon PanelUI-click-capture then
+ allows click events to go through it. -->
+ <xul:vbox anonid="clickCapturer" class="panel-clickcapturer"/>
+
+ <!-- We manually set display: none (via a CSS attribute selector) on the
+ subviews that are not being displayed. We're using this over a deck
+ because a deck assumes the size of its largest child, regardless of
+ whether or not it is shown. That's not good for our case, since we
+ want to allow each subview to be uniquely sized. -->
+ <xul:vbox anonid="subViews" class="panel-subviews" xbl:inherits="panelopen">
+ <children includes="panelview"/>
+ </xul:vbox>
+ </xul:stack>
+ </xul:box>
+ </content>
+ <implementation implements="nsIDOMEventListener">
+ <field name="_clickCapturer" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "clickCapturer");
+ </field>
+ <field name="_viewContainer" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "viewContainer");
+ </field>
+ <field name="_mainViewContainer" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "mainViewContainer");
+ </field>
+ <field name="_subViews" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "subViews");
+ </field>
+ <field name="_viewStack" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "viewStack");
+ </field>
+ <field name="_panel" readonly="true">
+ this.parentNode;
+ </field>
+
+ <field name="_currentSubView">null</field>
+ <field name="_anchorElement">null</field>
+ <field name="_mainViewHeight">0</field>
+ <field name="_subViewObserver">null</field>
+ <field name="__transitioning">false</field>
+ <field name="_ignoreMutations">false</field>
+
+ <property name="showingSubView" readonly="true"
+ onget="return this._viewStack.getAttribute('viewtype') == 'subview'"/>
+ <property name="_mainViewId" onget="return this.getAttribute('mainViewId');" onset="this.setAttribute('mainViewId', val); return val;"/>
+ <property name="_mainView" readonly="true"
+ onget="return this._mainViewId ? document.getElementById(this._mainViewId) : null;"/>
+ <property name="showingSubViewAsMainView" readonly="true"
+ onget="return this.getAttribute('mainViewIsSubView') == 'true'"/>
+
+ <property name="ignoreMutations">
+ <getter>
+ return this._ignoreMutations;
+ </getter>
+ <setter><![CDATA[
+ this._ignoreMutations = val;
+ if (!val && this._panel.state == "open") {
+ if (this.showingSubView) {
+ this._syncContainerWithSubView();
+ } else {
+ this._syncContainerWithMainView();
+ }
+ }
+ ]]></setter>
+ </property>
+
+ <property name="_transitioning">
+ <getter>
+ return this.__transitioning;
+ </getter>
+ <setter><![CDATA[
+ this.__transitioning = val;
+ if (val) {
+ this.setAttribute("transitioning", "true");
+ } else {
+ this.removeAttribute("transitioning");
+ }
+ ]]></setter>
+ </property>
+ <constructor><![CDATA[
+ this._clickCapturer.addEventListener("click", this);
+ this._panel.addEventListener("popupshowing", this);
+ this._panel.addEventListener("popupshown", this);
+ this._panel.addEventListener("popuphidden", this);
+ this._subViews.addEventListener("overflow", this);
+ this._mainViewContainer.addEventListener("overflow", this);
+
+ // Get a MutationObserver ready to react to subview size changes. We
+ // only attach this MutationObserver when a subview is being displayed.
+ this._subViewObserver =
+ new MutationObserver(this._syncContainerWithSubView.bind(this));
+ this._mainViewObserver =
+ new MutationObserver(this._syncContainerWithMainView.bind(this));
+
+ this._mainViewContainer.setAttribute("panelid",
+ this._panel.id);
+
+ if (this._mainView) {
+ this.setMainView(this._mainView);
+ }
+ this.setAttribute("viewtype", "main");
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ if (this._mainView) {
+ this._mainView.removeAttribute("mainview");
+ }
+ this._mainViewObserver.disconnect();
+ this._subViewObserver.disconnect();
+ this._panel.removeEventListener("popupshowing", this);
+ this._panel.removeEventListener("popupshown", this);
+ this._panel.removeEventListener("popuphidden", this);
+ this._subViews.removeEventListener("overflow", this);
+ this._mainViewContainer.removeEventListener("overflow", this);
+ this._clickCapturer.removeEventListener("click", this);
+ ]]></destructor>
+
+ <method name="setMainView">
+ <parameter name="aNewMainView"/>
+ <body><![CDATA[
+ if (this._mainView) {
+ this._mainViewObserver.disconnect();
+ this._subViews.appendChild(this._mainView);
+ this._mainView.removeAttribute("mainview");
+ }
+ this._mainViewId = aNewMainView.id;
+ aNewMainView.setAttribute("mainview", "true");
+ this._mainViewContainer.appendChild(aNewMainView);
+ ]]></body>
+ </method>
+
+ <method name="showMainView">
+ <body><![CDATA[
+ if (this.showingSubView) {
+ let viewNode = this._currentSubView;
+ let evt = document.createEvent("CustomEvent");
+ evt.initCustomEvent("ViewHiding", true, true, viewNode);
+ viewNode.dispatchEvent(evt);
+
+ viewNode.removeAttribute("current");
+ this._currentSubView = null;
+
+ this._subViewObserver.disconnect();
+
+ this._setViewContainerHeight(this._mainViewHeight);
+
+ this.setAttribute("viewtype", "main");
+ }
+
+ this._shiftMainView();
+ ]]></body>
+ </method>
+
+ <method name="showSubView">
+ <parameter name="aViewId"/>
+ <parameter name="aAnchor"/>
+ <body><![CDATA[
+ Task.spawn(function*() {
+ let viewNode = this.querySelector("#" + aViewId);
+ viewNode.setAttribute("current", true);
+ // Emit the ViewShowing event so that the widget definition has a chance
+ // to lazily populate the subview with things.
+ let detail = {
+ blockers: new Set(),
+ addBlocker(aPromise) {
+ this.blockers.add(aPromise);
+ },
+ };
+
+ let evt = new CustomEvent("ViewShowing", { bubbles: true, cancelable: true, detail });
+ viewNode.dispatchEvent(evt);
+
+ let cancel = evt.defaultPrevented;
+ if (detail.blockers.size) {
+ try {
+ let results = yield Promise.all(detail.blockers);
+ cancel = cancel || results.some(val => val === false);
+ } catch (e) {
+ Components.utils.reportError(e);
+ cancel = true;
+ }
+ }
+
+ if (cancel) {
+ return;
+ }
+
+ this._currentSubView = viewNode;
+
+ // Now we have to transition the panel. There are a few parts to this:
+ //
+ // 1) The main view content gets shifted so that the center of the anchor
+ // node is at the left-most edge of the panel.
+ // 2) The subview deck slides in so that it takes up almost all of the
+ // panel.
+ // 3) If the subview is taller then the main panel contents, then the panel
+ // must grow to meet that new height. Otherwise, it must shrink.
+ //
+ // All three of these actions make use of CSS transformations, so they
+ // should all occur simultaneously.
+ this.setAttribute("viewtype", "subview");
+ this._shiftMainView(aAnchor);
+
+ this._mainViewHeight = this._viewStack.clientHeight;
+
+ let newHeight = this._heightOfSubview(viewNode, this._subViews);
+ this._setViewContainerHeight(newHeight);
+
+ this._subViewObserver.observe(viewNode, {
+ attributes: true,
+ characterData: true,
+ childList: true,
+ subtree: true
+ });
+ }.bind(this));
+ ]]></body>
+ </method>
+
+ <method name="_setViewContainerHeight">
+ <parameter name="aHeight"/>
+ <body><![CDATA[
+ let container = this._viewContainer;
+ this._transitioning = true;
+
+ let onTransitionEnd = () => {
+ container.removeEventListener("transitionend", onTransitionEnd);
+ this._transitioning = false;
+ };
+
+ container.addEventListener("transitionend", onTransitionEnd);
+ container.style.height = `${aHeight}px`;
+ ]]></body>
+ </method>
+
+ <method name="_shiftMainView">
+ <parameter name="aAnchor"/>
+ <body><![CDATA[
+ if (aAnchor) {
+ // We need to find the edge of the anchor, relative to the main panel.
+ // Then we need to add half the width of the anchor. This is the target
+ // that we need to transition to.
+ let anchorRect = aAnchor.getBoundingClientRect();
+ let mainViewRect = this._mainViewContainer.getBoundingClientRect();
+ let center = aAnchor.clientWidth / 2;
+ let direction = aAnchor.ownerDocument.defaultView.getComputedStyle(aAnchor, null).direction;
+ let edge;
+ if (direction == "ltr") {
+ edge = anchorRect.left - mainViewRect.left;
+ } else {
+ edge = mainViewRect.right - anchorRect.right;
+ }
+
+ // If the anchor is an element on the far end of the mainView we
+ // don't want to shift the mainView too far, we would reveal empty
+ // space otherwise.
+ let cstyle = window.getComputedStyle(document.documentElement, null);
+ let exitSubViewGutterWidth =
+ cstyle.getPropertyValue("--panel-ui-exit-subview-gutter-width");
+ let maxShift = mainViewRect.width - parseInt(exitSubViewGutterWidth);
+ let target = Math.min(maxShift, edge + center);
+
+ let neg = direction == "ltr" ? "-" : "";
+ this._mainViewContainer.style.transform = `translateX(${neg}${target}px)`;
+ aAnchor.setAttribute("panel-multiview-anchor", true);
+ } else {
+ this._mainViewContainer.style.transform = "";
+ if (this.anchorElement)
+ this.anchorElement.removeAttribute("panel-multiview-anchor");
+ }
+ this.anchorElement = aAnchor;
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (aEvent.type.startsWith("popup") && aEvent.target != this._panel) {
+ // Shouldn't act on e.g. context menus being shown from within the panel.
+ return;
+ }
+ switch (aEvent.type) {
+ case "click":
+ if (aEvent.originalTarget == this._clickCapturer) {
+ this.showMainView();
+ }
+ break;
+ case "overflow":
+ if (aEvent.target.localName == "vbox") {
+ // Resize the right view on the next tick.
+ if (this.showingSubView) {
+ setTimeout(this._syncContainerWithSubView.bind(this), 0);
+ } else if (!this.transitioning) {
+ setTimeout(this._syncContainerWithMainView.bind(this), 0);
+ }
+ }
+ break;
+ case "popupshowing":
+ this.setAttribute("panelopen", "true");
+ // Bug 941196 - The panel can get taller when opening a subview. Disabling
+ // autoPositioning means that the panel won't jump around if an opened
+ // subview causes the panel to exceed the dimensions of the screen in the
+ // direction that the panel originally opened in. This property resets
+ // every time the popup closes, which is why we have to set it each time.
+ this._panel.autoPosition = false;
+ this._syncContainerWithMainView();
+
+ this._mainViewObserver.observe(this._mainView, {
+ attributes: true,
+ characterData: true,
+ childList: true,
+ subtree: true
+ });
+
+ break;
+ case "popupshown":
+ this._setMaxHeight();
+ break;
+ case "popuphidden":
+ this.removeAttribute("panelopen");
+ this._mainView.style.removeProperty("height");
+ this.showMainView();
+ this._mainViewObserver.disconnect();
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_shouldSetPosition">
+ <body><![CDATA[
+ return this.getAttribute("nosubviews") == "true";
+ ]]></body>
+ </method>
+
+ <method name="_shouldSetHeight">
+ <body><![CDATA[
+ return this.getAttribute("nosubviews") != "true";
+ ]]></body>
+ </method>
+
+ <method name="_setMaxHeight">
+ <body><![CDATA[
+ if (!this._shouldSetHeight())
+ return;
+
+ // Ignore the mutation that'll fire when we set the height of
+ // the main view.
+ this.ignoreMutations = true;
+ this._mainView.style.height =
+ this.getBoundingClientRect().height + "px";
+ this.ignoreMutations = false;
+ ]]></body>
+ </method>
+ <method name="_adjustContainerHeight">
+ <body><![CDATA[
+ if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
+ let height;
+ if (this.showingSubViewAsMainView) {
+ height = this._heightOfSubview(this._mainView);
+ } else {
+ height = this._mainView.scrollHeight;
+ }
+ this._viewContainer.style.height = height + "px";
+ }
+ ]]></body>
+ </method>
+ <method name="_syncContainerWithSubView">
+ <body><![CDATA[
+ // Check that this panel is still alive:
+ if (!this._panel || !this._panel.parentNode) {
+ return;
+ }
+
+ if (!this.ignoreMutations && this.showingSubView) {
+ let newHeight = this._heightOfSubview(this._currentSubView, this._subViews);
+ this._viewContainer.style.height = newHeight + "px";
+ }
+ ]]></body>
+ </method>
+ <method name="_syncContainerWithMainView">
+ <body><![CDATA[
+ // Check that this panel is still alive:
+ if (!this._panel || !this._panel.parentNode) {
+ return;
+ }
+
+ if (this._shouldSetPosition()) {
+ this._panel.adjustArrowPosition();
+ }
+
+ if (this._shouldSetHeight()) {
+ this._adjustContainerHeight();
+ }
+ ]]></body>
+ </method>
+
+ <!-- Call this when the height of one of your views (the main view or a
+ subview) changes and you want the heights of the multiview and panel
+ to be the same as the view's height.
+ If the caller can give a hint of the expected height change with the
+ optional aExpectedChange parameter, it prevents flicker. -->
+ <method name="setHeightToFit">
+ <parameter name="aExpectedChange"/>
+ <body><![CDATA[
+ // Set the max-height to zero, wait until the height is actually
+ // updated, and then remove it. If it's not removed, weird things can
+ // happen, like widgets in the panel won't respond to clicks even
+ // though they're visible.
+ let count = 5;
+ let height = getComputedStyle(this).height;
+ if (aExpectedChange)
+ this.style.maxHeight = (parseInt(height) + aExpectedChange) + "px";
+ else
+ this.style.maxHeight = "0";
+ let interval = setInterval(() => {
+ if (height != getComputedStyle(this).height || --count == 0) {
+ clearInterval(interval);
+ this.style.removeProperty("max-height");
+ }
+ }, 0);
+ ]]></body>
+ </method>
+
+ <method name="_heightOfSubview">
+ <parameter name="aSubview"/>
+ <parameter name="aContainerToCheck"/>
+ <body><![CDATA[
+ function getFullHeight(element) {
+ // XXXgijs: unfortunately, scrollHeight rounds values, and there's no alternative
+ // that works with overflow: auto elements. Fortunately for us,
+ // we have exactly 1 (potentially) scrolling element in here (the subview body),
+ // and rounding 1 value is OK - rounding more than 1 and adding them means we get
+ // off-by-1 errors. Now we might be off by a subpixel, but we care less about that.
+ // So, use scrollHeight *only* if the element is vertically scrollable.
+ let height;
+ let elementCS;
+ if (element.scrollTopMax) {
+ height = element.scrollHeight;
+ // Bounding client rects include borders, scrollHeight doesn't:
+ elementCS = win.getComputedStyle(element);
+ height += parseFloat(elementCS.borderTopWidth) +
+ parseFloat(elementCS.borderBottomWidth);
+ } else {
+ height = element.getBoundingClientRect().height;
+ if (height > 0) {
+ elementCS = win.getComputedStyle(element);
+ }
+ }
+ if (elementCS) {
+ // Include margins - but not borders or paddings because they
+ // were dealt with above.
+ height += parseFloat(elementCS.marginTop) + parseFloat(elementCS.marginBottom);
+ }
+ return height;
+ }
+ let win = aSubview.ownerDocument.defaultView;
+ let body = aSubview.querySelector(".panel-subview-body");
+ let height = getFullHeight(body || aSubview);
+ if (body) {
+ let header = aSubview.querySelector(".panel-subview-header");
+ let footer = aSubview.querySelector(".panel-subview-footer");
+ height += (header ? getFullHeight(header) : 0) +
+ (footer ? getFullHeight(footer) : 0);
+ }
+ if (aContainerToCheck) {
+ let containerCS = win.getComputedStyle(aContainerToCheck);
+ height += parseFloat(containerCS.paddingTop) + parseFloat(containerCS.paddingBottom);
+ }
+ return Math.ceil(height);
+ ]]></body>
+ </method>
+
+ </implementation>
+ </binding>
+
+ <binding id="panelview">
+ <implementation>
+ <property name="panelMultiView" readonly="true">
+ <getter><![CDATA[
+ if (this.parentNode.localName != "panelmultiview") {
+ return document.getBindingParent(this.parentNode);
+ }
+
+ return this.parentNode;
+ ]]></getter>
+ </property>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/browser/components/customizableui/content/toolbar.xml b/browser/components/customizableui/content/toolbar.xml
new file mode 100644
index 000000000..4e6964c9f
--- /dev/null
+++ b/browser/components/customizableui/content/toolbar.xml
@@ -0,0 +1,618 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<bindings id="browserToolbarBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="toolbar" role="xul:toolbar">
+ <resources>
+ <stylesheet src="chrome://global/skin/toolbar.css"/>
+ </resources>
+ <implementation>
+ <field name="overflowedDuringConstruction">null</field>
+
+ <constructor><![CDATA[
+ let scope = {};
+ Cu.import("resource:///modules/CustomizableUI.jsm", scope);
+ // Add an early overflow event listener that will mark if the
+ // toolbar overflowed during construction.
+ if (scope.CustomizableUI.isAreaOverflowable(this.id)) {
+ this.addEventListener("overflow", this);
+ this.addEventListener("underflow", this);
+ }
+
+ if (document.readyState == "complete") {
+ this._init();
+ } else {
+ // Need to wait until XUL overlays are loaded. See bug 554279.
+ let self = this;
+ document.addEventListener("readystatechange", function onReadyStateChange() {
+ if (document.readyState != "complete")
+ return;
+ document.removeEventListener("readystatechange", onReadyStateChange, false);
+ self._init();
+ }, false);
+ }
+ ]]></constructor>
+
+ <method name="_init">
+ <body><![CDATA[
+ let scope = {};
+ Cu.import("resource:///modules/CustomizableUI.jsm", scope);
+ let CustomizableUI = scope.CustomizableUI;
+
+ // Bug 989289: Forcibly set the now unsupported "mode" and "iconsize"
+ // attributes, just in case they accidentally get restored from
+ // persistence from a user that's been upgrading and downgrading.
+ if (CustomizableUI.isBuiltinToolbar(this.id)) {
+ const kAttributes = new Map([["mode", "icons"], ["iconsize", "small"]]);
+ for (let [attribute, value] of kAttributes) {
+ if (this.getAttribute(attribute) != value) {
+ this.setAttribute(attribute, value);
+ document.persist(this.id, attribute);
+ }
+ if (this.toolbox) {
+ if (this.toolbox.getAttribute(attribute) != value) {
+ this.toolbox.setAttribute(attribute, value);
+ document.persist(this.toolbox.id, attribute);
+ }
+ }
+ }
+ }
+
+ // Searching for the toolbox palette in the toolbar binding because
+ // toolbars are constructed first.
+ let toolbox = this.toolbox;
+ if (toolbox && !toolbox.palette) {
+ for (let node of toolbox.children) {
+ if (node.localName == "toolbarpalette") {
+ // Hold on to the palette but remove it from the document.
+ toolbox.palette = node;
+ toolbox.removeChild(node);
+ break;
+ }
+ }
+ }
+
+ // pass the current set of children for comparison with placements:
+ let children = Array.from(this.childNodes)
+ .filter(node => node.getAttribute("skipintoolbarset") != "true" && node.id)
+ .map(node => node.id);
+ CustomizableUI.registerToolbarNode(this, children);
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (aEvent.type == "overflow" && aEvent.detail > 0) {
+ if (this.overflowable && this.overflowable.initialized) {
+ this.overflowable.onOverflow(aEvent);
+ } else {
+ this.overflowedDuringConstruction = aEvent;
+ }
+ } else if (aEvent.type == "underflow" && aEvent.detail > 0) {
+ this.overflowedDuringConstruction = null;
+ }
+ ]]></body>
+ </method>
+
+ <method name="insertItem">
+ <parameter name="aId"/>
+ <parameter name="aBeforeElt"/>
+ <parameter name="aWrapper"/>
+ <body><![CDATA[
+ if (aWrapper) {
+ Cu.reportError("Can't insert " + aId + ": using insertItem " +
+ "no longer supports wrapper elements.");
+ return null;
+ }
+
+ // Hack, the customizable UI code makes this be the last position
+ let pos = null;
+ if (aBeforeElt) {
+ let beforeInfo = CustomizableUI.getPlacementOfWidget(aBeforeElt.id);
+ if (beforeInfo.area != this.id) {
+ Cu.reportError("Can't insert " + aId + " before " +
+ aBeforeElt.id + " which isn't in this area (" +
+ this.id + ").");
+ return null;
+ }
+ pos = beforeInfo.position;
+ }
+
+ CustomizableUI.addWidgetToArea(aId, this.id, pos);
+ return this.ownerDocument.getElementById(aId);
+ ]]></body>
+ </method>
+
+ <property name="toolbarName"
+ onget="return this.getAttribute('toolbarname');"
+ onset="this.setAttribute('toolbarname', val); return val;"/>
+
+ <property name="customizationTarget" readonly="true">
+ <getter><![CDATA[
+ if (this._customizationTarget)
+ return this._customizationTarget;
+
+ let id = this.getAttribute("customizationtarget");
+ if (id)
+ this._customizationTarget = document.getElementById(id);
+
+ if (this._customizationTarget)
+ this._customizationTarget.insertItem = this.insertItem.bind(this);
+ else
+ this._customizationTarget = this;
+
+ return this._customizationTarget;
+ ]]></getter>
+ </property>
+
+ <property name="toolbox" readonly="true">
+ <getter><![CDATA[
+ if (this._toolbox)
+ return this._toolbox;
+
+ let toolboxId = this.getAttribute("toolboxid");
+ if (toolboxId) {
+ let toolbox = document.getElementById(toolboxId);
+ if (toolbox) {
+ if (toolbox.externalToolbars.indexOf(this) == -1)
+ toolbox.externalToolbars.push(this);
+
+ this._toolbox = toolbox;
+ }
+ }
+
+ if (!this._toolbox && this.parentNode &&
+ this.parentNode.localName == "toolbox") {
+ this._toolbox = this.parentNode;
+ }
+
+ return this._toolbox;
+ ]]></getter>
+ </property>
+
+ <property name="currentSet">
+ <getter><![CDATA[
+ let currentWidgets = new Set();
+ for (let node of this.customizationTarget.children) {
+ let realNode = node.localName == "toolbarpaletteitem" ? node.firstChild : node;
+ if (realNode.getAttribute("skipintoolbarset") != "true") {
+ currentWidgets.add(realNode.id);
+ }
+ }
+ if (this.getAttribute("overflowing") == "true") {
+ let overflowTarget = this.getAttribute("overflowtarget");
+ let overflowList = this.ownerDocument.getElementById(overflowTarget);
+ for (let node of overflowList.children) {
+ let realNode = node.localName == "toolbarpaletteitem" ? node.firstChild : node;
+ if (realNode.getAttribute("skipintoolbarset") != "true") {
+ currentWidgets.add(realNode.id);
+ }
+ }
+ }
+ let orderedPlacements = CustomizableUI.getWidgetIdsInArea(this.id);
+ return orderedPlacements.filter((x) => currentWidgets.has(x)).join(',');
+ ]]></getter>
+ <setter><![CDATA[
+ // Get list of new and old ids:
+ let newVal = (val || '').split(',').filter(x => x);
+ let oldIds = CustomizableUI.getWidgetIdsInArea(this.id);
+
+ // Get a list of items only in the new list
+ let newIds = newVal.filter(id => oldIds.indexOf(id) == -1);
+ CustomizableUI.beginBatchUpdate();
+ try {
+ for (let newId of newIds) {
+ oldIds = CustomizableUI.getWidgetIdsInArea(this.id);
+ let nextId = newId;
+ let pos;
+ do {
+ // Get the next item
+ nextId = newVal[newVal.indexOf(nextId) + 1];
+ // Figure out where it is in the old list
+ pos = oldIds.indexOf(nextId);
+ // If it's not in the old list, repeat:
+ } while (pos == -1 && nextId);
+ if (pos == -1) {
+ pos = null; // We didn't find anything, insert at the end
+ }
+ CustomizableUI.addWidgetToArea(newId, this.id, pos);
+ }
+
+ let currentIds = this.currentSet.split(',');
+ let removedIds = currentIds.filter(id => newIds.indexOf(id) == -1 && newVal.indexOf(id) == -1);
+ for (let removedId of removedIds) {
+ CustomizableUI.removeWidgetFromArea(removedId);
+ }
+ } finally {
+ CustomizableUI.endBatchUpdate();
+ }
+ ]]></setter>
+ </property>
+
+
+ </implementation>
+ </binding>
+
+ <binding id="toolbar-menubar-stub">
+ <implementation>
+ <property name="toolbox" readonly="true">
+ <getter><![CDATA[
+ if (this._toolbox)
+ return this._toolbox;
+
+ if (this.parentNode && this.parentNode.localName == "toolbox") {
+ this._toolbox = this.parentNode;
+ }
+
+ return this._toolbox;
+ ]]></getter>
+ </property>
+ <property name="currentSet" readonly="true">
+ <getter><![CDATA[
+ return this.getAttribute("defaultset");
+ ]]></getter>
+ </property>
+ <method name="insertItem">
+ <body><![CDATA[
+ return null;
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <!-- The toolbar-menubar-autohide and toolbar-drag bindings are almost
+ verbatim copies of their toolkit counterparts - they just inherit from
+ the customizableui's toolbar binding instead of toolkit's. We're currently
+ OK with the maintainance burden of having two copies of a binding, since
+ the long term goal is to move the customization framework into toolkit. -->
+
+ <binding id="toolbar-menubar-autohide"
+ extends="chrome://browser/content/customizableui/toolbar.xml#toolbar">
+ <implementation>
+ <constructor>
+ this._setInactive();
+ </constructor>
+ <destructor>
+ this._setActive();
+ </destructor>
+
+ <field name="_inactiveTimeout">null</field>
+
+ <field name="_contextMenuListener"><![CDATA[({
+ toolbar: this,
+ contextMenu: null,
+
+ get active () {
+ return !!this.contextMenu;
+ },
+
+ init: function (event) {
+ let node = event.target;
+ while (node != this.toolbar) {
+ if (node.localName == "menupopup")
+ return;
+ node = node.parentNode;
+ }
+
+ let contextMenuId = this.toolbar.getAttribute("context");
+ if (!contextMenuId)
+ return;
+
+ this.contextMenu = document.getElementById(contextMenuId);
+ if (!this.contextMenu)
+ return;
+
+ this.contextMenu.addEventListener("popupshown", this, false);
+ this.contextMenu.addEventListener("popuphiding", this, false);
+ this.toolbar.addEventListener("mousemove", this, false);
+ },
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "popupshown":
+ this.toolbar.removeEventListener("mousemove", this, false);
+ break;
+ case "popuphiding":
+ case "mousemove":
+ this.toolbar._setInactiveAsync();
+ this.toolbar.removeEventListener("mousemove", this, false);
+ this.contextMenu.removeEventListener("popuphiding", this, false);
+ this.contextMenu.removeEventListener("popupshown", this, false);
+ this.contextMenu = null;
+ break;
+ }
+ }
+ })]]></field>
+
+ <method name="_setInactive">
+ <body><![CDATA[
+ this.setAttribute("inactive", "true");
+ ]]></body>
+ </method>
+
+ <method name="_setInactiveAsync">
+ <body><![CDATA[
+ this._inactiveTimeout = setTimeout(function (self) {
+ if (self.getAttribute("autohide") == "true") {
+ self._inactiveTimeout = null;
+ self._setInactive();
+ }
+ }, 0, this);
+ ]]></body>
+ </method>
+
+ <method name="_setActive">
+ <body><![CDATA[
+ if (this._inactiveTimeout) {
+ clearTimeout(this._inactiveTimeout);
+ this._inactiveTimeout = null;
+ }
+ this.removeAttribute("inactive");
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="DOMMenuBarActive" action="this._setActive();"/>
+ <handler event="popupshowing" action="this._setActive();"/>
+ <handler event="mousedown" button="2" action="this._contextMenuListener.init(event);"/>
+ <handler event="DOMMenuBarInactive"><![CDATA[
+ if (!this._contextMenuListener.active)
+ this._setInactiveAsync();
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="toolbar-drag"
+ extends="chrome://browser/content/customizableui/toolbar.xml#toolbar">
+ <implementation>
+ <field name="_dragBindingAlive">true</field>
+ <constructor><![CDATA[
+ if (!this._draggableStarted) {
+ this._draggableStarted = true;
+ try {
+ let tmp = {};
+ Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
+ let draggableThis = new tmp.WindowDraggingElement(this);
+ draggableThis.mouseDownCheck = function(e) {
+ return this._dragBindingAlive;
+ };
+ } catch (e) {}
+ }
+ ]]></constructor>
+ </implementation>
+ </binding>
+
+
+<!-- This is a peculiar binding. It is here to deal with overlayed/inserted add-on content,
+ and immediately direct such content elsewhere. -->
+ <binding id="addonbar-delegating">
+ <implementation>
+ <constructor><![CDATA[
+ // Reading these immediately so nobody messes with them anymore:
+ this._delegatingToolbar = this.getAttribute("toolbar-delegate");
+ this._wasCollapsed = this.getAttribute("collapsed") == "true";
+ // Leaving those in here to unbreak some code:
+ if (document.readyState == "complete") {
+ this._init();
+ } else {
+ // Need to wait until XUL overlays are loaded. See bug 554279.
+ let self = this;
+ document.addEventListener("readystatechange", function onReadyStateChange() {
+ if (document.readyState != "complete")
+ return;
+ document.removeEventListener("readystatechange", onReadyStateChange, false);
+ self._init();
+ }, false);
+ }
+ ]]></constructor>
+
+ <method name="_init">
+ <body><![CDATA[
+ // Searching for the toolbox palette in the toolbar binding because
+ // toolbars are constructed first.
+ let toolbox = this.toolbox;
+ if (toolbox && !toolbox.palette) {
+ for (let node of toolbox.children) {
+ if (node.localName == "toolbarpalette") {
+ // Hold on to the palette but remove it from the document.
+ toolbox.palette = node;
+ toolbox.removeChild(node);
+ }
+ }
+ }
+
+ // pass the current set of children for comparison with placements:
+ let children = [];
+ for (let node of this.childNodes) {
+ if (node.getAttribute("skipintoolbarset") != "true" && node.id) {
+ // Force everything to be removable so that buildArea can chuck stuff
+ // out if the user has customized things / we've been here before:
+ if (!this._whiteListed.has(node.id)) {
+ node.setAttribute("removable", "true");
+ }
+ children.push(node);
+ }
+ }
+ CustomizableUI.registerToolbarNode(this, children);
+ let existingMigratedItems = (this.getAttribute("migratedset") || "").split(',');
+ for (let migratedItem of existingMigratedItems.filter((x) => !!x)) {
+ this._currentSetMigrated.add(migratedItem);
+ }
+ this.evictNodes();
+ // We can't easily use |this| or strong bindings for the observer fn here
+ // because that creates leaky circular references when the node goes away,
+ // and XBL destructors are unreliable.
+ let mutationObserver = new MutationObserver(function(mutations) {
+ if (!mutations.length) {
+ return;
+ }
+ let toolbar = mutations[0].target;
+ // Can't use our own attribute because we might not have one if we're set to
+ // collapsed
+ let areCustomizing = toolbar.ownerDocument.documentElement.getAttribute("customizing");
+ if (!toolbar._isModifying && !areCustomizing) {
+ toolbar.evictNodes();
+ }
+ });
+ mutationObserver.observe(this, {childList: true});
+ ]]></body>
+ </method>
+ <method name="evictNodes">
+ <body><![CDATA[
+ this._isModifying = true;
+ let i = this.childNodes.length;
+ while (i--) {
+ let node = this.childNodes[i];
+ if (this.childNodes[i].id) {
+ this.evictNode(this.childNodes[i]);
+ } else {
+ node.remove();
+ }
+ }
+ this._isModifying = false;
+ this._updateMigratedSet();
+ ]]></body>
+ </method>
+ <method name="evictNode">
+ <parameter name="aNode"/>
+ <body>
+ <![CDATA[
+ if (this._whiteListed.has(aNode.id) || CustomizableUI.isSpecialWidget(aNode.id)) {
+ return;
+ }
+ const kItemMaxWidth = 100;
+ let oldParent = aNode.parentNode;
+ aNode.setAttribute("removable", "true");
+ this._currentSetMigrated.add(aNode.id);
+
+ let movedOut = false;
+ if (!this._wasCollapsed) {
+ try {
+ let nodeWidth = aNode.getBoundingClientRect().width;
+ if (nodeWidth == 0 || nodeWidth > kItemMaxWidth) {
+ throw new Error(aNode.id + " is too big (" + nodeWidth +
+ "px wide), moving to the palette");
+ }
+ CustomizableUI.addWidgetToArea(aNode.id, this._delegatingToolbar);
+ movedOut = true;
+ } catch (ex) {
+ // This will throw if the node is too big, or can't be moved there for
+ // some reason. Report this:
+ Cu.reportError(ex);
+ }
+ }
+
+ /* We won't have moved the widget if either the add-on bar was collapsed,
+ * or if it was too wide to be inserted into the navbar. */
+ if (!movedOut) {
+ try {
+ CustomizableUI.removeWidgetFromArea(aNode.id);
+ } catch (ex) {
+ Cu.reportError(ex);
+ aNode.remove();
+ }
+ }
+
+ // Surprise: addWidgetToArea(palette) will get you nothing if the palette
+ // is not constructed yet. Fix:
+ if (aNode.parentNode == oldParent) {
+ let palette = this.toolbox.palette;
+ if (palette && oldParent != palette) {
+ palette.appendChild(aNode);
+ }
+ }
+ ]]></body>
+ </method>
+ <method name="insertItem">
+ <parameter name="aId"/>
+ <parameter name="aBeforeElt"/>
+ <parameter name="aWrapper"/>
+ <body><![CDATA[
+ if (aWrapper) {
+ Cu.reportError("Can't insert " + aId + ": using insertItem " +
+ "no longer supports wrapper elements.");
+ return null;
+ }
+
+ let widget = CustomizableUI.getWidget(aId);
+ widget = widget && widget.forWindow(window);
+ let node = widget && widget.node;
+ if (!node) {
+ return null;
+ }
+
+ this._isModifying = true;
+ // Temporarily add it here so it can have a width, then ditch it:
+ this.appendChild(node);
+ this.evictNode(node);
+ this._isModifying = false;
+ this._updateMigratedSet();
+ // We will now have moved stuff around; kick off some events
+ // so add-ons know we've just moved their stuff:
+ // XXXgijs: only in this window. It's hard to know for sure what's the right
+ // thing to do here - typically insertItem is used on each window, so
+ // this seems to make the most sense, even if some of the effects of
+ // evictNode might affect multiple windows.
+ CustomizableUI.dispatchToolboxEvent("customizationchange", {}, window);
+ CustomizableUI.dispatchToolboxEvent("aftercustomization", {}, window);
+ return node;
+ ]]></body>
+ </method>
+ <method name="getMigratedItems">
+ <body><![CDATA[
+ return [... this._currentSetMigrated];
+ ]]></body>
+ </method>
+ <method name="_updateMigratedSet">
+ <body><![CDATA[
+ let newMigratedItems = this.getMigratedItems().join(',');
+ if (this.getAttribute("migratedset") != newMigratedItems) {
+ this.setAttribute("migratedset", newMigratedItems);
+ this.ownerDocument.persist(this.id, "migratedset");
+ }
+ ]]></body>
+ </method>
+ <property name="customizationTarget" readonly="true">
+ <getter><![CDATA[
+ return this;
+ ]]></getter>
+ </property>
+ <property name="currentSet">
+ <getter><![CDATA[
+ return Array.from(this.children, node => node.id).join(",");
+ ]]></getter>
+ <setter><![CDATA[
+ let v = val.split(',');
+ let newButtons = v.filter(x => x && (!this._whiteListed.has(x) &&
+ !CustomizableUI.isSpecialWidget(x) &&
+ !this._currentSetMigrated.has(x)));
+ for (let newButton of newButtons) {
+ this._currentSetMigrated.add(newButton);
+ this.insertItem(newButton);
+ }
+ this._updateMigratedSet();
+ ]]></setter>
+ </property>
+ <property name="toolbox" readonly="true">
+ <getter><![CDATA[
+ if (!this._toolbox && this.parentNode &&
+ this.parentNode.localName == "toolbox") {
+ this._toolbox = this.parentNode;
+ }
+
+ return this._toolbox;
+ ]]></getter>
+ </property>
+ <field name="_whiteListed" readonly="true">new Set(["addonbar-closebutton", "status-bar"])</field>
+ <field name="_isModifying">false</field>
+ <field name="_currentSetMigrated">new Set()</field>
+ </implementation>
+ </binding>
+</bindings>