diff options
Diffstat (limited to 'browser/components/customizableui/content')
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> |