author | Gijs Kruitbosch <gijskruitbosch@gmail.com> |
Thu, 04 Jul 2013 01:23:30 +0200 | |
changeset 155514 | ca7577238ef4599cc953450fe76de4b29630561a |
parent 137355 | 85d75ed04851daf765895ff21a6a1a5426ea7098 (current diff) |
parent 155513 | 070ae719d24245b2ed306734e7d242c3f16954f8 (diff) |
child 155515 | 0f8d8d9254ca4cec819d6576c5401c4d833ae773 |
push id | 25666 |
push user | jwein@mozilla.com |
push date | Mon, 18 Nov 2013 15:56:58 +0000 |
treeherder | mozilla-central@f2adb62d07eb [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 25.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -413,21 +413,20 @@ pref("browser.tabs.warnOnCloseOtherTabs" pref("browser.tabs.warnOnOpen", true); pref("browser.tabs.maxOpenBeforeWarn", 15); pref("browser.tabs.loadInBackground", true); pref("browser.tabs.opentabfor.middleclick", true); pref("browser.tabs.loadDivertedInBackground", false); pref("browser.tabs.loadBookmarksInBackground", false); pref("browser.tabs.tabClipWidth", 140); pref("browser.tabs.animate", true); -pref("browser.tabs.onTop", true); -#ifdef XP_WIN +#ifdef UNIX_BUT_NOT_MAC +pref("browser.tabs.drawInTitlebar", false); +#else pref("browser.tabs.drawInTitlebar", true); -#else -pref("browser.tabs.drawInTitlebar", false); #endif // Where to show tab close buttons: // 0 on active tab only // 1 on all tabs until tabClipWidth is reached, then active tab only // 2 no close buttons at all // 3 at the end of the tabstrip pref("browser.tabs.closeButtons", 1); @@ -1264,8 +1263,11 @@ pref("media.webaudio.enabled", true); #endif // If this turns true, Moz*Gesture events are not called stopPropagation() // before content. pref("dom.debug.propagate_gesture_events_through_content", false); // The request URL of the GeoLocation backend. pref("geo.wifi.uri", "https://www.googleapis.com/geolocation/v1/geolocate?key=%GOOGLE_API_KEY%"); + +// CustomizableUI debug logging. +pref("browser.uiCustomization.debug", false);
--- a/browser/base/content/browser-addons.js +++ b/browser/base/content/browser-addons.js @@ -173,60 +173,16 @@ const gXPInstallObserver = { PopupNotifications.show(browser, notificationID, messageString, anchorID, action, null, options); break; } } }; -/* - * When addons are installed/uninstalled, check and see if the number of items - * on the add-on bar changed: - * - If an add-on was installed, incrementing the count, show the bar. - * - If an add-on was uninstalled, and no more items are left, hide the bar. - */ -let AddonsMgrListener = { - get addonBar() document.getElementById("addon-bar"), - get statusBar() document.getElementById("status-bar"), - getAddonBarItemCount: function() { - // Take into account the contents of the status bar shim for the count. - var itemCount = this.statusBar.childNodes.length; - - var defaultOrNoninteractive = this.addonBar.getAttribute("defaultset") - .split(",") - .concat(["separator", "spacer", "spring"]); - for (let item of this.addonBar.currentSet.split(",")) { - if (defaultOrNoninteractive.indexOf(item) == -1) - itemCount++; - } - - return itemCount; - }, - onInstalling: function(aAddon) { - this.lastAddonBarCount = this.getAddonBarItemCount(); - }, - onInstalled: function(aAddon) { - if (this.getAddonBarItemCount() > this.lastAddonBarCount) - setToolbarVisibility(this.addonBar, true); - }, - onUninstalling: function(aAddon) { - this.lastAddonBarCount = this.getAddonBarItemCount(); - }, - onUninstalled: function(aAddon) { - if (this.getAddonBarItemCount() == 0) - setToolbarVisibility(this.addonBar, false); - }, - onEnabling: function(aAddon) this.onInstalling(), - onEnabled: function(aAddon) this.onInstalled(), - onDisabling: function(aAddon) this.onUninstalling(), - onDisabled: function(aAddon) this.onUninstalled(), -}; - - var LightWeightThemeWebInstaller = { handleEvent: function (event) { switch (event.type) { case "InstallBrowserTheme": case "PreviewBrowserTheme": case "ResetBrowserThemePreview": // ignore requests from background tabs if (event.target.ownerDocument.defaultView.top != content) @@ -410,8 +366,63 @@ var LightWeightThemeWebInstaller = { return pm.testPermission(uri, "install") == pm.ALLOW_ACTION; }, _getThemeFromNode: function (node) { return this._manager.parseTheme(node.getAttribute("data-browsertheme"), node.baseURI); } } + +/* + * Listen for Lightweight Theme styling changes and update the browser's theme accordingly. + */ +let LightweightThemeListener = { + _modifiedStyles: [], + + init: function () { + XPCOMUtils.defineLazyGetter(this, "styleSheet", function() { + for (let i = document.styleSheets.length - 1; i >= 0; i--) { + let sheet = document.styleSheets[i]; + if (sheet.href == "chrome://browser/skin/browser-lightweightTheme.css") + return sheet; + } + }); + + Services.obs.addObserver(this, "lightweight-theme-styling-update", false); + if (document.documentElement.hasAttribute("lwtheme")) + this.updateStyleSheet(document.documentElement.style.backgroundImage); + }, + + uninit: function () { + Services.obs.removeObserver(this, "lightweight-theme-styling-update"); + }, + + /** + * Append the headerImage to the background-image property of all rulesets in + * browser-lightweightTheme.css. + * + * @param headerImage - a string containing a CSS image for the lightweight theme header. + */ + updateStyleSheet: function(headerImage) { + if (!this.styleSheet) + return; + for (let i = 0; i < this.styleSheet.cssRules.length; i++) { + let rule = this.styleSheet.cssRules[i]; + if (!rule.style.backgroundImage) + continue; + + if (!this._modifiedStyles[i]) + this._modifiedStyles[i] = { backgroundImage: rule.style.backgroundImage }; + + rule.style.backgroundImage = this._modifiedStyles[i].backgroundImage + ", " + headerImage; + } + }, + + // nsIObserver + observe: function (aSubject, aTopic, aData) { + if (aTopic != "lightweight-theme-styling-update" || !this.styleSheet) + return; + + let themeData = JSON.parse(aData); + this.updateStyleSheet("url(" + themeData.headerURL + ")"); + }, +};
deleted file mode 100644 --- a/browser/base/content/browser-appmenu.inc +++ /dev/null @@ -1,400 +0,0 @@ -# -*- Mode: HTML -*- -# 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/. - -<menupopup id="appmenu-popup" - onpopupshowing="if (event.target == this) { - updateEditUIVisibility(); -#ifdef MOZ_SERVICES_SYNC - gSyncUI.updateUI(); -#endif - return; - } - updateCharacterEncodingMenuState(); - if (event.target.parentNode.parentNode.parentNode.parentNode == this) - this._currentPopup = event.target;"> - <hbox> - <vbox id="appmenuPrimaryPane"> - <splitmenu id="appmenu_newTab" - label="&tabCmd.label;" - command="cmd_newNavigatorTab"> - <menupopup> - <menuitem id="appmenu_newTab_popup" - label="&tabCmd.label;" - command="cmd_newNavigatorTab" - key="key_newNavigatorTab"/> - <menuitem id="appmenu_newNavigator" - label="&newNavigatorCmd.label;" - command="cmd_newNavigator" - key="key_newNavigator"/> - <menuseparator/> - <menuitem id="appmenu_openFile" - label="&openFileCmd.label;" - command="Browser:OpenFile" - key="openFileKb"/> - </menupopup> - </splitmenu> - <menuitem id="appmenu_newPrivateWindow" - class="menuitem-iconic menuitem-iconic-tooltip" - label="&newPrivateWindow.label;" - command="Tools:PrivateBrowsing" - key="key_privatebrowsing"/> - <menuitem label="&goOfflineCmd.label;" - id="appmenu_offlineModeRecovery" - type="checkbox" - observes="workOfflineMenuitemState" - oncommand="BrowserOffline.toggleOfflineStatus();"/> - <menuseparator class="appmenu-menuseparator"/> - <hbox> - <menuitem id="appmenu-edit-label" - label="&appMenuEdit.label;" - disabled="true"/> - <toolbarbutton id="appmenu-cut" - class="appmenu-edit-button" - command="cmd_cut" - onclick="if (!this.disabled) hidePopup();" - tooltiptext="&cutButton.tooltip;"/> - <toolbarbutton id="appmenu-copy" - class="appmenu-edit-button" - command="cmd_copy" - onclick="if (!this.disabled) hidePopup();" - tooltiptext="©Button.tooltip;"/> - <toolbarbutton id="appmenu-paste" - class="appmenu-edit-button" - command="cmd_paste" - onclick="if (!this.disabled) hidePopup();" - tooltiptext="&pasteButton.tooltip;"/> - <spacer flex="1"/> - <menu id="appmenu-editmenu"> - <menupopup id="appmenu-editmenu-menupopup"> - <menuitem id="appmenu-editmenu-cut" - class="menuitem-iconic" - label="&cutCmd.label;" - key="key_cut" - command="cmd_cut"/> - <menuitem id="appmenu-editmenu-copy" - class="menuitem-iconic" - label="©Cmd.label;" - key="key_copy" - command="cmd_copy"/> - <menuitem id="appmenu-editmenu-paste" - class="menuitem-iconic" - label="&pasteCmd.label;" - key="key_paste" - command="cmd_paste"/> - <menuseparator/> - <menuitem id="appmenu-editmenu-undo" - label="&undoCmd.label;" - key="key_undo" - command="cmd_undo"/> - <menuitem id="appmenu-editmenu-redo" - label="&redoCmd.label;" - key="key_redo" - command="cmd_redo"/> - <menuseparator/> - <menuitem id="appmenu-editmenu-selectAll" - label="&selectAllCmd.label;" - key="key_selectAll" - command="cmd_selectAll"/> - <menuseparator/> - <menuitem id="appmenu-editmenu-delete" - label="&deleteCmd.label;" - key="key_delete" - command="cmd_delete"/> - </menupopup> - </menu> - </hbox> - <menuitem id="appmenu_find" - class="menuitem-tooltip" - label="&appMenuFind.label;" - command="cmd_find" - key="key_find"/> - <menuseparator class="appmenu-menuseparator"/> - <menuitem id="appmenu_savePage" - class="menuitem-tooltip" - label="&savePageCmd.label;" - command="Browser:SavePage" - key="key_savePage"/> - <menuitem id="appmenu_sendLink" - label="&emailPageCmd.label;" - command="Browser:SendLink"/> - <splitmenu id="appmenu_print" - iconic="true" - label="&printCmd.label;" - command="cmd_print"> - <menupopup> - <menuitem id="appmenu_print_popup" - class="menuitem-iconic" - label="&printCmd.label;" - command="cmd_print" - key="printKb"/> - <menuitem id="appmenu_printPreview" - label="&printPreviewCmd.label;" - command="cmd_printPreview"/> - <menuitem id="appmenu_printSetup" - label="&printSetupCmd.label;" - command="cmd_pageSetup"/> - </menupopup> - </splitmenu> - <menuseparator class="appmenu-menuseparator"/> - <splitmenu id="appmenu_webDeveloper" - command="Tools:DevToolbox" - label="&appMenuWebDeveloper.label;"> - <menupopup id="appmenu_webDeveloper_popup"> - <menuitem id="appmenu_devToolbox" - observes="devtoolsMenuBroadcaster_DevToolbox"/> - <menuseparator id="appmenu_devtools_separator"/> - <menuitem id="appmenu_devToolbar" - observes="devtoolsMenuBroadcaster_DevToolbar"/> - <menuitem id="appmenu_chromeDebugger" - observes="devtoolsMenuBroadcaster_ChromeDebugger"/> - <menuitem id="appmenu_browserConsole" - observes="devtoolsMenuBroadcaster_BrowserConsole"/> - <menuitem id="appmenu_responsiveUI" - observes="devtoolsMenuBroadcaster_ResponsiveUI"/> - <menuitem id="appmenu_scratchpad" - observes="devtoolsMenuBroadcaster_Scratchpad"/> - <menuitem id="appmenu_pageSource" - observes="devtoolsMenuBroadcaster_PageSource"/> - <menuitem id="appmenu_errorConsole" - observes="devtoolsMenuBroadcaster_ErrorConsole"/> - <menuitem id="appmenu_devtools_connect" - observes="devtoolsMenuBroadcaster_connect"/> - <menuseparator id="appmenu_devToolsEndSeparator"/> - <menuitem id="appmenu_getMoreDevtools" - observes="devtoolsMenuBroadcaster_GetMoreTools"/> - <menuseparator/> -#define ID_PREFIX appmenu_developer_ -#define OMIT_ACCESSKEYS -#include browser-charsetmenu.inc -#undef ID_PREFIX -#undef OMIT_ACCESSKEYS - <menuitem label="&goOfflineCmd.label;" - type="checkbox" - observes="workOfflineMenuitemState" - oncommand="BrowserOffline.toggleOfflineStatus();"/> - </menupopup> - </splitmenu> - <menuseparator class="appmenu-menuseparator"/> -#define ID_PREFIX appmenu_ -#define OMIT_ACCESSKEYS -#include browser-charsetmenu.inc -#undef ID_PREFIX -#undef OMIT_ACCESSKEYS - <menuitem id="appmenu_fullScreen" - class="menuitem-tooltip" - label="&fullScreenCmd.label;" - type="checkbox" - observes="View:FullScreen" - key="key_fullScreen"/> -#ifdef MOZ_SERVICES_SYNC - <!-- only one of sync-setup or sync-syncnow will be showing at once --> - <menuitem id="sync-setup-appmenu" - label="&syncSetup.label;" - observes="sync-setup-state" - oncommand="gSyncUI.openSetup()"/> - <menuitem id="sync-syncnowitem-appmenu" - label="&syncSyncNowItem.label;" - observes="sync-syncnow-state" - oncommand="gSyncUI.doSync(event);"/> -#endif - <menuitem id="appmenu-quit" - class="menuitem-iconic" -#ifdef XP_WIN - label="&quitApplicationCmdWin.label;" -#else - label="&quitApplicationCmd.label;" -#endif - command="cmd_quitApplication"/> - </vbox> - <vbox id="appmenuSecondaryPane"> - <splitmenu id="appmenu_bookmarks" - iconic="true" - label="&bookmarksMenu.label;" - command="Browser:ShowAllBookmarks"> - <menupopup id="appmenu_bookmarksPopup" - placespopup="true" - context="placesContext" - openInTabs="children" - oncommand="BookmarksEventHandler.onCommand(event, this.parentNode._placesView);" - onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);" - onpopupshowing="BookmarkingUI.onPopupShowing(event); - if (!this.parentNode._placesView) - new PlacesMenu(event, 'place:folder=BOOKMARKS_MENU');" - tooltip="bhTooltip" - popupsinherittooltip="true"> - <menuitem id="appmenu_showAllBookmarks" - label="&showAllBookmarks2.label;" - command="Browser:ShowAllBookmarks" - context="" - key="manBookmarkKb"/> - <menuseparator/> - <menuitem id="appmenu_bookmarkThisPage" - class="menuitem-iconic" - label="&bookmarkThisPageCmd.label;" - command="Browser:AddBookmarkAs" - key="addBookmarkAsKb"/> - <menuitem id="appmenu_subscribeToPage" - class="menuitem-iconic" - label="&subscribeToPageMenuitem.label;" - oncommand="return FeedHandler.subscribeToFeed(null, event);" - onclick="checkForMiddleClick(this, event);" - observes="singleFeedMenuitemState"/> - <menu id="appmenu_subscribeToPageMenu" - class="menu-iconic" - label="&subscribeToPageMenupopup.label;" - observes="multipleFeedsMenuState"> - <menupopup id="appmenu_subscribeToPageMenupopup" - onpopupshowing="return FeedHandler.buildFeedList(event.target);" - oncommand="return FeedHandler.subscribeToFeed(null, event);" - onclick="checkForMiddleClick(this, event);"/> - </menu> - <menuseparator/> - <menu id="appmenu_bookmarksToolbar" - placesanonid="toolbar-autohide" - class="menu-iconic bookmark-item" - label="&personalbarCmd.label;" - container="true"> - <menupopup id="appmenu_bookmarksToolbarPopup" - placespopup="true" - context="placesContext" - onpopupshowing="if (!this.parentNode._placesView) - new PlacesMenu(event, 'place:folder=TOOLBAR');"/> - </menu> - <menuseparator/> - <!-- Bookmarks menu items --> - <menuseparator builder="end" - class="hide-if-empty-places-result"/> - <menuitem id="appmenu_unsortedBookmarks" - label="&appMenuUnsorted.label;" - oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks');" - class="menuitem-iconic"/> - </menupopup> - </splitmenu> - <splitmenu id="appmenu_history" - iconic="true" - label="&historyMenu.label;" - command="Browser:ShowAllHistory"> - <menupopup id="appmenu_historyMenupopup" - placespopup="true" - oncommand="this.parentNode._placesView._onCommand(event);" - onclick="checkForMiddleClick(this, event);" - onpopupshowing="if (!this.parentNode._placesView) - new HistoryMenu(event);" - tooltip="bhTooltip" - popupsinherittooltip="true"> - <menuitem id="appmenu_showAllHistory" - label="&showAllHistoryCmd2.label;" - command="Browser:ShowAllHistory" - key="showAllHistoryKb"/> - <menuseparator/> - <menuitem id="appmenu_sanitizeHistory" - label="&clearRecentHistory.label;" - key="key_sanitize" - command="Tools:Sanitize"/> - <menuseparator class="hide-if-empty-places-result"/> -#ifdef MOZ_SERVICES_SYNC - <menuitem id="appmenu_sync-tabs" - class="syncTabsMenuItem" - label="&syncTabsMenu2.label;" - oncommand="BrowserOpenSyncTabs();" - disabled="true"/> -#endif - <menuitem id="appmenu_restoreLastSession" - label="&historyRestoreLastSession.label;" - command="Browser:RestoreLastSession"/> - <menu id="appmenu_recentlyClosedTabsMenu" - class="recentlyClosedTabsMenu" - label="&historyUndoMenu.label;" - disabled="true"> - <menupopup id="appmenu_recentlyClosedTabsMenupopup" - onpopupshowing="document.getElementById('appmenu_history')._placesView.populateUndoSubmenu();"/> - </menu> - <menu id="appmenu_recentlyClosedWindowsMenu" - class="recentlyClosedWindowsMenu" - label="&historyUndoWindowMenu.label;" - disabled="true"> - <menupopup id="appmenu_recentlyClosedWindowsMenupopup" - onpopupshowing="document.getElementById('appmenu_history')._placesView.populateUndoWindowSubmenu();"/> - </menu> - <menuseparator/> - </menupopup> - </splitmenu> - <menuitem id="appmenu_downloads" - class="menuitem-tooltip" - label="&downloads.label;" - command="Tools:Downloads" - key="key_openDownloads"/> - <spacer id="appmenuSecondaryPane-spacer"/> - <menuitem id="appmenu_addons" - class="menuitem-iconic menuitem-iconic-tooltip" - label="&addons.label;" - command="Tools:Addons" - key="key_openAddons"/> - <splitmenu id="appmenu_customize" -#ifdef XP_UNIX - label="&preferencesCmdUnix.label;" -#else - label="&preferencesCmd2.label;" -#endif - oncommand="openPreferences();"> - <menupopup id="appmenu_customizeMenu" - onpopupshowing="onViewToolbarsPopupShowing(event, document.getElementById('appmenu_toggleToolbarsSeparator'));"> - <menuitem id="appmenu_preferences" -#ifdef XP_UNIX - label="&preferencesCmdUnix.label;" -#else - label="&preferencesCmd2.label;" -#endif - oncommand="openPreferences();"/> - <menuseparator/> - <menuseparator id="appmenu_toggleToolbarsSeparator"/> - <menuitem id="appmenu_toggleTabsOnTop" - label="&viewTabsOnTop.label;" - type="checkbox" - command="cmd_ToggleTabsOnTop"/> - <menuitem id="appmenu_toolbarLayout" - label="&appMenuToolbarLayout.label;" - command="cmd_CustomizeToolbars"/> - </menupopup> - </splitmenu> - <splitmenu id="appmenu_help" - label="&helpMenu.label;" - oncommand="openHelpLink('firefox-help')"> - <menupopup id="appmenu_helpMenupopup"> - <menuitem id="appmenu_openHelp" - label="&helpMenu.label;" - oncommand="openHelpLink('firefox-help')" - onclick="checkForMiddleClick(this, event);"/> - <menuitem id="appmenu_gettingStarted" - label="&appMenuGettingStarted.label;" - oncommand="gBrowser.loadOneTab('https://www.mozilla.org/firefox/central/', {inBackground: false});" - onclick="checkForMiddleClick(this, event);"/> -#ifdef MOZ_SERVICES_HEALTHREPORT - <menuitem id="appmenu_healthReport" - label="&healthReport.label;" - oncommand="openHealthReport()" - onclick="checkForMiddleClick(this, event);"/> -#endif - <menuitem id="appmenu_troubleshootingInfo" - label="&helpTroubleshootingInfo.label;" - oncommand="openTroubleshootingPage()" - onclick="checkForMiddleClick(this,event);"/> - <menuitem id="appmenu_feedbackPage" - label="&helpFeedbackPage.label;" - oncommand="openFeedbackPage()" - onclick="checkForMiddleClick(this, event);"/> - <menuseparator/> - <menuitem id="appmenu_safeMode" - label="&appMenuSafeMode.label;" - oncommand="safeModeRestart();"/> - <menuseparator/> - <menuitem id="appmenu_about" - label="&aboutProduct.label;" - oncommand="openAboutDialog();"/> - </menupopup> - </splitmenu> - </vbox> - </hbox> -</menupopup>
new file mode 100644 --- /dev/null +++ b/browser/base/content/browser-customization.js @@ -0,0 +1,104 @@ +# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- +# 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/. + +/** + * Customization handler prepares this browser window for entering and exiting + * customization mode by handling customizationstarting and customizationending + * events. + */ +let CustomizationHandler = { + handleEvent: function(aEvent) { + switch(aEvent.type) { + case "customizationstarting": + this._customizationStarting(); + break; + case "customizationending": + this._customizationEnding(aEvent.detail); + break; + } + }, + + isCustomizing: function() { + return document.documentElement.hasAttribute("customizing") || + document.documentElement.hasAttribute("customize-exiting"); + }, + + _customizationStarting: function() { + // Disable the toolbar context menu items + let menubar = document.getElementById("main-menubar"); + for (let childNode of menubar.childNodes) + childNode.setAttribute("disabled", true); + + let cmd = document.getElementById("cmd_CustomizeToolbars"); + cmd.setAttribute("disabled", "true"); + + let splitter = document.getElementById("urlbar-search-splitter"); + if (splitter) { + splitter.parentNode.removeChild(splitter); + } + + CombinedStopReload.uninit(); + PlacesToolbarHelper.customizeStart(); + BookmarkingUI.customizeStart(); + DownloadsButton.customizeStart(); + }, + + _customizationEnding: function(aDetails) { + // Update global UI elements that may have been added or removed + if (aDetails.changed) { + gURLBar = document.getElementById("urlbar"); + + gProxyFavIcon = document.getElementById("page-proxy-favicon"); + gHomeButton.updateTooltip(); + gIdentityHandler._cacheElements(); + XULBrowserWindow.init(); + +#ifndef XP_MACOSX + updateEditUIVisibility(); +#endif + + // Hacky: update the PopupNotifications' object's reference to the iconBox, + // if it already exists, since it may have changed if the URL bar was + // added/removed. + if (!window.__lookupGetter__("PopupNotifications")) { + PopupNotifications.iconBox = + document.getElementById("notification-popup-box"); + } + + } + + PlacesToolbarHelper.customizeDone(); + BookmarkingUI.customizeDone(); + DownloadsButton.customizeDone(); + + // The url bar splitter state is dependent on whether stop/reload + // and the location bar are combined, so we need this ordering + CombinedStopReload.init(); + UpdateUrlbarSearchSplitterState(); + setUrlAndSearchBarWidthForConditionalForwardButton(); + + // Update the urlbar + if (gURLBar) { + URLBarSetURI(); + XULBrowserWindow.asyncUpdateUI(); + BookmarkingUI.updateStarState(); + SocialMark.updateMarkState(); + } + + // Re-enable parts of the UI we disabled during the dialog + let menubar = document.getElementById("main-menubar"); + for (let childNode of menubar.childNodes) + childNode.setAttribute("disabled", false); + let cmd = document.getElementById("cmd_CustomizeToolbars"); + cmd.removeAttribute("disabled"); + + // make sure to re-enable click-and-hold + if (!getBoolPref("ui.click_hold_context_menus", false)) { + SetClickAndHoldHandlers(); + } + + gBrowser.selectedBrowser.focus(); + } +}
--- a/browser/base/content/browser-feeds.js +++ b/browser/base/content/browser-feeds.js @@ -3,85 +3,69 @@ # 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/. /** * The Feed Handler object manages discovery of RSS/ATOM feeds in web pages * and shows UI when they are discovered. */ var FeedHandler = { - /** - * The click handler for the Feed icon in the toolbar. Opens the - * subscription page if user is not given a choice of feeds. - * (Otherwise the list of available feeds will be presented to the - * user in a popup menu.) - */ - onFeedButtonClick: function(event) { - event.stopPropagation(); - - let feeds = gBrowser.selectedBrowser.feeds || []; - // If there are multiple feeds, the menu will open, so no need to do - // anything. If there are no feeds, nothing to do either. - if (feeds.length != 1) - return; - - if (event.eventPhase == Event.AT_TARGET && - (event.button == 0 || event.button == 1)) { - this.subscribeToFeed(feeds[0].href, event); - } - }, - - /** Called when the user clicks on the Subscribe to This Page... menu item. + /** Called when the user clicks on the Subscribe to This Page... menu item, + * or when the user clicks the feed button when the page contains multiple + * feeds. * Builds a menu of unique feeds associated with the page, and if there * is only one, shows the feed inline in the browser window. - * @param menuPopup - * The feed list menupopup to be populated. - * @returns true if the menu should be shown, false if there was only + * @param container + * The feed list container (menupopup or subview) to be populated. + * @param isSubview + * Whether we're creating a subview (true) or menu (false/undefined) + * @returns true if the menu/subview should be shown, false if there was only * one feed and the feed should be shown inline in the browser - * window (do not show the menupopup). + * window (do not show the menupopup/subview). */ - buildFeedList: function(menuPopup) { + buildFeedList: function(container, isSubview) { var feeds = gBrowser.selectedBrowser.feeds; - if (feeds == null) { + if (!isSubview && feeds == null) { // XXX hack -- menu opening depends on setting of an "open" // attribute, and the menu refuses to open if that attribute is // set (because it thinks it's already open). onpopupshowing gets // called after the attribute is unset, and it doesn't get unset // if we return false. so we unset it here; otherwise, the menu // refuses to work past this point. - menuPopup.parentNode.removeAttribute("open"); + container.parentNode.removeAttribute("open"); return false; } - while (menuPopup.firstChild) - menuPopup.removeChild(menuPopup.firstChild); + while (container.firstChild) + container.removeChild(container.firstChild); - if (feeds.length <= 1) + if (!feeds || feeds.length <= 1) return false; // Build the menu showing the available feed choices for viewing. + var itemNodeType = isSubview ? "toolbarbutton" : "menuitem"; for (let feedInfo of feeds) { - var menuItem = document.createElement("menuitem"); + var item = document.createElement(itemNodeType); var baseTitle = feedInfo.title || feedInfo.href; var labelStr = gNavigatorBundle.getFormattedString("feedShowFeedNew", [baseTitle]); - menuItem.setAttribute("class", "feed-menuitem"); - menuItem.setAttribute("label", labelStr); - menuItem.setAttribute("feed", feedInfo.href); - menuItem.setAttribute("tooltiptext", feedInfo.href); - menuItem.setAttribute("crop", "center"); - menuPopup.appendChild(menuItem); + item.setAttribute("class", "feed-" + itemNodeType); + item.setAttribute("label", labelStr); + item.setAttribute("feed", feedInfo.href); + item.setAttribute("tooltiptext", feedInfo.href); + item.setAttribute("crop", "center"); + container.appendChild(item); } return true; }, /** * Subscribe to a given feed. Called when * 1. Page has a single feed and user clicks feed icon in location bar * 2. Page has a single feed and user selects Subscribe menu item - * 3. Page has multiple feeds and user selects from feed icon popup + * 3. Page has multiple feeds and user selects from feed icon popup (or subview) * 4. Page has multiple feeds and user selects from Subscribe submenu * @param href * The feed to subscribe to. May be null, in which case the * event target's feed attribute is examined. * @param event * The event this method is handling. Used to decide where * to open the preview UI. (Optional, unless href is null) */
--- a/browser/base/content/browser-fullScreen.js +++ b/browser/base/content/browser-fullScreen.js @@ -12,17 +12,17 @@ var FullScreen = { toggle: function (event) { var enterFS = window.fullScreen; // We get the fullscreen event _before_ the window transitions into or out of FS mode. if (event && event.type == "fullscreen") enterFS = !enterFS; // Toggle the View:FullScreen command, which controls elements like the - // fullscreen menuitem, menubars, and the appmenu. + // fullscreen menuitem, and menubars. let fullscreenCommand = document.getElementById("View:FullScreen"); if (enterFS) { fullscreenCommand.setAttribute("checked", enterFS); } else { fullscreenCommand.removeAttribute("checked"); } #ifdef XP_MACOSX @@ -512,50 +512,31 @@ var FullScreen = { showXULChrome: function(aTag, aShow) { var els = document.getElementsByTagNameNS(this._XULNS, aTag); for (let el of els) { // XXX don't interfere with previously collapsed toolbars if (el.getAttribute("fullscreentoolbar") == "true") { if (!aShow) { - - var toolbarMode = el.getAttribute("mode"); - if (toolbarMode != "text") { - el.setAttribute("saved-mode", toolbarMode); - el.setAttribute("saved-iconsize", el.getAttribute("iconsize")); - el.setAttribute("mode", "icons"); - el.setAttribute("iconsize", "small"); - } - // Give the main nav bar and the tab bar the fullscreen context menu, // otherwise remove context menu to prevent breakage el.setAttribute("saved-context", el.getAttribute("context")); if (el.id == "nav-bar" || el.id == "TabsToolbar") el.setAttribute("context", "autohide-context"); else el.removeAttribute("context"); // Set the inFullscreen attribute to allow specific styling // in fullscreen mode el.setAttribute("inFullscreen", true); } else { - var restoreAttr = function restoreAttr(attrName) { - var savedAttr = "saved-" + attrName; - if (el.hasAttribute(savedAttr)) { - el.setAttribute(attrName, el.getAttribute(savedAttr)); - el.removeAttribute(savedAttr); - } - } - - restoreAttr("mode"); - restoreAttr("iconsize"); - restoreAttr("context"); - + el.setAttribute("context", el.getAttribute("saved-context")); + el.removeAttribute("saved-context"); el.removeAttribute("inFullscreen"); } } else { // use moz-collapsed so it doesn't persist hidden/collapsed, // so that new windows don't have missing toolbars if (aShow) el.removeAttribute("moz-collapsed"); else @@ -566,21 +547,19 @@ var FullScreen = { if (aShow) { gNavToolbox.removeAttribute("inFullscreen"); document.documentElement.removeAttribute("inFullscreen"); } else { gNavToolbox.setAttribute("inFullscreen", true); document.documentElement.setAttribute("inFullscreen", true); } - // In tabs-on-top mode, move window controls to the tab bar, - // and in tabs-on-bottom mode, move them back to the navigation toolbar. var fullscreenctls = document.getElementById("window-controls"); var navbar = document.getElementById("nav-bar"); - var ctlsOnTabbar = window.toolbar.visible && (navbar.collapsed || TabsOnTop.enabled); + var ctlsOnTabbar = window.toolbar.visible; if (fullscreenctls.parentNode == navbar && ctlsOnTabbar) { fullscreenctls.removeAttribute("flex"); document.getElementById("TabsToolbar").appendChild(fullscreenctls); } else if (fullscreenctls.parentNode.id == "TabsToolbar" && !ctlsOnTabbar) { fullscreenctls.setAttribute("flex", "1"); navbar.appendChild(fullscreenctls); }
--- a/browser/base/content/browser-fullZoom.js +++ b/browser/base/content/browser-fullZoom.js @@ -385,16 +385,17 @@ var FullZoom = { }); }, /** * Saves the zoom level of the page in the current browser to the content * prefs store. */ _applyZoomToPref: function FullZoom__applyZoomToPref() { + Services.obs.notifyObservers(null, "browser-fullZoom:zoomChange", ""); if (!this.siteSpecific || gInPrintPreviewMode || content.document.mozSyntheticDocument) return; this._cps2.set(gBrowser.currentURI.spec, this.name, ZoomManager.zoom, this._loadContextFromWindow(gBrowser.contentWindow), { handleCompletion: function () { @@ -402,26 +403,28 @@ var FullZoom = { }.bind(this), }); }, /** * Removes from the content prefs store the zoom level of the current browser. */ _removePref: function FullZoom__removePref() { + Services.obs.notifyObservers(null, "browser-fullZoom:zoomReset", ""); if (content.document.mozSyntheticDocument) return; let ctxt = this._loadContextFromWindow(gBrowser.contentWindow); this._cps2.removeByDomainAndName(gBrowser.currentURI.spec, this.name, ctxt, { handleCompletion: function () { this._isNextContentPrefChangeInternal = true; }.bind(this), }); }, + //**************************************************************************// // Utilities /** * Returns the current FullZoom "state". Asynchronous operations that change * the zoom should use this method to capture the state before starting and * use _isStateCurrent to determine if it's still current when done. If the * captured state is no longer current, then the zoom should not be changed.
--- a/browser/base/content/browser-menubar.inc +++ b/browser/base/content/browser-menubar.inc @@ -180,21 +180,16 @@ accesskey="&viewMenu.accesskey;"> <menupopup id="menu_viewPopup" onpopupshowing="updateCharacterEncodingMenuState();"> <menu id="viewToolbarsMenu" label="&viewToolbarsMenu.label;" accesskey="&viewToolbarsMenu.accesskey;"> <menupopup onpopupshowing="onViewToolbarsPopupShowing(event);"> <menuseparator/> - <menuitem id="menu_tabsOnTop" - command="cmd_ToggleTabsOnTop" - type="checkbox" - label="&viewTabsOnTop.label;" - accesskey="&viewTabsOnTop.accesskey;"/> <menuitem id="menu_customizeToolbars" label="&viewCustomizeToolbar.label;" accesskey="&viewCustomizeToolbar.accesskey;" command="cmd_CustomizeToolbars"/> </menupopup> </menu> <menu id="viewSidebarMenuMenu" label="&viewSidebarMenu.label;"
--- a/browser/base/content/browser-places.js +++ b/browser/base/content/browser-places.js @@ -129,19 +129,16 @@ var StarUI = { this._doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition); return; } this._overlayLoading = true; document.loadOverlay( "chrome://browser/content/places/editBookmarkOverlay.xul", (function (aSubject, aTopic, aData) { - //XXX We just caused localstore.rdf to be re-applied (bug 640158) - retrieveToolbarIconsizesFromTheme(); - // Move the header (star, title, button) into the grid, // so that it aligns nicely with the other items (bug 484022). let header = this._element("editBookmarkPanelHeader"); let rows = this._element("editBookmarkPanelGrid").lastChild; rows.insertBefore(header, rows.firstChild); header.hidden = false; this._overlayLoading = false; @@ -989,52 +986,55 @@ let PlacesToolbarHelper = { this.init(); } }; //////////////////////////////////////////////////////////////////////////////// //// BookmarkingUI /** - * Handles the bookmarks star button in the URL bar, as well as the bookmark - * menu button. + * Handles the bookmarks menu-button in the toolbar. */ let BookmarkingUI = { get button() { if (!this._button) { this._button = document.getElementById("bookmarks-menu-button"); } return this._button; }, get star() { - if (!this._star) { - this._star = document.getElementById("star-button"); + if (!this._star && this.button) { + this._star = document.getAnonymousElementByAttribute(this.button, + "anonid", + "button"); } return this._star; }, get anchor() { - if (this.star && isElementVisible(this.star)) { + if (!this._anchor && this.star && isElementVisible(this.star)) { // Anchor to the icon, so the panel looks more natural. - return this.star; + this._anchor = document.getAnonymousElementByAttribute(this.star, + "class", + "toolbarbutton-icon"); } - return null; + return this._anchor; }, STATUS_UPDATING: -1, STATUS_UNSTARRED: 0, STATUS_STARRED: 1, get status() { if (this._pendingStmt) return this.STATUS_UPDATING; - return this.star && - this.star.hasAttribute("starred") ? this.STATUS_STARRED - : this.STATUS_UNSTARRED; + return this.button && + this.button.hasAttribute("starred") ? this.STATUS_STARRED + : this.STATUS_UNSTARRED; }, get _starredTooltip() { delete this._starredTooltip; return this._starredTooltip = gNavigatorBundle.getString("starButtonOn.tooltip"); }, @@ -1093,21 +1093,22 @@ let BookmarkingUI = { */ onPageProxyStateChanged: function BUI_onPageProxyStateChanged(aState) { if (!this.star) { return; } if (aState == "invalid") { this.star.setAttribute("disabled", "true"); - this.star.removeAttribute("starred"); + this.button.removeAttribute("starred"); } else { this.star.removeAttribute("disabled"); } + this._updateToolbarStyle(); }, _updateToolbarStyle: function BUI__updateToolbarStyle() { if (!this.button) { return; } let personalToolbar = document.getElementById("PersonalToolbar"); @@ -1138,16 +1139,18 @@ let BookmarkingUI = { }, customizeChange: function BUI_customizeChange() { this._updateToolbarStyle(); }, customizeDone: function BUI_customizeDone() { delete this._button; + delete this._star; + delete this._anchor; this.onToolbarVisibilityChange(); this._updateToolbarStyle(); }, _hasBookmarksObserver: false, uninit: function BUI_uninit() { this._uninitView(); @@ -1157,17 +1160,17 @@ let BookmarkingUI = { if (this._pendingStmt) { this._pendingStmt.cancel(); delete this._pendingStmt; } }, updateStarState: function BUI_updateStarState() { - if (!this.star || (this._uri && gBrowser.currentURI.equals(this._uri))) { + if (!this.button || (this._uri && gBrowser.currentURI.equals(this._uri))) { return; } // Reset tracked values. this._uri = gBrowser.currentURI; this._itemIds = []; if (this._pendingStmt) { @@ -1206,63 +1209,75 @@ let BookmarkingUI = { } } delete this._pendingStmt; }, this); }, _updateStar: function BUI__updateStar() { - if (!this.star) { + if (!this.button) { return; } if (this._itemIds.length > 0) { - this.star.setAttribute("starred", "true"); - this.star.setAttribute("tooltiptext", this._starredTooltip); + this.button.setAttribute("starred", "true"); + this.button.setAttribute("tooltiptext", this._starredTooltip); } else { - this.star.removeAttribute("starred"); - this.star.setAttribute("tooltiptext", this._unstarredTooltip); + this.button.removeAttribute("starred"); + this.button.setAttribute("tooltiptext", this._unstarredTooltip); } }, onCommand: function BUI_onCommand(aEvent) { if (aEvent.target != aEvent.currentTarget) { return; } // Ignore clicks on the star if we are updating its state. if (!this._pendingStmt) { PlacesCommandHook.bookmarkCurrentPage(this._itemIds.length > 0); } }, // nsINavBookmarkObserver onItemAdded: function BUI_onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI) { + if (!this.button) { + return; + } + if (aURI && aURI.equals(this._uri)) { // If a new bookmark has been added to the tracked uri, register it. if (this._itemIds.indexOf(aItemId) == -1) { this._itemIds.push(aItemId); this._updateStar(); } } }, onItemRemoved: function BUI_onItemRemoved(aItemId) { + if (!this.button) { + return; + } + let index = this._itemIds.indexOf(aItemId); // If one of the tracked bookmarks has been removed, unregister it. if (index != -1) { this._itemIds.splice(index, 1); this._updateStar(); } }, onItemChanged: function BUI_onItemChanged(aItemId, aProperty, aIsAnnotationProperty, aNewValue) { + if (!this.button) { + return; + } + if (aProperty == "uri") { let index = this._itemIds.indexOf(aItemId); // If the changed bookmark was tracked, check if it is now pointing to // a different uri and unregister it. if (index != -1 && aNewValue != this._uri.spec) { this._itemIds.splice(index, 1); this._updateStar(); }
--- a/browser/base/content/browser-sets.inc +++ b/browser/base/content/browser-sets.inc @@ -27,17 +27,16 @@ <command id="Browser:SendLink" oncommand="MailIntegration.sendLinkForWindow(window.content);"/> <command id="cmd_pageSetup" oncommand="PrintUtils.showPageSetup();"/> <command id="cmd_print" oncommand="PrintUtils.print();"/> <command id="cmd_printPreview" oncommand="PrintUtils.printPreview(PrintPreviewListener);"/> <command id="cmd_close" oncommand="BrowserCloseTabOrWindow()"/> <command id="cmd_closeWindow" oncommand="BrowserTryToCloseWindow()"/> - <command id="cmd_ToggleTabsOnTop" oncommand="TabsOnTop.toggle()"/> <command id="cmd_CustomizeToolbars" oncommand="BrowserCustomizeToolbar()"/> <command id="cmd_quitApplication" oncommand="goQuitApplication()"/> <commandset id="editMenuCommands"/> <command id="View:PageSource" oncommand="BrowserViewSourceOfDocument(content.document);" observes="isImage"/> <command id="View:PageInfo" oncommand="BrowserPageInfo();"/> @@ -103,17 +102,16 @@ <command id="Tools:ErrorConsole" oncommand="toJavaScriptConsole()" disabled="true" hidden="true"/> <command id="Tools:DevToolsConnect" oncommand="gDevToolsBrowser.openConnectScreen(gBrowser)" disabled="true" hidden="true"/> <command id="Tools:Sanitize" oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/> <command id="Tools:PrivateBrowsing" oncommand="OpenBrowserWindow({private: true});"/> <command id="History:UndoCloseTab" oncommand="undoCloseTab();"/> <command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/> - <command id="Browser:ToggleAddonBar" oncommand="toggleAddonBar();"/> <command id="Social:TogglePageMark" oncommand="SocialMark.togglePageMark();" disabled="true"/> <command id="Social:SharePage" oncommand="SocialShare.sharePage();" disabled="true"/> <command id="Social:ToggleSidebar" oncommand="Social.toggleSidebar();"/> <command id="Social:ToggleNotifications" oncommand="Social.toggleNotifications();" hidden="true"/> <command id="Social:FocusChat" oncommand="SocialChatBar.focus();" hidden="true" disabled="true"/> <command id="Social:Toggle" oncommand="Social.toggle();" hidden="true"/> <command id="Social:Addons" oncommand="BrowserOpenAddonsMgr('addons://list/service');"/> </commandset> @@ -408,17 +406,15 @@ #expand <key id="key_selectTab3" oncommand="gBrowser.selectTabAtIndex(2, event);" key="3" modifiers="__NUM_SELECT_TAB_MODIFIER__"/> #expand <key id="key_selectTab4" oncommand="gBrowser.selectTabAtIndex(3, event);" key="4" modifiers="__NUM_SELECT_TAB_MODIFIER__"/> #expand <key id="key_selectTab5" oncommand="gBrowser.selectTabAtIndex(4, event);" key="5" modifiers="__NUM_SELECT_TAB_MODIFIER__"/> #expand <key id="key_selectTab6" oncommand="gBrowser.selectTabAtIndex(5, event);" key="6" modifiers="__NUM_SELECT_TAB_MODIFIER__"/> #expand <key id="key_selectTab7" oncommand="gBrowser.selectTabAtIndex(6, event);" key="7" modifiers="__NUM_SELECT_TAB_MODIFIER__"/> #expand <key id="key_selectTab8" oncommand="gBrowser.selectTabAtIndex(7, event);" key="8" modifiers="__NUM_SELECT_TAB_MODIFIER__"/> #expand <key id="key_selectLastTab" oncommand="gBrowser.selectTabAtIndex(-1, event);" key="9" modifiers="__NUM_SELECT_TAB_MODIFIER__"/> - <key id="key_toggleAddonBar" command="Browser:ToggleAddonBar" key="&toggleAddonBarCmd.key;" modifiers="accel"/> - </keyset> # Used by baseMenuOverlay #ifdef XP_MACOSX <commandset id="baseMenuCommandSet" /> #endif <keyset id="baseMenuKeyset" />
--- a/browser/base/content/browser-social.js +++ b/browser/base/content/browser-social.js @@ -792,19 +792,17 @@ SocialShare = { }, true); } // always ensure that origin belongs to the endpoint let uri = Services.io.newURI(shareEndpoint, null, null); iframe.setAttribute("origin", provider.origin); iframe.setAttribute("src", shareEndpoint); let navBar = document.getElementById("nav-bar"); - let anchor = navBar.getAttribute("mode") == "text" ? - document.getAnonymousElementByAttribute(this.shareButton, "class", "toolbarbutton-text") : - document.getAnonymousElementByAttribute(this.shareButton, "class", "toolbarbutton-icon"); + let anchor = document.getAnonymousElementByAttribute(this.shareButton, "class", "toolbarbutton-icon"); this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false); Social.setErrorListener(iframe, this.setErrorMessage.bind(this)); }, _generateShareEndpointURL: function(shareURL, pageData) { // support for existing share endpoints by supporting their querystring // arguments. parse the query string template and do replacements where // necessary the query names may be different than ours, so we could see @@ -1233,19 +1231,17 @@ SocialToolbar = { setTimeout(function() { dispatchPanelEvent("socialFrameShow"); }, 0); }, true); } }); let navBar = document.getElementById("nav-bar"); - let anchor = navBar.getAttribute("mode") == "text" ? - document.getAnonymousElementByAttribute(aToolbarButton, "class", "toolbarbutton-text") : - document.getAnonymousElementByAttribute(aToolbarButton, "class", "toolbarbutton-badge-container"); + let anchor = document.getAnonymousElementByAttribute(aToolbarButton, "class", "toolbarbutton-badge-container"); // Bug 849216 - open the popup in a setTimeout so we avoid the auto-rollup // handling from preventing it being opened in some cases. setTimeout(function() { panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false); }, 0); }, setPanelErrorMessage: function SocialToolbar_setPanelErrorMessage(aNotificationFrame) {
--- a/browser/base/content/browser-tabview.js +++ b/browser/base/content/browser-tabview.js @@ -415,26 +415,23 @@ let TabView = { _addToolbarButton: function TabView__addToolbarButton() { let buttonId = "tabview-button"; if (document.getElementById(buttonId)) return; let toolbar = document.getElementById("TabsToolbar"); let currentSet = toolbar.currentSet.split(","); - let alltabsPos = currentSet.indexOf("alltabs-button"); if (-1 == alltabsPos) return; - currentSet[alltabsPos] += "," + buttonId; - currentSet = currentSet.join(","); - toolbar.currentSet = currentSet; - toolbar.setAttribute("currentset", currentSet); - document.persist(toolbar.id, "currentset"); + let allTabsBtn = document.getElementById("alltabs-button"); + let nextItem = allTabsBtn.nextSibling; + toolbar.insertItem(buttonId, nextItem); }, // ---------- // Function: updateGroupNumberBroadcaster // Updates the group number broadcaster. updateGroupNumberBroadcaster: function TabView_updateGroupNumberBroadcaster(number) { let groupsNumber = document.getElementById("tabviewGroupsNumber"); groupsNumber.setAttribute("groups", number);
--- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -8,16 +8,69 @@ searchbar { -moz-binding: url("chrome://browser/content/search/search.xml#searchbar"); } browser[remote="true"] { -moz-binding: url("chrome://global/content/bindings/remote-browser.xml#remote-browser"); } +toolbar[customizable="true"] { + -moz-binding: url("chrome://browser/content/customizableui/toolbar.xml#toolbar"); +} + +%ifdef XP_MACOSX +toolbar[customizable="true"]:not([nowindowdrag="true"]) { + -moz-binding: url("chrome://browser/content/customizableui/toolbar.xml#toolbar-drag"); +} +%endif + +#toolbar-menubar[autohide="true"] { + -moz-binding: url("chrome://browser/content/customizableui/toolbar.xml#toolbar-menubar-autohide"); +} + +#addon-bar { + -moz-binding: url("chrome://browser/content/customizableui/toolbar.xml#addonbar-delegating"); + visibility: visible; + margin: 0; + height: 0 !important; + overflow: hidden; + padding: 0; + border: 0 none; +} + +#addonbar-closebutton { + visibility: visible; + height: 0 !important; +} + +#status-bar { + height: 0 !important; + -moz-binding: none; + padding: 0; + margin: 0; +} + +panelmultiview { + -moz-binding: url("chrome://browser/content/customizableui/panelUI.xml#panelmultiview"); +} + +panelview { + -moz-binding: url("chrome://browser/content/customizableui/panelUI.xml#panelview"); + -moz-box-orient: vertical; +} + +.panel-mainview { + transition: transform 150ms; +} + +panelview:not([mainview]):not([current]) { + display: none; +} + tabbrowser { -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser"); } .tabbrowser-tabs { -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tabs"); } @@ -34,31 +87,31 @@ tabbrowser { } .tabbrowser-tab { -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tab"); } .tabbrowser-tab:not([pinned]) { -moz-box-flex: 100; - max-width: 250px; + max-width: 180px; min-width: 100px; width: 0; transition: min-width 200ms ease-out, - max-width 250ms ease-out, + max-width 230ms ease-out, opacity 50ms ease-out 20ms /* hide the tab for the first 20ms of the max-width transition */; } .tabbrowser-tab:not([pinned]):not([fadein]) { max-width: 0.1px; min-width: 0.1px; opacity: 0 !important; transition: min-width 200ms ease-out, - max-width 250ms ease-out, - opacity 50ms ease-out 180ms /* hide the tab for the last 20ms of the max-width transition */; + max-width 230ms ease-out, + opacity 50ms ease-out 160ms /* hide the tab for the last 20ms of the max-width transition */; } .tab-throbber:not([fadein]):not([pinned]), .tab-label:not([fadein]):not([pinned]), .tab-icon-image:not([fadein]):not([pinned]), .tab-close-button:not([fadein]):not([pinned]) { display: none; } @@ -81,30 +134,23 @@ tabbrowser { #alltabs-popup { -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup"); } toolbar[printpreview="true"] { -moz-binding: url("chrome://global/content/printPreviewBindings.xml#printpreviewtoolbar"); } -#toolbar-menubar { - -moz-box-ordinal-group: 5; +toolbar[overflowable] > .customization-target { + overflow: hidden; } -#navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar) { - -moz-box-ordinal-group: 50; -} - -#TabsToolbar { - -moz-box-ordinal-group: 100; -} - -#TabsToolbar[tabsontop="true"] { - -moz-box-ordinal-group: 10; +toolbar[customizing][overflowable] > .overflow-button, +toolbar[overflowable]:not([overflowing]) > .overflow-button { + display: none; } %ifdef CAN_DRAW_IN_TITLEBAR #main-window[inFullscreen] > #titlebar, #main-window[inFullscreen] .titlebar-placeholder, #main-window:not([tabsintitlebar]) .titlebar-placeholder { display: none; } @@ -112,53 +158,68 @@ toolbar[printpreview="true"] { #titlebar { -moz-binding: url("chrome://global/content/bindings/general.xml#windowdragbox"); } #titlebar-spacer { pointer-events: none; } -#main-window[tabsintitlebar] #appmenu-button-container, #main-window[tabsintitlebar] #titlebar-buttonbox { position: relative; } + +#titlebar-buttonbox { + -moz-appearance: -moz-window-button-box; +} + +%ifdef XP_MACOSX +#titlebar-fullscreen-button { + -moz-appearance: -moz-mac-fullscreen-button; +} +%endif + +%ifdef XP_WIN +#main-window[sizemode="maximized"] #titlebar-buttonbox { + -moz-appearance: -moz-window-button-box-maximized; +} +%endif + %endif .bookmarks-toolbar-customize, #wrapper-personal-bookmarks > #personal-bookmarks > #PlacesToolbar > hbox > #PlacesToolbarItems { display: none; } #wrapper-personal-bookmarks[place="toolbar"] > #personal-bookmarks > #PlacesToolbar > .bookmarks-toolbar-customize { display: -moz-box; } -#main-window[disablechrome] #navigator-toolbox[tabsontop="true"] > toolbar:not(#toolbar-menubar):not(#TabsToolbar) { - visibility: collapse; -} - #wrapper-urlbar-container #urlbar-container > #urlbar > toolbarbutton, #urlbar-container:not([combined]) > #urlbar > toolbarbutton, #urlbar-container[combined] + #reload-button + #stop-button, #urlbar-container[combined] + #reload-button, -toolbar:not([mode="icons"]) > #urlbar-container > #urlbar > toolbarbutton, -toolbar[mode="icons"] > #urlbar-container > #urlbar > #urlbar-reload-button:not([displaystop]) + #urlbar-stop-button, -toolbar[mode="icons"] > #urlbar-container > #urlbar > #urlbar-reload-button[displaystop], -toolbar[mode="icons"] > #reload-button:not([displaystop]) + #stop-button, -toolbar[mode="icons"] > #reload-button[displaystop] { +#urlbar-reload-button:not([displaystop]) + #urlbar-stop-button, +#urlbar-reload-button[displaystop], +#reload-button:not([displaystop]) + #stop-button, +#reload-button[displaystop] { visibility: collapse; } -#feed-button > .toolbarbutton-menu-dropmarker { - display: none; +#PanelUI-feeds > .feed-toolbarbutton:-moz-locale-dir(rtl) { + direction: rtl; } -#feed-menu > .feed-menuitem:-moz-locale-dir(rtl) { - direction: rtl; +#urlbar-container { + min-width: 50ch; +} + +#search-container { + min-width: 25ch; } #main-window:-moz-lwtheme { background-repeat: no-repeat; background-position: top right; } %ifdef XP_MACOSX @@ -167,59 +228,26 @@ toolbar[mode="icons"] > #reload-button[d } %endif #browser-bottombox[lwthemefooter="true"] { background-repeat: no-repeat; background-position: bottom left; } -splitmenu { - -moz-binding: url("chrome://browser/content/urlbarBindings.xml#splitmenu"); -} - -.splitmenu-menuitem { - -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem"); - list-style-image: inherit; - -moz-image-region: inherit; -} - -.splitmenu-menuitem[iconic="true"] { - -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic"); -} - -.splitmenu-menu > .menu-text, -:-moz-any(.splitmenu-menu, .splitmenu-menuitem) > .menu-accel-container, -#appmenu-editmenu > .menu-text, -#appmenu-editmenu > .menu-accel-container { - display: none; -} - .menuitem-tooltip { -moz-binding: url("chrome://browser/content/urlbarBindings.xml#menuitem-tooltip"); } .menuitem-iconic-tooltip, .menuitem-tooltip[type="checkbox"], .menuitem-tooltip[type="radio"] { -moz-binding: url("chrome://browser/content/urlbarBindings.xml#menuitem-iconic-tooltip"); } -%ifdef MENUBAR_CAN_AUTOHIDE -%ifndef CAN_DRAW_IN_TITLEBAR -#appmenu-toolbar-button > .toolbarbutton-text { - display: -moz-box; -} -%endif - -#appmenu_offlineModeRecovery:not([checked=true]) { - display: none; -} -%endif - /* Hide menu elements intended for keyboard access support */ #main-menubar[openedwithkey=false] .show-only-for-keyboard { display: none; } /* ::::: location bar ::::: */ #urlbar { -moz-binding: url(chrome://browser/content/urlbarBindings.xml#urlbar); @@ -303,28 +331,30 @@ panel[noactions] > richlistbox > richlis .unified-nav-current { font-weight: bold; } toolbarbutton.bookmark-item { max-width: 13em; } -%ifdef MENUBAR_CAN_AUTOHIDE -#toolbar-menubar:not([autohide="true"]) ~ toolbar > #bookmarks-menu-button, -#toolbar-menubar:not([autohide="true"]) > #bookmarks-menu-button { - display: none; -} -%endif - #editBMPanel_tagsSelector { /* override default listbox width from xul.css */ width: auto; } +/* The star doesn't make sense as text */ +toolbar[mode="text"] #bookmarks-menu-button > .toolbarbutton-menubutton-button > .toolbarbutton-icon { + display: -moz-box !important; +} +toolbar[mode="text"] #bookmarks-menu-button > .toolbarbutton-menubutton-button > .toolbarbutton-text, +toolbar[mode="full"] #bookmarks-menu-button.bookmark-item > .toolbarbutton-menubutton-button > .toolbarbutton-text { + display: none; +} + menupopup[emptyplacesresult="true"] > .hide-if-empty-places-result { display: none; } menuitem.spell-suggestion { font-weight: bold; } @@ -339,17 +369,16 @@ window[sizemode="maximized"] #content .n /* Hide extension toolbars that neglected to set the proper class */ window[chromehidden~="location"][chromehidden~="toolbar"] toolbar:not(.chromeclass-menubar), window[chromehidden~="toolbar"] toolbar:not(.toolbar-primary):not(.chromeclass-menubar) { display: none; } #navigator-toolbox , -#status-bar , #mainPopupSet { min-width: 1px; } %ifdef MOZ_SERVICES_SYNC /* Sync notification UI */ #sync-notifications { -moz-binding: url("chrome://browser/content/sync/notification.xml#notificationbox"); @@ -438,24 +467,16 @@ window[chromehidden~="toolbar"] toolbar: #full-screen-domain-text, #full-screen-remember-decision > .checkbox-label-box > .checkbox-label { word-wrap: break-word; /* We must specify a min-width, otherwise word-wrap:break-word doesn't work. Bug 630864. */ min-width: 1px; } -#nav-bar[mode="text"] > #window-controls > toolbarbutton > .toolbarbutton-icon { - display: -moz-box; -} - -#nav-bar[mode="text"] > #window-controls > toolbarbutton > .toolbarbutton-text { - display: none; -} - /* ::::: Ctrl-Tab Panel ::::: */ .ctrlTab-preview > html|img, .ctrlTab-preview > html|canvas { min-width: inherit; max-width: inherit; min-height: inherit; max-height: inherit; @@ -510,27 +531,16 @@ window[chromehidden~="toolbar"] toolbar: #click-to-play-plugins-notification { -moz-binding: url("chrome://browser/content/urlbarBindings.xml#click-to-play-plugins-notification"); } .plugin-popupnotification-centeritem { -moz-binding: url("chrome://browser/content/urlbarBindings.xml#plugin-popupnotification-center-item"); } -/* override hidden="true" for the status bar compatibility shim - in case it was persisted for the real status bar */ -#status-bar { - display: -moz-box; -} - -/* Remove the resizer from the statusbar compatibility shim */ -#status-bar[hideresizer] > .statusbar-resizerpanel { - display: none; -} - browser[tabmodalPromptShowing] { -moz-user-focus: none !important; } /* Status panel */ statuspanel { -moz-binding: url("chrome://browser/content/tabbrowser.xml#statuspanel");
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -89,16 +89,22 @@ this.__defineSetter__("PluralForm", func }); XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch", "resource://gre/modules/TelemetryStopwatch.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils", "resource:///modules/AboutHomeUtils.jsm"); +XPCOMUtils.defineLazyGetter(this, "gCustomizeMode", function() { + let scope = {}; + Cu.import("resource:///modules/CustomizeMode.jsm", scope); + return new scope.CustomizeMode(window); +}); + #ifdef MOZ_SERVICES_SYNC XPCOMUtils.defineLazyModuleGetter(this, "Weave", "resource://services-sync/main.js"); #endif XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function () { let tmp = {}; Cu.import("resource://gre/modules/PopupNotifications.jsm", tmp); @@ -149,16 +155,17 @@ let gInitialPages = [ "about:newtab", "about:home", "about:privatebrowsing", "about:welcomeback", "about:sessionrestore" ]; #include browser-addons.js +#include browser-customization.js #include browser-feeds.js #include browser-fullScreen.js #include browser-fullZoom.js #include browser-places.js #include browser-plugins.js #include browser-safebrowsing.js #include browser-social.js #include browser-tabPreviews.js @@ -922,38 +929,33 @@ var gBrowserInit = { // border. Use 28px as a guess (titlebar + bottom window border) defaultHeight -= 28; #endif } document.documentElement.setAttribute("width", defaultWidth); document.documentElement.setAttribute("height", defaultHeight); } - if (!gShowPageResizers) - document.getElementById("status-bar").setAttribute("hideresizer", "true"); - if (!window.toolbar.visible) { // adjust browser UI for popups if (gURLBar) { gURLBar.setAttribute("readonly", "true"); gURLBar.setAttribute("enablehistory", "false"); } goSetCommandEnabled("cmd_newNavigatorTab", false); } -#ifdef MENUBAR_CAN_AUTOHIDE - updateAppButtonDisplay(); +#ifdef CAN_DRAW_IN_TITLEBAR + updateTitlebarDisplay(); #endif // Misc. inits. CombinedStopReload.init(); - TabsOnTop.init(); gPrivateBrowsingUI.init(); TabsInTitlebar.init(); - retrieveToolbarIconsizesFromTheme(); // Wait until chrome is painted before executing code not critical to making the window visible this._boundDelayedStartup = this._delayedStartup.bind(this, uriToLoad, mustLoadSidebar); window.addEventListener("MozAfterPaint", this._boundDelayedStartup); this._loadHandled = true; }, @@ -1040,18 +1042,23 @@ var gBrowserInit = { Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false); Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false); Services.obs.addObserver(gFormSubmitObserver, "invalidformsubmit", false); BrowserOffline.init(); OfflineApps.init(); IndexedDBPromptHelper.init(); gFormSubmitObserver.init(); + // Initialize the full zoom setting. + // We do this before the session restore service gets initialized so we can + // apply full zoom settings to tabs restored by the session restore service. + FullZoom.init(); + PanelUI.init(); SocialUI.init(); - AddonManager.addAddonListener(AddonsMgrListener); + LightweightThemeListener.init(); WebrtcIndicator.init(); // Ensure login manager is up and running. Services.logins; if (mustLoadSidebar) { let sidebar = document.getElementById("sidebar"); let sidebarBox = document.getElementById("sidebar-box"); @@ -1087,21 +1094,16 @@ var gBrowserInit = { document.getElementById("textfieldDirection-swap").hidden = false; } // Setup click-and-hold gestures access to the session history // menus if global click-and-hold isn't turned on if (!getBoolPref("ui.click_hold_context_menus", false)) SetClickAndHoldHandlers(); - // Initialize the full zoom setting. - // We do this before the session restore service gets initialized so we can - // apply full zoom settings to tabs restored by the session restore service. - FullZoom.init(); - // Bug 666804 - NetworkPrioritizer support for e10s if (!gMultiProcessBrowser) { let NP = {}; Cu.import("resource:///modules/NetworkPrioritizer.jsm", NP); NP.trackBrowserWindow(window); } // initialize the session-restore service (in case it's not already running) @@ -1226,56 +1228,33 @@ var gBrowserInit = { // Enable DevTools connection screen, if the preference allows this. let devtoolsRemoteEnabled = gPrefService.getBoolPref("devtools.debugger.remote-enabled"); if (devtoolsRemoteEnabled) { let cmd = document.getElementById("Tools:DevToolsConnect"); cmd.removeAttribute("disabled"); cmd.removeAttribute("hidden"); } -#ifdef MENUBAR_CAN_AUTOHIDE - // If the user (or the locale) hasn't enabled the top-level "Character - // Encoding" menu via the "browser.menu.showCharacterEncoding" preference, - // hide it. - if ("true" != gPrefService.getComplexValue("browser.menu.showCharacterEncoding", - Ci.nsIPrefLocalizedString).data) - document.getElementById("appmenu_charsetMenu").hidden = true; -#endif - // Enable Responsive UI? let responsiveUIEnabled = gPrefService.getBoolPref("devtools.responsiveUI.enabled"); if (responsiveUIEnabled) { let cmd = document.getElementById("Tools:ResponsiveUI"); cmd.removeAttribute("disabled"); cmd.removeAttribute("hidden"); } // Add Devtools menuitems and listeners gDevToolsBrowser.registerBrowserWindow(window); - let appMenuButton = document.getElementById("appmenu-button"); - let appMenuPopup = document.getElementById("appmenu-popup"); - if (appMenuButton && appMenuPopup) { - let appMenuOpening = null; - appMenuButton.addEventListener("mousedown", function(event) { - if (event.button == 0) - appMenuOpening = new Date(); - }, false); - appMenuPopup.addEventListener("popupshown", function(event) { - if (event.target != appMenuPopup || !appMenuOpening) - return; - let duration = new Date() - appMenuOpening; - appMenuOpening = null; - Services.telemetry.getHistogramById("FX_APP_MENU_OPEN_MS").add(duration); - }, false); - } - window.addEventListener("mousemove", MousePosTracker, false); window.addEventListener("dragover", MousePosTracker, false); + gNavToolbox.addEventListener("customizationstarting", CustomizationHandler); + gNavToolbox.addEventListener("customizationending", CustomizationHandler); + // End startup crash tracking after a delay to catch crashes while restoring // tabs and to postpone saving the pref to disk. try { const startupCrashEndDelay = 30 * 1000; setTimeout(Services.startup.trackStartupCrashEnd, startupCrashEndDelay); } catch (ex) { Cu.reportError("Could not end startup crash tracking: " + ex); } @@ -1324,18 +1303,16 @@ var gBrowserInit = { try { gBrowser.removeProgressListener(window.XULBrowserWindow); gBrowser.removeTabsProgressListener(window.TabsProgressListener); } catch (ex) { } BookmarkingUI.uninit(); - TabsOnTop.uninit(); - TabsInTitlebar.uninit(); var enumerator = Services.wm.getEnumerator(null); enumerator.getNext(); if (!enumerator.hasMoreElements()) { document.persist("sidebar-box", "sidebarcommand"); document.persist("sidebar-box", "width"); document.persist("sidebar-box", "src"); @@ -1376,18 +1353,19 @@ var gBrowserInit = { Services.prefs.removeObserver(prefName, gMetroPrefs); }); #endif #endif BrowserOffline.uninit(); OfflineApps.uninit(); IndexedDBPromptHelper.uninit(); - AddonManager.removeAddonListener(AddonsMgrListener); SocialUI.uninit(); + LightweightThemeListener.uninit(); + PanelUI.uninit(); } // Final window teardown, do this last. window.XULBrowserWindow.destroy(); window.XULBrowserWindow = null; window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner @@ -1403,17 +1381,17 @@ var gBrowserInit = { // macBrowserOverlay nonBrowserWindowStartup: function() { // Disable inappropriate commands / submenus var disabledItems = ['Browser:SavePage', 'Browser:SendLink', 'cmd_pageSetup', 'cmd_print', 'cmd_find', 'cmd_findAgain', 'viewToolbarsMenu', 'viewSidebarMenuMenu', 'Browser:Reload', 'viewFullZoomMenu', 'pageStyleMenu', 'charsetMenu', 'View:PageSource', 'View:FullScreen', 'viewHistorySidebar', 'Browser:AddBookmarkAs', 'Browser:BookmarkAllTabs', - 'View:PageInfo', 'Browser:ToggleTabView', 'Browser:ToggleAddonBar']; + 'View:PageInfo', 'Browser:ToggleTabView']; var element; for (let disabledItem of disabledItems) { element = document.getElementById(disabledItem); if (element) element.setAttribute("disabled", "true"); } @@ -2685,35 +2663,32 @@ var PrintPreviewListener = { if (gInPrintPreviewMode) this._hideChrome(); else this._showChrome(); if (this._chromeState.sidebarOpen) toggleSidebar(this._sidebarCommand); -#ifdef MENUBAR_CAN_AUTOHIDE - updateAppButtonDisplay(); +#ifdef CAN_DRAW_IN_TITLEBAR + updateTitlebarDisplay(); #endif }, _hideChrome: function () { this._chromeState = {}; var sidebar = document.getElementById("sidebar-box"); this._chromeState.sidebarOpen = !sidebar.hidden; this._sidebarCommand = sidebar.getAttribute("sidebarcommand"); var notificationBox = gBrowser.getNotificationBox(); this._chromeState.notificationsOpen = !notificationBox.notificationsHidden; notificationBox.notificationsHidden = true; document.getElementById("sidebar").setAttribute("src", "about:blank"); - var addonBar = document.getElementById("addon-bar"); - this._chromeState.addonBarOpen = !addonBar.collapsed; - addonBar.collapsed = true; gBrowser.updateWindowResizers(); this._chromeState.findOpen = gFindBarInitialized && !gFindBar.hidden; if (gFindBarInitialized) gFindBar.close(); var globalNotificationBox = document.getElementById("global-notificationbox"); this._chromeState.globalNotificationsOpen = !globalNotificationBox.notificationsHidden; @@ -2725,21 +2700,16 @@ var PrintPreviewListener = { this._chromeState.syncNotificationsOpen = !syncNotifications.notificationsHidden; syncNotifications.notificationsHidden = true; } }, _showChrome: function () { if (this._chromeState.notificationsOpen) gBrowser.getNotificationBox().notificationsHidden = false; - if (this._chromeState.addonBarOpen) { - document.getElementById("addon-bar").collapsed = false; - gBrowser.updateWindowResizers(); - } - if (this._chromeState.findOpen) gFindBar.open(); if (this._chromeState.globalNotificationsOpen) document.getElementById("global-notificationbox").notificationsHidden = false; if (this._chromeState.syncNotificationsOpen) document.getElementById("sync-notifications").notificationsHidden = false; @@ -2806,45 +2776,16 @@ function openHomeDialog(aURL) gPrefService.setComplexValue("browser.startup.homepage", Components.interfaces.nsISupportsString, str); } catch (ex) { dump("Failed to set the home page.\n"+ex+"\n"); } } } -var bookmarksButtonObserver = { - onDrop: function (aEvent) - { - let name = { }; - let url = browserDragAndDrop.drop(aEvent, name); - try { - PlacesUIUtils.showBookmarkDialog({ action: "add" - , type: "bookmark" - , uri: makeURI(url) - , title: name - , hiddenRows: [ "description" - , "location" - , "loadInSidebar" - , "keyword" ] - }, window); - } catch(ex) { } - }, - - onDragOver: function (aEvent) - { - browserDragAndDrop.dragOver(aEvent); - aEvent.dropEffect = "link"; - }, - - onDragExit: function (aEvent) - { - } -} - var newTabButtonObserver = { onDragOver: function (aEvent) { browserDragAndDrop.dragOver(aEvent); }, onDragExit: function (aEvent) { @@ -3353,164 +3294,28 @@ function OpenBrowserWindow(options) else // forget about the charset information. { win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs); } return win; } -var gCustomizeSheet = false; +//XXXunf Are these still useful to keep around? function BrowserCustomizeToolbar() { - // Disable the toolbar context menu items - var menubar = document.getElementById("main-menubar"); - for (let childNode of menubar.childNodes) - childNode.setAttribute("disabled", true); - - var cmd = document.getElementById("cmd_CustomizeToolbars"); - cmd.setAttribute("disabled", "true"); - - var splitter = document.getElementById("urlbar-search-splitter"); - if (splitter) - splitter.parentNode.removeChild(splitter); - - CombinedStopReload.uninit(); - - PlacesToolbarHelper.customizeStart(); - BookmarkingUI.customizeStart(); - DownloadsButton.customizeStart(); - - TabsInTitlebar.allowedBy("customizing-toolbars", false); - - var customizeURL = "chrome://global/content/customizeToolbar.xul"; - gCustomizeSheet = getBoolPref("toolbar.customization.usesheet", false); - - if (gCustomizeSheet) { - let sheetFrame = document.createElement("iframe"); - let panel = document.getElementById("customizeToolbarSheetPopup"); - sheetFrame.id = "customizeToolbarSheetIFrame"; - sheetFrame.toolbox = gNavToolbox; - sheetFrame.panel = panel; - sheetFrame.setAttribute("style", panel.getAttribute("sheetstyle")); - panel.appendChild(sheetFrame); - - // Open the panel, but make it invisible until the iframe has loaded so - // that the user doesn't see a white flash. - panel.style.visibility = "hidden"; - gNavToolbox.addEventListener("beforecustomization", function onBeforeCustomization() { - gNavToolbox.removeEventListener("beforecustomization", onBeforeCustomization, false); - panel.style.removeProperty("visibility"); - }, false); - - sheetFrame.setAttribute("src", customizeURL); - - panel.openPopup(gNavToolbox, "after_start", 0, 0); - } else { - window.openDialog(customizeURL, - "CustomizeToolbar", - "chrome,titlebar,toolbar,location,resizable,dependent", - gNavToolbox); - } + gCustomizeMode.enter(); } function BrowserToolboxCustomizeDone(aToolboxChanged) { - if (gCustomizeSheet) { - document.getElementById("customizeToolbarSheetPopup").hidePopup(); - let iframe = document.getElementById("customizeToolbarSheetIFrame"); - iframe.parentNode.removeChild(iframe); - } - - // Update global UI elements that may have been added or removed - if (aToolboxChanged) { - gURLBar = document.getElementById("urlbar"); - - gProxyFavIcon = document.getElementById("page-proxy-favicon"); - gHomeButton.updateTooltip(); - gIdentityHandler._cacheElements(); - window.XULBrowserWindow.init(); - -#ifndef XP_MACOSX - updateEditUIVisibility(); -#endif - - // Hacky: update the PopupNotifications' object's reference to the iconBox, - // if it already exists, since it may have changed if the URL bar was - // added/removed. - if (!window.__lookupGetter__("PopupNotifications")) - PopupNotifications.iconBox = document.getElementById("notification-popup-box"); - } - - PlacesToolbarHelper.customizeDone(); - BookmarkingUI.customizeDone(); - DownloadsButton.customizeDone(); - - // The url bar splitter state is dependent on whether stop/reload - // and the location bar are combined, so we need this ordering - CombinedStopReload.init(); - UpdateUrlbarSearchSplitterState(); - setUrlAndSearchBarWidthForConditionalForwardButton(); - - // Update the urlbar - if (gURLBar) { - URLBarSetURI(); - XULBrowserWindow.asyncUpdateUI(); - BookmarkingUI.updateStarState(); - SocialMark.updateMarkState(); - SocialShare.update(); - } - - TabsInTitlebar.allowedBy("customizing-toolbars", true); - - // Re-enable parts of the UI we disabled during the dialog - var menubar = document.getElementById("main-menubar"); - for (let childNode of menubar.childNodes) - childNode.setAttribute("disabled", false); - var cmd = document.getElementById("cmd_CustomizeToolbars"); - cmd.removeAttribute("disabled"); - - // make sure to re-enable click-and-hold - if (!getBoolPref("ui.click_hold_context_menus", false)) - SetClickAndHoldHandlers(); - - gBrowser.selectedBrowser.focus(); + gCustomizeMode.exit(aToolboxChanged); } function BrowserToolboxCustomizeChange(aType) { - switch (aType) { - case "iconsize": - case "mode": - retrieveToolbarIconsizesFromTheme(); - break; - default: - gHomeButton.updatePersonalToolbarStyle(); - BookmarkingUI.customizeChange(); - } -} - -/** - * Allows themes to override the "iconsize" attribute on toolbars. - */ -function retrieveToolbarIconsizesFromTheme() { - function retrieveToolbarIconsize(aToolbar) { - if (aToolbar.localName != "toolbar") - return; - - // The theme indicates that it wants to override the "iconsize" attribute - // by specifying a special value for the "counter-reset" property on the - // toolbar. A custom property cannot be used because getComputedStyle can - // only return the values of standard CSS properties. - let counterReset = getComputedStyle(aToolbar).counterReset; - if (counterReset == "smallicons 0") - aToolbar.setAttribute("iconsize", "small"); - else if (counterReset == "largeicons 0") - aToolbar.setAttribute("iconsize", "large"); - } - - Array.forEach(gNavToolbox.childNodes, retrieveToolbarIconsize); - gNavToolbox.externalToolbars.forEach(retrieveToolbarIconsize); + gHomeButton.updatePersonalToolbarStyle(); + BookmarksMenuButton.customizeChange(); } /** * Update the global flag that tracks whether or not any edit UI (the Edit menu, * edit-related items in the context menu, and edit-related toolbar buttons * is visible, then update the edit commands' enabled state accordingly. We use * this flag to skip updating the edit commands on focus or selection changes * when no UI is visible to improve performance (including pageload performance, @@ -3529,36 +3334,27 @@ function retrieveToolbarIconsizesFromThe * is a no op. */ function updateEditUIVisibility() { #ifndef XP_MACOSX let editMenuPopupState = document.getElementById("menu_EditPopup").state; let contextMenuPopupState = document.getElementById("contentAreaContextMenu").state; let placesContextMenuPopupState = document.getElementById("placesContext").state; -#ifdef MENUBAR_CAN_AUTOHIDE - let appMenuPopupState = document.getElementById("appmenu-popup").state; -#endif // The UI is visible if the Edit menu is opening or open, if the context menu // is open, or if the toolbar has been customized to include the Cut, Copy, // or Paste toolbar buttons. gEditUIVisible = editMenuPopupState == "showing" || editMenuPopupState == "open" || contextMenuPopupState == "showing" || contextMenuPopupState == "open" || placesContextMenuPopupState == "showing" || placesContextMenuPopupState == "open" || -#ifdef MENUBAR_CAN_AUTOHIDE - appMenuPopupState == "showing" || - appMenuPopupState == "open" || -#endif - document.getElementById("cut-button") || - document.getElementById("copy-button") || - document.getElementById("paste-button") ? true : false; + document.getElementById("edit-controls") ? true : false; // If UI is visible, update the edit commands' enabled state to reflect // whether or not they are actually enabled for the current focus/selection. if (gEditUIVisible) goUpdateGlobalEditMenuItems(); // Otherwise, enable all commands, so that keyboard shortcuts still work, // then lazily determine their actual enabled state when the user presses @@ -3578,44 +3374,29 @@ function updateEditUIVisibility() /** * Makes the Character Encoding menu enabled or disabled as appropriate. * To be called when the View menu or the app menu is opened. */ function updateCharacterEncodingMenuState() { let charsetMenu = document.getElementById("charsetMenu"); - let appCharsetMenu = document.getElementById("appmenu_charsetMenu"); - let appDevCharsetMenu = - document.getElementById("appmenu_developer_charsetMenu"); // gBrowser is null on Mac when the menubar shows in the context of // non-browser windows. The above elements may be null depending on // what parts of the menubar are present. E.g. no app menu on Mac. if (gBrowser && gBrowser.docShell && gBrowser.docShell.mayEnableCharacterEncodingMenu) { if (charsetMenu) { charsetMenu.removeAttribute("disabled"); } - if (appCharsetMenu) { - appCharsetMenu.removeAttribute("disabled"); - } - if (appDevCharsetMenu) { - appDevCharsetMenu.removeAttribute("disabled"); - } } else { if (charsetMenu) { charsetMenu.setAttribute("disabled", "true"); } - if (appCharsetMenu) { - appCharsetMenu.setAttribute("disabled", "true"); - } - if (appDevCharsetMenu) { - appDevCharsetMenu.setAttribute("disabled", "true"); - } } } /** * Returns true if |aMimeType| is text-based, false otherwise. * * @param aMimeType * The MIME type to check. @@ -3637,18 +3418,18 @@ function mimeTypeIsTextBased(aMimeType) var XULBrowserWindow = { // Stored Status, Link and Loading values status: "", defaultStatus: "", overLink: "", startTime: 0, statusText: "", isBusy: false, - inContentWhitelist: ["about:addons", "about:downloads", "about:permissions", - "about:sync-progress", "about:preferences"], + // Left here for add-on compatibility, see bug 752434 + inContentWhitelist: [], QueryInterface: function (aIID) { if (aIID.equals(Ci.nsIWebProgressListener) || aIID.equals(Ci.nsIWebProgressListener2) || aIID.equals(Ci.nsISupportsWeakReference) || aIID.equals(Ci.nsIXULBrowserWindow) || aIID.equals(Ci.nsISupports)) return this; @@ -3668,32 +3449,29 @@ var XULBrowserWindow = { return this.statusTextField = document.getElementById("statusbar-display"); }, get isImage () { delete this.isImage; return this.isImage = document.getElementById("isImage"); }, init: function () { - this.throbberElement = document.getElementById("navigator-throbber"); - // Bug 666809 - SecurityUI support for e10s if (gMultiProcessBrowser) return; // Initialize the security button's state and tooltip text. Remember to reset // _hostChanged, otherwise onSecurityChange will short circuit. var securityUI = gBrowser.securityUI; this._hostChanged = true; this.onSecurityChange(null, null, securityUI.state); }, destroy: function () { // XXXjag to avoid leaks :-/, see bug 60729 - delete this.throbberElement; delete this.stopCommand; delete this.reloadCommand; delete this.statusTextField; delete this.statusText; }, setJSStatus: function () { // unsupported @@ -3808,20 +3586,16 @@ var XULBrowserWindow = { gBrowser.selectedBrowser.engines = null; } this.isBusy = true; if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) { this._busyUI = true; - // Turn the throbber on. - if (this.throbberElement) - this.throbberElement.setAttribute("busy", "true"); - // XXX: This needs to be based on window activity... this.stopCommand.removeAttribute("disabled"); CombinedStopReload.switchToStop(); } } else if (aStateFlags & nsIWebProgressListener.STATE_STOP) { // This (thanks to the filter) is a network stop or the last // request stop outside of loading the document, stop throbbers @@ -3856,20 +3630,16 @@ var XULBrowserWindow = { this.isImage.setAttribute('disabled', 'true'); } this.isBusy = false; if (this._busyUI) { this._busyUI = false; - // Turn the throbber off. - if (this.throbberElement) - this.throbberElement.removeAttribute("busy"); - this.stopCommand.setAttribute("disabled", "true"); CombinedStopReload.switchToReload(aRequest instanceof Ci.nsIRequest); } } }, onLocationChange: function (aWebProgress, aRequest, aLocationURI, aFlags) { var location = aLocationURI ? aLocationURI.spec : ""; @@ -3928,27 +3698,16 @@ var XULBrowserWindow = { URLBarSetURI(aLocationURI); // Update starring UI BookmarkingUI.updateStarState(); SocialMark.updateMarkState(); SocialShare.update(); } - // Show or hide browser chrome based on the whitelist - if (this.hideChromeForLocation(location)) { - document.documentElement.setAttribute("disablechrome", "true"); - } else { - let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); - if (ss.getTabValue(gBrowser.selectedTab, "appOrigin")) - document.documentElement.setAttribute("disablechrome", "true"); - else - document.documentElement.removeAttribute("disablechrome"); - } - // Utility functions for disabling find var shouldDisableFind = function shouldDisableFind(aDocument) { let docElt = aDocument.documentElement; return docElt && docElt.getAttribute("disablefastfind") == "true"; } var disableFindCommands = function disableFindCommands(aDisable) { let findCommands = [document.getElementById("cmd_find"), @@ -3998,16 +3757,27 @@ var XULBrowserWindow = { if (gFindBar.findMode != gFindBar.FIND_NORMAL) { // Close the Find toolbar if we're in old-style TAF mode gFindBar.close(); } // fix bug 253793 - turn off highlight when page changes gFindBar.getElement("highlight").checked = false; } + + // Try not to instantiate gCustomizeMode as much as possible, + // so don't use CustomizeMode.jsm to check for URI or customizing. + let customizingURI = "about:customizing"; + if (location == customizingURI && + !CustomizationHandler.isCustomizing()) { + gCustomizeMode.enter(); + } else if (location != customizingURI && + CustomizationHandler.isCustomizing()) { + gCustomizeMode.exit(); + } } UpdateBackForwardCommands(gBrowser.webNavigation); gGestureSupport.restoreRotationState(); // See bug 358202, when tabs are switched during a drag operation, // timers don't fire on windows (bug 203573) if (aRequest) @@ -4015,22 +3785,18 @@ var XULBrowserWindow = { else this.asyncUpdateUI(); }, asyncUpdateUI: function () { FeedHandler.updateFeeds(); }, - hideChromeForLocation: function(aLocation) { - aLocation = aLocation.toLowerCase(); - return this.inContentWhitelist.some(function(aSpec) { - return aSpec == aLocation; - }); - }, + // Left here for add-on compatibility, see bug 752434 + hideChromeForLocation: function() {}, onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) { this.status = aMessage; this.updateStatusField(); }, // Properties used to cache security state used to update the UI _state: null, @@ -4198,18 +3964,17 @@ var CombinedStopReload = { if (this._initialized) return; var urlbar = document.getElementById("urlbar-container"); var reload = document.getElementById("reload-button"); var stop = document.getElementById("stop-button"); if (urlbar) { - if (urlbar.parentNode.getAttribute("mode") != "icons" || - !reload || urlbar.nextSibling != reload || + if (!reload || urlbar.nextSibling != reload || !stop || reload.nextSibling != stop) urlbar.removeAttribute("combined"); else { urlbar.setAttribute("combined", "true"); reload = document.getElementById("urlbar-reload-button"); stop = document.getElementById("urlbar-stop-button"); } } @@ -4513,31 +4278,28 @@ function onViewToolbarsPopupShowing(aEve var deadItem = popup.childNodes[i]; if (deadItem.hasAttribute("toolbarId")) popup.removeChild(deadItem); } var firstMenuItem = aInsertPoint || popup.firstChild; let toolbarNodes = Array.slice(gNavToolbox.childNodes); - toolbarNodes.push(document.getElementById("addon-bar")); for (let toolbar of toolbarNodes) { let toolbarName = toolbar.getAttribute("toolbarname"); if (toolbarName) { let menuItem = document.createElement("menuitem"); let hidingAttribute = toolbar.getAttribute("type") == "menubar" ? "autohide" : "collapsed"; menuItem.setAttribute("id", "toggle_" + toolbar.id); menuItem.setAttribute("toolbarId", toolbar.id); menuItem.setAttribute("type", "checkbox"); menuItem.setAttribute("label", toolbarName); menuItem.setAttribute("checked", toolbar.getAttribute(hidingAttribute) != "true"); - if (popup.id != "appmenu_customizeMenu") - menuItem.setAttribute("accesskey", toolbar.getAttribute("accesskey")); if (popup.id != "toolbar-context-menu") menuItem.setAttribute("key", toolbar.getAttribute("key")); popup.insertBefore(menuItem, firstMenuItem); menuItem.addEventListener("command", onViewToolbarCommand, false); } } @@ -4556,205 +4318,249 @@ function setToolbarVisibility(toolbar, i toolbar.setAttribute(hidingAttribute, !isVisible); document.persist(toolbar.id, hidingAttribute); PlacesToolbarHelper.init(); BookmarkingUI.onToolbarVisibilityChange(); gBrowser.updateWindowResizers(); -#ifdef MENUBAR_CAN_AUTOHIDE - updateAppButtonDisplay(); +#ifdef CAN_DRAW_IN_TITLEBAR + updateTitlebarDisplay(); #endif } -var TabsOnTop = { - init: function TabsOnTop_init() { - Services.prefs.addObserver(this._prefName, this, false); - - // Only show the toggle UI if the user disabled tabs on top. - if (Services.prefs.getBoolPref(this._prefName)) { - for (let item of document.querySelectorAll("menuitem[command=cmd_ToggleTabsOnTop]")) - item.parentNode.removeChild(item); - } - }, - - uninit: function TabsOnTop_uninit() { - Services.prefs.removeObserver(this._prefName, this); - }, - - toggle: function () { - this.enabled = !Services.prefs.getBoolPref(this._prefName); - }, - - syncUI: function () { - let userEnabled = Services.prefs.getBoolPref(this._prefName); - let enabled = userEnabled && gBrowser.tabContainer.visible; - - document.getElementById("cmd_ToggleTabsOnTop") - .setAttribute("checked", userEnabled); - - document.documentElement.setAttribute("tabsontop", enabled); - document.getElementById("navigator-toolbox").setAttribute("tabsontop", enabled); - document.getElementById("TabsToolbar").setAttribute("tabsontop", enabled); - document.getElementById("nav-bar").setAttribute("tabsontop", enabled); - gBrowser.tabContainer.setAttribute("tabsontop", enabled); - TabsInTitlebar.allowedBy("tabs-on-top", enabled); - }, - - get enabled () { - return gNavToolbox.getAttribute("tabsontop") == "true"; - }, - - set enabled (val) { - Services.prefs.setBoolPref(this._prefName, !!val); - return val; - }, - - observe: function (subject, topic, data) { - if (topic == "nsPref:changed") - this.syncUI(); - }, - - _prefName: "browser.tabs.onTop" -} - var TabsInTitlebar = { init: function () { #ifdef CAN_DRAW_IN_TITLEBAR this._readPref(); Services.prefs.addObserver(this._prefName, this, false); - // Don't trust the initial value of the sizemode attribute; wait for - // the resize event (handled in tabbrowser.xml). - this.allowedBy("sizemode", false); - + // We need to update the appearance of the titlebar when the menu changes + // from the active to the inactive state. We can't, however, rely on + // DOMMenuBarInactive, because the menu fires this event and then removes + // the inactive attribute after an event-loop spin. + // + // Because updating the appearance involves sampling the heights and margins + // of various elements, it's important that the layout be more or less + // settled before updating the titlebar. So instead of listening to + // DOMMenuBarActive and DOMMenuBarInactive, we use a MutationObserver to + // watch the "invalid" attribute directly. + let menu = document.getElementById("toolbar-menubar"); + this._menuObserver = new MutationObserver(this._onMenuMutate); + this._menuObserver.observe(menu, {attributes: true}); this._initialized = true; #endif }, allowedBy: function (condition, allow) { #ifdef CAN_DRAW_IN_TITLEBAR if (allow) { if (condition in this._disallowed) { delete this._disallowed[condition]; - this._update(); + this._update(true); } } else { if (!(condition in this._disallowed)) { this._disallowed[condition] = null; - this._update(); + this._update(true); } } #endif }, + updateAppearance: function updateAppearance(aForce) { +#ifdef CAN_DRAW_IN_TITLEBAR + this._update(aForce); +#endif + }, + get enabled() { return document.documentElement.getAttribute("tabsintitlebar") == "true"; }, #ifdef CAN_DRAW_IN_TITLEBAR observe: function (subject, topic, data) { if (topic == "nsPref:changed") this._readPref(); }, + _onMenuMutate: function (aMutations) { + // We don't care about restored windows, since the menu shouldn't be + // pushing the tab-strip down. + if (document.documentElement.getAttribute("sizemode") == "normal") { + return; + } + + for (let mutation of aMutations) { + if (mutation.attributeName == "inactive" || + mutation.attributeName == "autohide") { + TabsInTitlebar._update(true); + return; + } + } + }, + _initialized: false, _disallowed: {}, _prefName: "browser.tabs.drawInTitlebar", + _lastSizeMode: null, _readPref: function () { this.allowedBy("pref", Services.prefs.getBoolPref(this._prefName)); }, - _update: function () { + _update: function (aForce=false) { function $(id) document.getElementById(id); function rect(ele) ele.getBoundingClientRect(); if (!this._initialized || window.fullScreen) return; let allowed = true; + + if (!aForce) { + // _update is called on resize events, because the window is not ready + // after sizemode events. However, we only care about the event when the + // sizemode is different from the last time we updated the appearance of + // the tabs in the titlebar. + let sizemode = document.documentElement.getAttribute("sizemode"); + if (this._lastSizeMode == sizemode) { + return; + } + this._lastSizeMode = sizemode; + } + for (let something in this._disallowed) { allowed = false; break; } - if (allowed == this.enabled) - return; + function $(id) document.getElementById(id); let titlebar = $("titlebar"); + let titlebarContent = $("titlebar-content"); + let menubar = $("toolbar-menubar"); + + // Reset the margins and padding that _update modifies so that we can take + // accurate measurements. + titlebarContent.style.marginBottom = ""; + titlebar.style.marginBottom = ""; + menubar.style.paddingBottom = ""; if (allowed) { - let tabsToolbar = $("TabsToolbar"); - -#ifdef MENUBAR_CAN_AUTOHIDE - let appmenuButtonBox = $("appmenu-button-container"); - this._sizePlaceholder("appmenu-button", rect(appmenuButtonBox).width); -#endif + // We set the tabsintitlebar attribute first so that our CSS for + // tabsintitlebar manifests before we do our measurements. + document.documentElement.setAttribute("tabsintitlebar", "true"); + let captionButtonsBox = $("titlebar-buttonbox"); this._sizePlaceholder("caption-buttons", rect(captionButtonsBox).width); - - let tabsToolbarRect = rect(tabsToolbar); - let titlebarTop = rect($("titlebar-content")).top; - titlebar.style.marginBottom = - Math.min(tabsToolbarRect.top - titlebarTop, - tabsToolbarRect.height) + "px"; - - document.documentElement.setAttribute("tabsintitlebar", "true"); - - if (!this._draghandle) { +#ifdef XP_MACOSX + let fullscreenButton = $("titlebar-fullscreen-button"); + this._sizePlaceholder("fullscreen-button", rect(fullscreenButton).width); +#endif + let titlebarContentHeight = rect(titlebarContent).height; + let menuHeight = this._outerHeight(menubar); + + // If the titlebar is taller than the menubar, add more padding to the + // bottom of the menubar so that it matches. + if (menuHeight && titlebarContentHeight > menuHeight) { + let menuTitlebarDelta = titlebarContentHeight - menuHeight; + menubar.style.paddingBottom = menuTitlebarDelta + "px"; + menuHeight += menuTitlebarDelta; + } + + // Next, we calculate how much we need to stretch the titlebar down to + // go all the way to the bottom of the tab strip. + let tabsToolbar = $("TabsToolbar"); + let tabAndMenuHeight = this._outerHeight(tabsToolbar) + menuHeight; + titlebarContent.style.marginBottom = tabAndMenuHeight + "px"; + + // Finally, we have to determine how much to bring up the elements below + // the titlebar. We start with a baseHeight of tabAndMenuHeight, to offset + // the amount we added to the titlebar content. Then, we have two cases: + // + // 1) The titlebar is larger than the tabAndMenuHeight. This can happen in + // large font mode with the menu autohidden. In this case, we want to + // add tabAndMenuHeight, since this should line up the bottom of the + // tabstrip with the bottom of the titlebar. + // + // 2) The titlebar is equal to or smaller than the tabAndMenuHeight. This + // is the more common case, and occurs with normal font sizes. In this + // case, we want to bring the menu and tabstrip right up to the top of + // the titlebar, so we add the titlebarContentHeight to the baseHeight. + let baseHeight = tabAndMenuHeight; + baseHeight += (titlebarContentHeight > tabAndMenuHeight) ? tabAndMenuHeight + : titlebarContentHeight; + titlebar.style.marginBottom = "-" + baseHeight + "px"; + + if (!this._draghandles) { + this._draghandles = {}; let tmp = {}; Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp); - this._draghandle = new tmp.WindowDraggingElement(tabsToolbar); - this._draghandle.mouseDownCheck = function () { + + let mouseDownCheck = function () { return !this._dragBindingAlive && TabsInTitlebar.enabled; }; + + this._draghandles.tabsToolbar = new tmp.WindowDraggingElement(tabsToolbar); + this._draghandles.tabsToolbar.mouseDownCheck = mouseDownCheck; + + this._draghandles.navToolbox = new tmp.WindowDraggingElement(gNavToolbox); + this._draghandles.navToolbox.mouseDownCheck = mouseDownCheck; } } else { document.documentElement.removeAttribute("tabsintitlebar"); - - titlebar.style.marginBottom = ""; } }, _sizePlaceholder: function (type, width) { Array.forEach(document.querySelectorAll(".titlebar-placeholder[type='"+ type +"']"), function (node) { node.width = width; }); }, + + /** + * Retrieve the height of an element, including its top and bottom + * margins. + * + * @param ele + * The element to measure. + * @return + * The height and margins as an integer. If the height of the element + * is 0, then this returns 0, regardless of what the margins are. + */ + _outerHeight: function (ele) { + let cstyle = document.defaultView.getComputedStyle(ele); + let margins = parseInt(cstyle.marginTop) + parseInt(cstyle.marginBottom); + let height = ele.getBoundingClientRect().height; + return height > 0 ? Math.abs(height + margins) : 0; + }, #endif uninit: function () { #ifdef CAN_DRAW_IN_TITLEBAR this._initialized = false; Services.prefs.removeObserver(this._prefName, this); + this._menuObserver.disconnect(); #endif } }; -#ifdef MENUBAR_CAN_AUTOHIDE -function updateAppButtonDisplay() { - var displayAppButton = - !gInPrintPreviewMode && - window.menubar.visible && - document.getElementById("toolbar-menubar").getAttribute("autohide") == "true"; - #ifdef CAN_DRAW_IN_TITLEBAR - document.getElementById("titlebar").hidden = !displayAppButton; - - if (displayAppButton) +function updateTitlebarDisplay() { + let drawInTitlebar = !gInPrintPreviewMode && window.toolbar.visible; + document.getElementById("titlebar").hidden = !drawInTitlebar; + + if (drawInTitlebar) document.documentElement.setAttribute("chromemargin", "0,2,2,2"); else document.documentElement.removeAttribute("chromemargin"); - TabsInTitlebar.allowedBy("drawing-in-titlebar", displayAppButton); -#else - document.getElementById("appmenu-toolbar-button").hidden = - !displayAppButton; -#endif + TabsInTitlebar.allowedBy("drawing-in-titlebar", drawInTitlebar); } #endif #ifdef CAN_DRAW_IN_TITLEBAR function onTitlebarMaxClick() { if (window.windowState == window.STATE_MAXIMIZED) window.restore(); else @@ -6967,39 +6773,32 @@ let gPrivateBrowsingUI = { return; } // Disable the Clear Recent History... menu item when in PB mode // temporary fix until bug 463607 is fixed document.getElementById("Tools:Sanitize").setAttribute("disabled", "true"); if (window.location.href == getBrowserURL()) { -#ifdef XP_MACOSX - if (!PrivateBrowsingUtils.permanentPrivateBrowsing) { - document.documentElement.setAttribute("drawintitlebar", true); - } -#endif - // Adjust the window's title let docElement = document.documentElement; if (!PrivateBrowsingUtils.permanentPrivateBrowsing) { docElement.setAttribute("title", docElement.getAttribute("title_privatebrowsing")); docElement.setAttribute("titlemodifier", docElement.getAttribute("titlemodifier_privatebrowsing")); } docElement.setAttribute("privatebrowsingmode", PrivateBrowsingUtils.permanentPrivateBrowsing ? "permanent" : "temporary"); gBrowser.updateTitlebar(); if (PrivateBrowsingUtils.permanentPrivateBrowsing) { // Adjust the New Window menu entries [ { normal: "menu_newNavigator", private: "menu_newPrivateWindow" }, - { normal: "appmenu_newNavigator", private: "appmenu_newPrivateWindow" }, ].forEach(function(menu) { let newWindow = document.getElementById(menu.normal); let newPrivateWindow = document.getElementById(menu.private); if (newWindow && newPrivateWindow) { newPrivateWindow.hidden = true; newWindow.label = newPrivateWindow.label; newWindow.accessKey = newPrivateWindow.accessKey; newWindow.command = newPrivateWindow.command; @@ -7203,21 +7002,16 @@ function duplicateTabIn(aTab, where, del // A background tab has been opened, nothing else to do here. break; case "tab": gBrowser.selectedTab = newTab; break; } } -function toggleAddonBar() { - let addonBar = document.getElementById("addon-bar"); - setToolbarVisibility(addonBar, addonBar.collapsed); -} - var Scratchpad = { prefEnabledName: "devtools.scratchpad.enabled", openScratchpad: function SP_openScratchpad() { return this.ScratchpadManager.openScratchpad(); } };
--- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -4,52 +4,57 @@ # # 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/. <?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?> <?xml-stylesheet href="chrome://browser/content/places/places.css" type="text/css"?> <?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/customizableui/panelUIOverlay.css" type="text/css"?> <?xml-stylesheet href="chrome://browser/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/browser-lightweightTheme.css" type="text/css"?> <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?> <?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?> <?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> # All DTD information is stored in a separate file so that it can be shared by # hiddenWindow.xul. #include browser-doctype.inc <window id="main-window" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" + xmlns:html="http://www.w3.org/1999/xhtml" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="gBrowserInit.onLoad()" onunload="gBrowserInit.onUnload()" onclose="return WindowIsClosing();" title="&mainWindow.title;@PRE_RELEASE_SUFFIX@" title_normal="&mainWindow.title;@PRE_RELEASE_SUFFIX@" #ifdef XP_MACOSX title_privatebrowsing="&mainWindow.title;@PRE_RELEASE_SUFFIX@&mainWindow.titlemodifiermenuseparator;&mainWindow.titlePrivateBrowsingSuffix;" titledefault="&mainWindow.title;@PRE_RELEASE_SUFFIX@" titlemodifier="" titlemodifier_normal="" titlemodifier_privatebrowsing="&mainWindow.titlePrivateBrowsingSuffix;" + chromemargin="0,-1,-1,-1" #else title_privatebrowsing="&mainWindow.titlemodifier;@PRE_RELEASE_SUFFIX@ &mainWindow.titlePrivateBrowsingSuffix;" titlemodifier="&mainWindow.titlemodifier;@PRE_RELEASE_SUFFIX@" titlemodifier_normal="&mainWindow.titlemodifier;@PRE_RELEASE_SUFFIX@" titlemodifier_privatebrowsing="&mainWindow.titlemodifier;@PRE_RELEASE_SUFFIX@ &mainWindow.titlePrivateBrowsingSuffix;" #endif titlemenuseparator="&mainWindow.titlemodifiermenuseparator;" lightweightthemes="true" lightweightthemesfooter="browser-bottombox" windowtype="navigator:browser" macanimationtype="document" screenX="4" screenY="4" fullscreenbutton="true" + sizemode="normal" persist="screenX screenY width height sizemode"> # All JS files which are not content (only) dependent that browser.xul # wishes to include *must* go into the global-scripts.inc file # so that they can be shared by macBrowserOverlay.xul. #include global-scripts.inc <script type="application/javascript" src="chrome://browser/content/nsContextMenu.js"/> @@ -236,20 +241,16 @@ rolluponmousewheel="true" consumeoutsideclicks="false" noautofocus="true" position="topcenter topright"/> <menupopup id="toolbar-context-menu" onpopupshowing="onViewToolbarsPopupShowing(event);"> <menuseparator/> - <menuitem command="cmd_ToggleTabsOnTop" - type="checkbox" - label="&viewTabsOnTop.label;" - accesskey="&viewTabsOnTop.accesskey;"/> <menuitem command="cmd_CustomizeToolbars" label="&viewCustomizeToolbar.label;" accesskey="&viewCustomizeToolbar.accesskey;"/> </menupopup> <menupopup id="blockedPopupOptions" onpopupshowing="gPopupBlockerObserver.fillPopupList(event);" onpopuphiding="gPopupBlockerObserver.onPopupHiding(event);"> @@ -357,20 +358,16 @@ <hbox pack="center"> <button id="ctrlTab-showAll" class="ctrlTab-preview" noicon="true"/> </hbox> </panel> <!-- Bookmarks and history tooltip --> <tooltip id="bhTooltip"/> - <panel id="customizeToolbarSheetPopup" - noautohide="true" - sheetstyle="&dialog.dimensions;"/> - <tooltip id="tabbrowser-tab-tooltip" onpopupshowing="gBrowser.createTooltip(event);"/> <tooltip id="back-button-tooltip"> <label class="tooltip-label" value="&backButton.tooltip;"/> #ifdef XP_MACOSX <label class="tooltip-label" value="&backForwardButtonMenuMac.tooltip;"/> #else <label class="tooltip-label" value="&backForwardButtonMenu.tooltip;"/> @@ -383,39 +380,37 @@ <label class="tooltip-label" value="&backForwardButtonMenuMac.tooltip;"/> #else <label class="tooltip-label" value="&backForwardButtonMenu.tooltip;"/> #endif </tooltip> #include popup-notifications.inc +#include ../../components/customizableui/content/panelUI.inc.xul </popupset> #ifdef CAN_DRAW_IN_TITLEBAR <vbox id="titlebar"> <hbox id="titlebar-content"> -#ifdef MENUBAR_CAN_AUTOHIDE - <hbox id="appmenu-button-container"> - <button id="appmenu-button" - type="menu" - label="&brandShortName;" - style="-moz-user-focus: ignore;"> -#include browser-appmenu.inc - </button> - </hbox> + <spacer id="titlebar-spacer" flex="1"/> + <hbox id="titlebar-buttonbox-container" align="start" +#ifdef XP_MACOSX + ordinal="0" #endif - <spacer id="titlebar-spacer" flex="1"/> - <hbox id="titlebar-buttonbox-container" align="start"> + > <hbox id="titlebar-buttonbox"> <toolbarbutton class="titlebar-button" id="titlebar-min" oncommand="window.minimize();"/> <toolbarbutton class="titlebar-button" id="titlebar-max" oncommand="onTitlebarMaxClick();"/> <toolbarbutton class="titlebar-button" id="titlebar-close" command="cmd_closeWindow"/> </hbox> </hbox> +#ifdef XP_MACOSX + <hbox id="titlebar-fullscreen-button" ordinal="1000"/> +#endif </hbox> </vbox> #endif <deck flex="1" id="tab-view-deck"> <vbox flex="1" id="browser-panel"> <toolbox id="navigator-toolbox" @@ -433,325 +428,438 @@ context="toolbar-context-menu"> <toolbaritem id="menubar-items" align="center"> # The entire main menubar is placed into browser-menubar.inc, so that it can be shared by # hiddenWindow.xul. #include browser-menubar.inc </toolbaritem> #ifdef CAN_DRAW_IN_TITLEBAR - <hbox class="titlebar-placeholder" type="appmenu-button" ordinal="0"/> - <hbox class="titlebar-placeholder" type="caption-buttons" ordinal="1000"/> + <hbox class="titlebar-placeholder" type="caption-buttons" +#ifndef XP_MACOSX + ordinal="1000" +#endif + /> + +#ifdef XP_MACOSX + <hbox class="titlebar-placeholder" type="fullscreen-button"/> +#endif +#endif + </toolbar> + + <toolbar id="TabsToolbar" + class="toolbar-primary" + fullscreentoolbar="true" + customizable="true" + mode="icons" lockmode="true" + iconsize="small" defaulticonsize="small" lockiconsize="true" + aria-label="&tabsToolbar.label;" + context="toolbar-context-menu" + defaultset="tabbrowser-tabs,new-tab-button,alltabs-button,tabs-closebutton" + collapsed="true"> + + <tabs id="tabbrowser-tabs" + class="tabbrowser-tabs" + tabbrowser="content" + flex="1" + setfocus="false" + tooltip="tabbrowser-tab-tooltip" + stopwatchid="FX_TAB_CLICK_MS"> + <tab class="tabbrowser-tab" selected="true" fadein="true"/> + </tabs> + + <toolbarbutton id="new-tab-button" + class="toolbarbutton-1 chromeclass-toolbar-additional" + label="&tabCmd.label;" + command="cmd_newNavigatorTab" + onclick="checkForMiddleClick(this, event);" + tooltiptext="&newTabButton.tooltip;" + ondrop="newTabButtonObserver.onDrop(event)" + ondragover="newTabButtonObserver.onDragOver(event)" + ondragenter="newTabButtonObserver.onDragOver(event)" + ondragexit="newTabButtonObserver.onDragExit(event)" + removable="true"/> + + <toolbarbutton id="alltabs-button" + class="toolbarbutton-1 chromeclass-toolbar-additional tabs-alltabs-button" + type="menu" + label="&listAllTabs.label;" + tooltiptext="&listAllTabs.label;" + removable="true"> + <menupopup id="alltabs-popup" + position="after_end"> + <menuitem id="menu_tabview" + class="menuitem-iconic" + key="key_tabview" + label="&viewTabGroups.label;" + command="Browser:ToggleTabView" + observes="tabviewGroupsNumber"/> + <menuseparator id="alltabs-popup-separator"/> + </menupopup> + </toolbarbutton> + + <toolbarbutton id="tabs-closebutton" + class="close-button tabs-closebutton close-icon" + command="cmd_close" + label="&closeTab.label;" + tooltiptext="&closeTab.label;"/> + +#ifdef CAN_DRAW_IN_TITLEBAR + <hbox class="titlebar-placeholder" type="caption-buttons" +#ifndef XP_MACOSX + ordinal="1000" +#endif + /> + +#ifdef XP_MACOSX + <hbox class="titlebar-placeholder" type="fullscreen-button"/> +#endif #endif </toolbar> + <!-- + CAVEAT EMPTOR + Should you need to add items to the toolbar here, make sure to also add them + to the default placements of buttons in CustomizableUI.jsm, so the + customization code doesn't get confused. + --> <toolbar id="nav-bar" class="toolbar-primary chromeclass-toolbar" - toolbarname="&navbarCmd.label;" accesskey="&navbarCmd.accesskey;" + aria-label="&navbarCmd.label;" fullscreentoolbar="true" mode="icons" customizable="true" iconsize="large" - defaultset="unified-back-forward-button,urlbar-container,reload-button,stop-button,search-container,webrtc-status-button,bookmarks-menu-button,downloads-button,home-button,window-controls" + defaultset="unified-back-forward-button,urlbar-container,reload-button,stop-button,search-container,webrtc-status-button,bookmarks-menu-button,downloads-button,home-button,social-share-button" + customizationtarget="nav-bar-customizationtarget" + overflowbutton="nav-bar-overflow-button" context="toolbar-context-menu"> - <toolbaritem id="unified-back-forward-button" class="chromeclass-toolbar-additional" - context="backForwardMenu" removable="true" - forwarddisabled="true" - title="&backForwardItem.title;"> - <toolbarbutton id="back-button" class="toolbarbutton-1" - label="&backCmd.label;" - command="Browser:BackOrBackDuplicate" - onclick="checkForMiddleClick(this, event);" - tooltip="back-button-tooltip"/> - <toolbarbutton id="forward-button" class="toolbarbutton-1" - label="&forwardCmd.label;" - command="Browser:ForwardOrForwardDuplicate" - onclick="checkForMiddleClick(this, event);" - tooltip="forward-button-tooltip"/> - <dummyobservertarget hidden="true" - onbroadcast="if (this.getAttribute('disabled') == 'true') - this.parentNode.setAttribute('forwarddisabled', 'true'); - else - this.parentNode.removeAttribute('forwarddisabled');"> - <observes element="Browser:ForwardOrForwardDuplicate" attribute="disabled"/> - </dummyobservertarget> - </toolbaritem> + <hbox id="nav-bar-customizationtarget" class="customization-target" flex="1"> + <toolbaritem id="unified-back-forward-button" class="chromeclass-toolbar-additional" + context="backForwardMenu" removable="false" + forwarddisabled="true" + title="&backForwardItem.title;" + nooverflow="true"> + <toolbarbutton id="back-button" class="toolbarbutton-1" + label="&backCmd.label;" + command="Browser:BackOrBackDuplicate" + onclick="checkForMiddleClick(this, event);" + tooltip="back-button-tooltip"/> + <toolbarbutton id="forward-button" class="toolbarbutton-1" + label="&forwardCmd.label;" + command="Browser:ForwardOrForwardDuplicate" + onclick="checkForMiddleClick(this, event);" + tooltip="forward-button-tooltip"/> + <dummyobservertarget hidden="true" + onbroadcast="if (this.getAttribute('disabled') == 'true') + this.parentNode.setAttribute('forwarddisabled', 'true'); + else + this.parentNode.removeAttribute('forwarddisabled');"> + <observes element="Browser:ForwardOrForwardDuplicate" attribute="disabled"/> + </dummyobservertarget> + </toolbaritem> - <toolbaritem id="urlbar-container" align="center" flex="400" persist="width" combined="true" - title="&locationItem.title;" class="chromeclass-location" removable="true"> - <textbox id="urlbar" flex="1" - placeholder="&urlbar.placeholder2;" - type="autocomplete" - autocompletesearch="urlinline history" - autocompletesearchparam="enable-actions" - autocompletepopup="PopupAutoCompleteRichResult" - completeselectedindex="true" - tabscrolling="true" - showcommentcolumn="true" - showimagecolumn="true" - enablehistory="true" - maxrows="6" - newlines="stripsurroundingwhitespace" - oninput="gBrowser.userTypedValue = this.value;" - ontextentered="this.handleCommand(param);" - ontextreverted="return this.handleRevert();" - pageproxystate="invalid" - onfocus="document.getElementById('identity-box').style.MozUserFocus= 'normal'" - onblur="setTimeout(function() document.getElementById('identity-box').style.MozUserFocus = '', 0);"> - <box id="notification-popup-box" hidden="true" align="center"> - <image id="default-notification-icon" class="notification-anchor-icon" role="button"/> - <image id="identity-notification-icon" class="notification-anchor-icon" role="button"/> - <image id="geo-notification-icon" class="notification-anchor-icon" role="button"/> - <image id="addons-notification-icon" class="notification-anchor-icon" role="button"/> - <image id="indexedDB-notification-icon" class="notification-anchor-icon" role="button"/> - <image id="password-notification-icon" class="notification-anchor-icon" role="button"/> - <image id="webapps-notification-icon" class="notification-anchor-icon" role="button"/> - <image id="plugins-notification-icon" class="notification-anchor-icon" role="button"/> - <image id="web-notifications-notification-icon" class="notification-anchor-icon" role="button"/> - <image id="blocked-plugins-notification-icon" class="notification-anchor-icon" role="button"/> - <image id="plugin-install-notification-icon" class="notification-anchor-icon" role="button"/> - <image id="mixed-content-blocked-notification-icon" class="notification-anchor-icon" role="button"/> - <image id="webRTC-shareDevices-notification-icon" class="notification-anchor-icon" role="button"/> - <image id="webRTC-sharingDevices-notification-icon" class="notification-anchor-icon" role="button"/> - <image id="pointerLock-notification-icon" class="notification-anchor-icon" role="button"/> - <image id="servicesInstall-notification-icon" class="notification-anchor-icon" role="button"/> - </box> - <!-- Use onclick instead of normal popup= syntax since the popup - code fires onmousedown, and hence eats our favicon drag events. - We only add the identity-box button to the tab order when the location bar - has focus, otherwise pressing F6 focuses it instead of the location bar --> - <box id="identity-box" role="button" - align="center" - onclick="gIdentityHandler.handleIdentityButtonEvent(event);" - onkeypress="gIdentityHandler.handleIdentityButtonEvent(event);" - ondragstart="gIdentityHandler.onDragStart(event);"> - <image id="page-proxy-favicon" - onclick="PageProxyClickHandler(event);" - pageproxystate="invalid"/> - <hbox id="identity-icon-labels"> - <label id="identity-icon-label" class="plain" flex="1"/> - <label id="identity-icon-country-label" class="plain"/> + <toolbaritem id="urlbar-container" align="center" flex="400" persist="width" combined="true" + title="&locationItem.title;" class="chromeclass-location" removable="false" nooverflow="true"> + <textbox id="urlbar" flex="1" + placeholder="&urlbar.placeholder2;" + type="autocomplete" + autocompletesearch="urlinline history" + autocompletesearchparam="enable-actions" + autocompletepopup="PopupAutoCompleteRichResult" + completeselectedindex="true" + tabscrolling="true" + showcommentcolumn="true" + showimagecolumn="true" + enablehistory="true" + maxrows="6" + newlines="stripsurroundingwhitespace" + oninput="gBrowser.userTypedValue = this.value;" + ontextentered="this.handleCommand(param);" + ontextreverted="return this.handleRevert();" + pageproxystate="invalid" + onfocus="document.getElementById('identity-box').style.MozUserFocus= 'normal'" + onblur="setTimeout(function() document.getElementById('identity-box').style.MozUserFocus = '', 0);"> + <box id="notification-popup-box" hidden="true" align="center"> + <image id="default-notification-icon" class="notification-anchor-icon" role="button"/> + <image id="identity-notification-icon" class="notification-anchor-icon" role="button"/> + <image id="geo-notification-icon" class="notification-anchor-icon" role="button"/> + <image id="addons-notification-icon" class="notification-anchor-icon" role="button"/> + <image id="indexedDB-notification-icon" class="notification-anchor-icon" role="button"/> + <image id="password-notification-icon" class="notification-anchor-icon" role="button"/> + <image id="webapps-notification-icon" class="notification-anchor-icon" role="button"/> + <image id="plugins-notification-icon" class="notification-anchor-icon" role="button"/> + <image id="web-notifications-notification-icon" class="notification-anchor-icon" role="button"/> + <image id="blocked-plugins-notification-icon" class="notification-anchor-icon" role="button"/> + <image id="plugin-install-notification-icon" class="notification-anchor-icon" role="button"/> + <image id="mixed-content-blocked-notification-icon" class="notification-anchor-icon" role="button"/> + <image id="webRTC-shareDevices-notification-icon" class="notification-anchor-icon" role="button"/> + <image id="webRTC-sharingDevices-notification-icon" class="notification-anchor-icon" role="button"/> + <image id="pointerLock-notification-icon" class="notification-anchor-icon" role="button"/> + <image id="servicesInstall-notification-icon" class="notification-anchor-icon" role="button"/> + </box> + <!-- Use onclick instead of normal popup= syntax since the popup + code fires onmousedown, and hence eats our favicon drag events. + We only add the identity-box button to the tab order when the location bar + has focus, otherwise pressing F6 focuses it instead of the location bar --> + <box id="identity-box" role="button" + align="center" + onclick="gIdentityHandler.handleIdentityButtonEvent(event);" + onkeypress="gIdentityHandler.handleIdentityButtonEvent(event);" + ondragstart="gIdentityHandler.onDragStart(event);"> + <image id="page-proxy-favicon" + onclick="PageProxyClickHandler(event);" + pageproxystate="invalid"/> + <hbox id="identity-icon-labels"> + <label id="identity-icon-label" class="plain" flex="1"/> + <label id="identity-icon-country-label" class="plain"/> + </hbox> + </box> + <box id="urlbar-display-box" align="center"> + <label id="urlbar-display" value="&urlbar.switchToTab.label;"/> + </box> + <hbox id="urlbar-icons"> + <image id="page-report-button" + class="urlbar-icon" + hidden="true" + tooltiptext="&pageReportIcon.tooltip;" + onclick="gPopupBlockerObserver.onReportButtonClick(event);"/> + <image id="go-button" + class="urlbar-icon" + tooltiptext="&goEndCap.tooltip;" + onclick="gURLBar.handleCommand(event);"/> </hbox> - </box> - <box id="urlbar-display-box" align="center"> - <label id="urlbar-display" value="&urlbar.switchToTab.label;"/> - </box> - <hbox id="urlbar-icons"> - <image id="page-report-button" - class="urlbar-icon" - hidden="true" - tooltiptext="&pageReportIcon.tooltip;" - onclick="gPopupBlockerObserver.onReportButtonClick(event);"/> - <image id="star-button" - class="urlbar-icon" - onclick="BookmarkingUI.onCommand(event);"/> - <image id="go-button" - class="urlbar-icon" - tooltiptext="&goEndCap.tooltip;" - onclick="gURLBar.handleCommand(event);"/> - </hbox> - <toolbarbutton id="urlbar-go-button" - class="chromeclass-toolbar-additional" - onclick="gURLBar.handleCommand(event);" - tooltiptext="&goEndCap.tooltip;"/> - <toolbarbutton id="urlbar-reload-button" - class="chromeclass-toolbar-additional" - command="Browser:ReloadOrDuplicate" - onclick="checkForMiddleClick(this, event);" - tooltiptext="&reloadButton.tooltip;"/> - <toolbarbutton id="urlbar-stop-button" - class="chromeclass-toolbar-additional" - command="Browser:Stop" - tooltiptext="&stopButton.tooltip;"/> - </textbox> - </toolbaritem> + <toolbarbutton id="urlbar-go-button" + class="chromeclass-toolbar-additional" + onclick="gURLBar.handleCommand(event);" + tooltiptext="&goEndCap.tooltip;"/> + <toolbarbutton id="urlbar-reload-button" + class="chromeclass-toolbar-additional" + command="Browser:ReloadOrDuplicate" + onclick="checkForMiddleClick(this, event);" + tooltiptext="&reloadButton.tooltip;"/> + <toolbarbutton id="urlbar-stop-button" + class="chromeclass-toolbar-additional" + command="Browser:Stop" + tooltiptext="&stopButton.tooltip;"/> + </textbox> + </toolbaritem> - <toolbarbutton id="reload-button" class="toolbarbutton-1 chromeclass-toolbar-additional" - label="&reloadCmd.label;" removable="true" - command="Browser:ReloadOrDuplicate" - onclick="checkForMiddleClick(this, event);" - tooltiptext="&reloadButton.tooltip;"/> + <toolbarbutton id="reload-button" class="toolbarbutton-1 chromeclass-toolbar-additional" + label="&reloadCmd.label;" removable="true" + command="Browser:ReloadOrDuplicate" + onclick="checkForMiddleClick(this, event);" + tooltiptext="&reloadButton.tooltip;" + nooverflow="true"/> - <toolbarbutton id="stop-button" class="toolbarbutton-1 chromeclass-toolbar-additional" - label="&stopCmd.label;" removable="true" - command="Browser:Stop" - tooltiptext="&stopButton.tooltip;"/> + <toolbarbutton id="stop-button" class="toolbarbutton-1 chromeclass-toolbar-additional" + label="&stopCmd.label;" removable="true" + command="Browser:Stop" + tooltiptext="&stopButton.tooltip;" + nooverflow="true"/> - <toolbaritem id="search-container" title="&searchItem.title;" - align="center" class="chromeclass-toolbar-additional" - flex="100" persist="width" removable="true"> - <searchbar id="searchbar" flex="1"/> - </toolbaritem> + <toolbaritem id="search-container" title="&searchItem.title;" + align="center" class="chromeclass-toolbar-additional" + flex="100" persist="width" removable="true"> + <searchbar id="searchbar" flex="1"/> + </toolbaritem> - <toolbarbutton id="webrtc-status-button" - class="toolbarbutton-1 chromeclass-toolbar-additional" - type="menu" - hidden="true" - orient="horizontal" - label="&webrtcIndicatorButton.label;" - tooltiptext="&webrtcIndicatorButton.tooltip;"> - <menupopup onpopupshowing="WebrtcIndicator.fillPopup(this);" - onpopuphiding="WebrtcIndicator.clearPopup(this);" - oncommand="WebrtcIndicator.menuCommand(event.target);"/> - </toolbarbutton> + <toolbarbutton id="webrtc-status-button" + class="toolbarbutton-1 chromeclass-toolbar-additional" + type="menu" + hidden="true" + orient="horizontal" + label="&webrtcIndicatorButton.label;" + tooltiptext="&webrtcIndicatorButton.tooltip;" + nooverflow="true"> + <menupopup onpopupshowing="WebrtcIndicator.fillPopup(this);" + onpopuphiding="WebrtcIndicator.clearPopup(this);" + oncommand="WebrtcIndicator.menuCommand(event.target);"/> + </toolbarbutton> - <toolbarbutton id="bookmarks-menu-button" - class="toolbarbutton-1 chromeclass-toolbar-additional" - persist="class" - removable="true" - type="menu" - label="&bookmarksMenuButton.label;" - tooltiptext="&bookmarksMenuButton.tooltip;" - ondragenter="PlacesMenuDNDHandler.onDragEnter(event);" - ondragover="PlacesMenuDNDHandler.onDragOver(event);" - ondragleave="PlacesMenuDNDHandler.onDragLeave(event);" - ondrop="PlacesMenuDNDHandler.onDrop(event);"> - <menupopup id="BMB_bookmarksPopup" - placespopup="true" - context="placesContext" - openInTabs="children" - oncommand="BookmarksEventHandler.onCommand(event, this.parentNode._placesView);" - onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);" - onpopupshowing="BookmarkingUI.onPopupShowing(event); - if (!this.parentNode._placesView) - new PlacesMenu(event, 'place:folder=BOOKMARKS_MENU');" - tooltip="bhTooltip" popupsinherittooltip="true"> - <menuitem id="BMB_viewBookmarksToolbar" - placesanonid="view-toolbar" - toolbarId="PersonalToolbar" - type="checkbox" - oncommand="onViewToolbarCommand(event)" - label="&viewBookmarksToolbar.label;"/> - <menuseparator/> - <menuitem id="BMB_bookmarksShowAll" - label="&showAllBookmarks2.label;" - command="Browser:ShowAllBookmarks" - key="manBookmarkKb"/> - <menuseparator/> - <menuitem id="BMB_bookmarkThisPage" + <toolbarbutton id="bookmarks-menu-button" + class="toolbarbutton-1 chromeclass-toolbar-additional" + persist="class" + removable="true" + type="menu-button" + label="&bookmarksMenuButton.label;" + tooltiptext="&bookmarksMenuButton.tooltip;" + ondragenter="PlacesMenuDNDHandler.onDragEnter(event);" + ondragover="PlacesMenuDNDHandler.onDragOver(event);" + ondragleave="PlacesMenuDNDHandler.onDragLeave(event);" + ondrop="PlacesMenuDNDHandler.onDrop(event);" + oncommand="BookmarkingUI.onCommand(event);"> + <menupopup id="BMB_bookmarksPopup" + placespopup="true" + context="placesContext" + openInTabs="children" + oncommand="BookmarksEventHandler.onCommand(event, this.parentNode._placesView);" + onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);" + onpopupshowing="BookmarkingUI.onPopupShowing(event); + if (!this.parentNode._placesView) + new PlacesMenu(event, 'place:folder=BOOKMARKS_MENU');" + tooltip="bhTooltip" popupsinherittooltip="true"> + <menuitem id="BMB_viewBookmarksToolbar" + placesanonid="view-toolbar" + toolbarId="PersonalToolbar" + type="checkbox" + oncommand="onViewToolbarCommand(event)" + label="&viewBookmarksToolbar.label;"/> + <menuseparator/> + <menuitem id="BMB_bookmarksShowAll" + label="&showAllBookmarks2.label;" + command="Browser:ShowAllBookmarks" + key="manBookmarkKb"/> + <menuseparator/> + <menuitem id="BMB_subscribeToPageMenuitem" #ifndef XP_MACOSX - class="menuitem-iconic" + class="menuitem-iconic" #endif - label="&bookmarkThisPageCmd.label;" - command="Browser:AddBookmarkAs" - key="addBookmarkAsKb"/> - <menuitem id="BMB_subscribeToPageMenuitem" + label="&subscribeToPageMenuitem.label;" + oncommand="return FeedHandler.subscribeToFeed(null, event);" + onclick="checkForMiddleClick(this, event);" + observes="singleFeedMenuitemState"/> + <menu id="BMB_subscribeToPageMenupopup" #ifndef XP_MACOSX - class="menuitem-iconic" + class="menu-iconic" #endif - label="&subscribeToPageMenuitem.label;" - oncommand="return FeedHandler.subscribeToFeed(null, event);" - onclick="checkForMiddleClick(this, event);" - observes="singleFeedMenuitemState"/> - <menu id="BMB_subscribeToPageMenupopup" -#ifndef XP_MACOSX - class="menu-iconic" -#endif - label="&subscribeToPageMenupopup.label;" - observes="multipleFeedsMenuState"> - <menupopup id="BMB_subscribeToPageSubmenuMenupopup" - onpopupshowing="return FeedHandler.buildFeedList(event.target);" - oncommand="return FeedHandler.subscribeToFeed(null, event);" - onclick="checkForMiddleClick(this, event);"/> - </menu> - <menuseparator/> - <menu id="BMB_bookmarksToolbar" - placesanonid="toolbar-autohide" - class="menu-iconic bookmark-item" - label="&personalbarCmd.label;" - container="true"> - <menupopup id="BMB_bookmarksToolbarPopup" - placespopup="true" - context="placesContext" - onpopupshowing="if (!this.parentNode._placesView) - new PlacesMenu(event, 'place:folder=TOOLBAR');"/> - </menu> - <menuseparator/> - <!-- Bookmarks menu items --> - <menuseparator builder="end" - class="hide-if-empty-places-result"/> - <menuitem id="BMB_unsortedBookmarks" - label="&bookmarksMenuButton.unsorted.label;" - oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks');" - class="menuitem-iconic"/> - </menupopup> - </toolbarbutton> + label="&subscribeToPageMenupopup.label;" + observes="multipleFeedsMenuState"> + <menupopup id="BMB_subscribeToPageSubmenuMenupopup" + onpopupshowing="return FeedHandler.buildFeedList(event.target);" + oncommand="return FeedHandler.subscribeToFeed(null, event);" + onclick="checkForMiddleClick(this, event);"/> + </menu> + <menuseparator/> + <menu id="BMB_bookmarksToolbar" + placesanonid="toolbar-autohide" + class="menu-iconic bookmark-item" + label="&personalbarCmd.label;" + container="true"> + <menupopup id="BMB_bookmarksToolbarPopup" + placespopup="true" + context="placesContext" + onpopupshowing="if (!this.parentNode._placesView) + new PlacesMenu(event, 'place:folder=TOOLBAR');"/> + </menu> + <menuseparator/> + <!-- Bookmarks menu items --> + <menuseparator builder="end" + class="hide-if-empty-places-result"/> + <menuitem id="BMB_unsortedBookmarks" + label="&bookmarksMenuButton.unsorted.label;" + oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks');" + class="menuitem-iconic"/> + </menupopup> + </toolbarbutton> - <toolbarbutton id="home-button" class="toolbarbutton-1 chromeclass-toolbar-additional" - persist="class" removable="true" - label="&homeButton.label;" - ondragover="homeButtonObserver.onDragOver(event)" - ondragenter="homeButtonObserver.onDragOver(event)" - ondrop="homeButtonObserver.onDrop(event)" - ondragexit="homeButtonObserver.onDragExit(event)" - onclick="BrowserGoHome(event);" - aboutHomeOverrideTooltip="&abouthome.pageTitle;"/> + <!-- This is a placeholder for the Downloads Indicator. It is visible + during the customization of the toolbar, in the palette, and before + the Downloads Indicator overlay is loaded. --> + <toolbarbutton id="downloads-button" class="toolbarbutton-1 chromeclass-toolbar-additional" + oncommand="DownloadsIndicatorView.onCommand(event);" + ondrop="DownloadsIndicatorView.onDrop(event);" + ondragover="DownloadsIndicatorView.onDragOver(event);" + ondragenter="DownloadsIndicatorView.onDragOver(event);" + label="&downloads.label;" + removable="true" + tooltiptext="&downloads.tooltip;"/> - <toolbarbutton id="social-share-button" - class="toolbarbutton-1 chromeclass-toolbar-additional" - hidden="true" - label="&sharePageCmd.label;" - tooltiptext="&sharePageCmd.label;" - command="Social:SharePage"/> + <toolbarbutton id="home-button" class="toolbarbutton-1 chromeclass-toolbar-additional" + persist="class" removable="true" + label="&homeButton.label;" + ondragover="homeButtonObserver.onDragOver(event)" + ondragenter="homeButtonObserver.onDragOver(event)" + ondrop="homeButtonObserver.onDrop(event)" + ondragexit="homeButtonObserver.onDragExit(event)" + onclick="BrowserGoHome(event);" + aboutHomeOverrideTooltip="&abouthome.pageTitle;"/> + - <toolbaritem id="social-toolbar-item" - class="chromeclass-toolbar-additional" - removable="false" - title="&socialToolbar.title;" - hidden="true" - skipintoolbarset="true" - observes="socialActiveBroadcaster"> + <toolbarbutton id="social-share-button" + class="toolbarbutton-1 chromeclass-toolbar-additional" + hidden="true" + nooverflow="true" + label="&sharePageCmd.label;" + tooltiptext="&sharePageCmd.label;" + command="Social:SharePage"/> + + <toolbaritem id="social-toolbar-item" + class="chromeclass-toolbar-additional" + removable="false" + title="&socialToolbar.title;" + hidden="true" + nooverflow="true" + skipintoolbarset="true" + observes="socialActiveBroadcaster"> <toolbarbutton id="social-notification-icon" class="default-notification-icon toolbarbutton-1 notification-anchor-icon" oncommand="PopupNotifications._reshowNotifications(this, document.getElementById('social-sidebar-browser'));"/> - <toolbarbutton id="social-provider-button" - class="toolbarbutton-1" - type="menu"> - <menupopup id="social-statusarea-popup"> - <menuitem class="social-statusarea-user menuitem-iconic" pack="start" align="center" - observes="socialBroadcaster_userDetails" - oncommand="SocialUI.showProfile(); document.getElementById('social-statusarea-popup').hidePopup();"> - <image class="social-statusarea-user-portrait" - observes="socialBroadcaster_userDetails"/> - <vbox> - <label class="social-statusarea-loggedInStatus" + <toolbarbutton id="social-provider-button" + class="toolbarbutton-1" + type="menu"> + <menupopup id="social-statusarea-popup"> + <menuitem class="social-statusarea-user menuitem-iconic" pack="start" align="center" + observes="socialBroadcaster_userDetails" + oncommand="SocialUI.showProfile(); document.getElementById('social-statusarea-popup').hidePopup();"> + <image class="social-statusarea-user-portrait" observes="socialBroadcaster_userDetails"/> - </vbox> - </menuitem> + <vbox> + <label class="social-statusarea-loggedInStatus" + observes="socialBroadcaster_userDetails"/> + </vbox> + </menuitem> #ifndef XP_WIN - <menuseparator class="social-statusarea-separator"/> + <menuseparator class="social-statusarea-separator"/> #endif - <menuitem class="social-toggle-sidebar-menuitem" - type="checkbox" - autocheck="false" - command="Social:ToggleSidebar" - label="&social.toggleSidebar.label;" - accesskey="&social.toggleSidebar.accesskey;"/> - <menuitem class="social-toggle-notifications-menuitem" - type="checkbox" - autocheck="false" - command="Social:ToggleNotifications" - label="&social.toggleNotifications.label;" - accesskey="&social.toggleNotifications.accesskey;"/> - <menuitem class="social-toggle-menuitem" command="Social:Toggle"/> - <menuseparator/> - <menuseparator class="social-provider-menu" hidden="true"/> - <menuitem class="social-addons-menuitem" command="Social:Addons" - label="&social.addons.label;"/> - <menuitem label="&social.learnMore.label;" - accesskey="&social.learnMore.accesskey;" - oncommand="SocialUI.showLearnMore();"/> - </menupopup> - </toolbarbutton> - <toolbarbutton id="social-mark-button" + <menuitem class="social-toggle-sidebar-menuitem" + type="checkbox" + autocheck="false" + command="Social:ToggleSidebar" + label="&social.toggleSidebar.label;" + accesskey="&social.toggleSidebar.accesskey;"/> + <menuitem class="social-toggle-notifications-menuitem" + type="checkbox" + autocheck="false" + command="Social:ToggleNotifications" + label="&social.toggleNotifications.label;" + accesskey="&social.toggleNotifications.accesskey;"/> + <menuitem class="social-toggle-menuitem" command="Social:Toggle"/> + <menuseparator/> + <menuseparator class="social-provider-menu" hidden="true"/> + <menuitem class="social-addons-menuitem" command="Social:Addons" + label="&social.addons.label;"/> + <menuitem label="&social.learnMore.label;" + accesskey="&social.learnMore.accesskey;" + oncommand="SocialUI.showLearnMore();"/> + </menupopup> + </toolbarbutton> + <toolbarbutton id="social-mark-button" + class="toolbarbutton-1" + hidden="true" + disabled="true" + command="Social:TogglePageMark"/> + </toolbaritem> + </hbox> + + <toolbarbutton id="nav-bar-overflow-button" + class="toolbarbutton-1 chromeclass-toolbar-additional chevron overflow-button" + skipintoolbarset="true" + tooltiptext="&navbarOverflow.label;"/> + + <toolbaritem id="PanelUI-button" + class="chromeclass-toolbar-additional" + removable="false" + title="&appmenu.title;"> + <toolbarbutton id="PanelUI-menu-button" class="toolbarbutton-1" - hidden="true" - disabled="true" - command="Social:TogglePageMark"/> + label="&brandShortName;" + tooltiptext="&appmenu.title;" + oncommand="PanelUI.toggle(event);"/> </toolbaritem> - <hbox id="window-controls" hidden="true" pack="end"> + <hbox id="window-controls" hidden="true" pack="end" skipintoolbarset="true"> <toolbarbutton id="minimize-button" tooltiptext="&fullScreenMinimize.tooltip;" oncommand="window.minimize();"/> <toolbarbutton id="restore-button" tooltiptext="&fullScreenRestore.tooltip;" oncommand="BrowserFullScreen();"/> @@ -806,250 +914,111 @@ tooltip="bhTooltip" popupsinherittooltip="true" context="placesContext"/> </toolbarbutton> </hbox> </hbox> </toolbaritem> </toolbar> -#ifdef MENUBAR_CAN_AUTOHIDE -#ifndef CAN_DRAW_IN_TITLEBAR -#define APPMENU_ON_TABBAR -#endif -#endif - - - <toolbar id="TabsToolbar" - class="toolbar-primary" - fullscreentoolbar="true" - customizable="true" - mode="icons" lockmode="true" - iconsize="small" defaulticonsize="small" lockiconsize="true" - aria-label="&tabsToolbar.label;" - context="toolbar-context-menu" -#ifdef APPMENU_ON_TABBAR - defaultset="appmenu-toolbar-button,tabbrowser-tabs,new-tab-button,alltabs-button,tabs-closebutton" -#else - defaultset="tabbrowser-tabs,new-tab-button,alltabs-button,tabs-closebutton" -#endif - collapsed="true"> - -#ifdef APPMENU_ON_TABBAR - <toolbarbutton id="appmenu-toolbar-button" - class="chromeclass-toolbar-additional" - type="menu" - label="&brandShortName;" - tooltiptext="&appMenuButton.tooltip;"> -#include browser-appmenu.inc - </toolbarbutton> -#endif - - <tabs id="tabbrowser-tabs" - class="tabbrowser-tabs" - tabbrowser="content" - flex="1" - setfocus="false" - tooltip="tabbrowser-tab-tooltip" - stopwatchid="FX_TAB_CLICK_MS"> - <tab class="tabbrowser-tab" selected="true" fadein="true"/> - </tabs> - - <toolbarbutton id="new-tab-button" - class="toolbarbutton-1 chromeclass-toolbar-additional" - label="&tabCmd.label;" - command="cmd_newNavigatorTab" - onclick="checkForMiddleClick(this, event);" - tooltiptext="&newTabButton.tooltip;" - ondrop="newTabButtonObserver.onDrop(event)" - ondragover="newTabButtonObserver.onDragOver(event)" - ondragenter="newTabButtonObserver.onDragOver(event)" - ondragexit="newTabButtonObserver.onDragExit(event)" - removable="true"/> - - <toolbarbutton id="alltabs-button" - class="toolbarbutton-1 chromeclass-toolbar-additional tabs-alltabs-button" - type="menu" - label="&listAllTabs.label;" - tooltiptext="&listAllTabs.label;" - removable="true"> - <menupopup id="alltabs-popup" - position="after_end"> - <menuitem id="menu_tabview" - class="menuitem-iconic" - key="key_tabview" - label="&viewTabGroups.label;" - command="Browser:ToggleTabView" - observes="tabviewGroupsNumber"/> - <menuseparator id="alltabs-popup-separator"/> - </menupopup> - </toolbarbutton> - - <toolbarbutton id="tabs-closebutton" - class="close-button tabs-closebutton" - command="cmd_close" - label="&closeTab.label;" - tooltiptext="&closeTab.label;"/> - -#ifdef CAN_DRAW_IN_TITLEBAR - <hbox class="titlebar-placeholder" type="appmenu-button" ordinal="0"/> - <hbox class="titlebar-placeholder" type="caption-buttons" ordinal="1000"/> -#endif + <!-- This is a shim which will go away ASAP. See bug 749804 for details --> + <toolbar id="addon-bar" toolbar-delegate="nav-bar"> + <hbox id="addonbar-closebutton"/> + <statusbar id="status-bar"/> </toolbar> - <toolbarpalette id="BrowserToolbarPalette"> # Update primaryToolbarButtons in browser/themes/shared/browser.inc when adding # or removing default items with the toolbarbutton-1 class. <toolbarbutton id="print-button" class="toolbarbutton-1 chromeclass-toolbar-additional" - label="&printButton.label;" command="cmd_print" - tooltiptext="&printButton.tooltip;"/> +#ifdef XP_MACOSX + command="cmd_print" +#else + command="cmd_printPreview" +#endif + label="&printButton.label;" tooltiptext="&printButton.tooltip;"/> - <!-- This is a placeholder for the Downloads Indicator. It is visible - during the customization of the toolbar, in the palette, and before - the Downloads Indicator overlay is loaded. --> - <toolbarbutton id="downloads-button" class="toolbarbutton-1 chromeclass-toolbar-additional" - oncommand="DownloadsIndicatorView.onCommand(event);" - ondrop="DownloadsIndicatorView.onDrop(event);" - ondragover="DownloadsIndicatorView.onDragOver(event);" - ondragenter="DownloadsIndicatorView.onDragOver(event);" - label="&downloads.label;" - tooltiptext="&downloads.tooltip;"/> - - <toolbarbutton id="history-button" class="toolbarbutton-1 chromeclass-toolbar-additional" - observes="viewHistorySidebar" label="&historyButton.label;" - tooltiptext="&historyButton.tooltip;"/> - - <toolbarbutton id="bookmarks-button" class="toolbarbutton-1 chromeclass-toolbar-additional" - observes="viewBookmarksSidebar" - tooltiptext="&bookmarksButton.tooltip;" - ondrop="bookmarksButtonObserver.onDrop(event)" - ondragover="bookmarksButtonObserver.onDragOver(event)" - ondragenter="bookmarksButtonObserver.onDragOver(event)" - ondragexit="bookmarksButtonObserver.onDragExit(event)"/> <toolbarbutton id="new-window-button" class="toolbarbutton-1 chromeclass-toolbar-additional" label="&newNavigatorCmd.label;" command="key_newNavigator" tooltiptext="&newWindowButton.tooltip;" ondrop="newWindowButtonObserver.onDrop(event)" ondragover="newWindowButtonObserver.onDragOver(event)" ondragenter="newWindowButtonObserver.onDragOver(event)" ondragexit="newWindowButtonObserver.onDragExit(event)"/> <toolbarbutton id="fullscreen-button" class="toolbarbutton-1 chromeclass-toolbar-additional" observes="View:FullScreen" type="checkbox" label="&fullScreenCmd.label;" tooltiptext="&fullScreenButton.tooltip;"/> - <toolbaritem id="zoom-controls" class="chromeclass-toolbar-additional" - title="&zoomControls.label;"> - <toolbarbutton id="zoom-out-button" class="toolbarbutton-1" - label="&fullZoomReduceCmd.label;" - command="cmd_fullZoomReduce" - tooltiptext="&zoomOutButton.tooltip;"/> - <toolbarbutton id="zoom-in-button" class="toolbarbutton-1" - label="&fullZoomEnlargeCmd.label;" - command="cmd_fullZoomEnlarge" - tooltiptext="&zoomInButton.tooltip;"/> - </toolbaritem> - - <toolbarbutton id="feed-button" - type="menu" - class="toolbarbutton-1 chromeclass-toolbar-additional" - disabled="true" - label="&feedButton.label;" - tooltiptext="&feedButton.tooltip;" - onclick="return FeedHandler.onFeedButtonClick(event);"> - <menupopup position="after_end" - id="feed-menu" - onpopupshowing="return FeedHandler.buildFeedList(this);" - oncommand="return FeedHandler.subscribeToFeed(null, event);" - onclick="checkForMiddleClick(this, event);"/> - </toolbarbutton> - - <toolbarbutton id="cut-button" class="toolbarbutton-1 chromeclass-toolbar-additional" - label="&cutCmd.label;" - command="cmd_cut" - tooltiptext="&cutButton.tooltip;"/> - - <toolbarbutton id="copy-button" class="toolbarbutton-1 chromeclass-toolbar-additional" - label="©Cmd.label;" - command="cmd_copy" - tooltiptext="©Button.tooltip;"/> - - <toolbarbutton id="paste-button" class="toolbarbutton-1 chromeclass-toolbar-additional" - label="&pasteCmd.label;" - command="cmd_paste" - tooltiptext="&pasteButton.tooltip;"/> - #ifdef MOZ_SERVICES_SYNC <toolbarbutton id="sync-button" class="toolbarbutton-1 chromeclass-toolbar-additional" label="&syncToolbarButton.label;" oncommand="gSyncUI.handleToolbarButton()"/> #endif - <toolbaritem id="navigator-throbber" title="&throbberItem.title;" align="center" pack="center" - mousethrough="always"> - <image/> - </toolbaritem> - <toolbarbutton id="tabview-button" class="toolbarbutton-1 chromeclass-toolbar-additional" label="&tabGroupsButton.label;" command="Browser:ToggleTabView" tooltiptext="&tabGroupsButton.tooltip;" observes="tabviewGroupsNumber"/> </toolbarpalette> </toolbox> <hbox id="fullscr-toggler" collapsed="true"/> - <hbox flex="1" id="browser"> - <vbox id="browser-border-start" hidden="true" layer="true"/> - <vbox id="sidebar-box" hidden="true" class="chromeclass-extrachrome"> - <sidebarheader id="sidebar-header" align="center"> - <label id="sidebar-title" persist="value" flex="1" crop="end" control="sidebar"/> - <image id="sidebar-throbber"/> - <toolbarbutton class="tabs-closebutton" tooltiptext="&sidebarCloseButton.tooltip;" oncommand="toggleSidebar();"/> - </sidebarheader> - <browser id="sidebar" flex="1" autoscroll="false" disablehistory="true" - style="min-width: 14em; width: 18em; max-width: 36em;"/> - </vbox> + <deck id="content-deck" flex="1"> + <hbox flex="1" id="browser"> + <vbox id="browser-border-start" hidden="true" layer="true"/> + <vbox id="sidebar-box" hidden="true" class="chromeclass-extrachrome"> + <sidebarheader id="sidebar-header" align="center"> + <label id="sidebar-title" persist="value" flex="1" crop="end" control="sidebar"/> + <image id="sidebar-throbber"/> + <toolbarbutton class="tabs-closebutton close-icon" tooltiptext="&sidebarCloseButton.tooltip;" oncommand="toggleSidebar();"/> + </sidebarheader> + <browser id="sidebar" flex="1" autoscroll="false" disablehistory="true" + style="min-width: 14em; width: 18em; max-width: 36em;"/> + </vbox> - <splitter id="sidebar-splitter" class="chromeclass-extrachrome sidebar-splitter" hidden="true"/> - <vbox id="appcontent" flex="1"> - <tabbrowser id="content" disablehistory="true" - flex="1" contenttooltip="aHTMLTooltip" - tabcontainer="tabbrowser-tabs" - contentcontextmenu="contentAreaContextMenu" - autocompletepopup="PopupAutoComplete"/> - <chatbar id="pinnedchats" layer="true" mousethrough="always" hidden="true"/> - <statuspanel id="statusbar-display" inactive="true"/> - </vbox> - <splitter id="social-sidebar-splitter" - class="chromeclass-extrachrome sidebar-splitter" - observes="socialSidebarBroadcaster"/> - <vbox id="social-sidebar-box" - class="chromeclass-extrachrome" - observes="socialSidebarBroadcaster" - persist="width"> - <browser id="social-sidebar-browser" - type="content" - context="contentAreaContextMenu" - disableglobalhistory="true" - tooltip="aHTMLTooltip" + <splitter id="sidebar-splitter" class="chromeclass-extrachrome sidebar-splitter" hidden="true"/> + <vbox id="appcontent" flex="1"> + <tabbrowser id="content" disablehistory="true" + flex="1" contenttooltip="aHTMLTooltip" + tabcontainer="tabbrowser-tabs" + contentcontextmenu="contentAreaContextMenu" + autocompletepopup="PopupAutoComplete" + onclick="contentAreaClick(event, false);"/> + <chatbar id="pinnedchats" layer="true" mousethrough="always" hidden="true"/> + <statuspanel id="statusbar-display" inactive="true"/> + </vbox> + <splitter id="social-sidebar-splitter" + class="chromeclass-extrachrome sidebar-splitter" + observes="socialSidebarBroadcaster"/> + <vbox id="social-sidebar-box" + class="chromeclass-extrachrome" + observes="socialSidebarBroadcaster" + persist="width"> + <browser id="social-sidebar-browser" + type="content" + context="contentAreaContextMenu" + disableglobalhistory="true" + tooltip="aHTMLTooltip" popupnotificationanchor="social-notification-icon" - flex="1" - style="min-width: 14em; width: 18em; max-width: 36em;"/> - </vbox> - <vbox id="browser-border-end" hidden="true" layer="true"/> - </hbox> + flex="1" + style="min-width: 14em; width: 18em; max-width: 36em;"/> + </vbox> + <vbox id="browser-border-end" hidden="true" layer="true"/> + </hbox> +#include ../../components/customizableui/content/customizeMode.inc.xul + </deck> <hbox id="full-screen-warning-container" hidden="true" fadeout="true"> <hbox style="width: 100%;" pack="center"> <!-- Inner hbox needed due to bug 579776. --> <vbox id="full-screen-warning-message" align="center"> <description id="full-screen-domain-text"/> <description class="full-screen-description" value="&fullscreenExitHint.value;"/> <vbox id="full-screen-approval-pane" align="center"> <description class="full-screen-description" value="&fullscreenApproval.value;"/> @@ -1088,66 +1057,46 @@ tooltiptext="&devToolbarToolsButton.tooltip;"/> #ifndef XP_MACOSX <toolbarbutton id="developer-toolbar-closebutton" class="devtools-closebutton" oncommand="DeveloperToolbar.hide();" tooltiptext="&devToolbarCloseButton.tooltiptext;"/> #endif </toolbar> - - <toolbar id="addon-bar" - toolbarname="&addonBarCmd.label;" accesskey="&addonBarCmd.accesskey;" - collapsed="true" - class="toolbar-primary chromeclass-toolbar" - context="toolbar-context-menu" toolboxid="navigator-toolbox" - mode="icons" iconsize="small" defaulticonsize="small" - lockiconsize="true" - defaultset="addonbar-closebutton,spring,status-bar" - customizable="true" - key="key_toggleAddonBar"> - <toolbarbutton id="addonbar-closebutton" - tooltiptext="&addonBarCloseButton.tooltip;" - oncommand="setToolbarVisibility(this.parentNode, false);"/> - <statusbar id="status-bar" ordinal="1000"/> - </toolbar> </vbox> + <svg:svg height="0"> + <svg:clipPath id="tab-curve-clip-path-start" clipPathUnits="objectBoundingBox"> + <svg:path d="m 1,0.0625 0.05,0 0,0.938 -1,0 0,-0.028 C 0.32082458,0.95840561 0.4353096,0.81970962 0.48499998,0.5625 0.51819998,0.3905 0.535,0.0659 1,0.0625 z"/> + </svg:clipPath> + <svg:clipPath id="tab-curve-clip-path-end" clipPathUnits="objectBoundingBox"> + <svg:path d="m 0,0.0625 -0.05,0 0,0.938 1,0 0,-0.028 C 0.67917542,0.95840561 0.56569036,0.81970962 0.51599998,0.5625 0.48279998,0.3905 0.465,0.0659 0,0.0625 z"/> + </svg:clipPath> + <svg:clipPath id="tab-clip-path-outer" clipPathUnits="objectBoundingBox"> + <svg:path d="m 0.1894,0 0,0.4194 C 0.1742,0.5484 0.1667,0.6774 0.1364,0.7903 0.1123,0.8935 0,0.9032 0,0.9032 l 0,0.0968 1,0 0,-0.0968 c 0,0 -0.1121,0 -0.1364,-0.1129 C 0.8333,0.6774 0.8258,0.5452 0.8106,0.4194 l 0,-0.4194 z"/> + </svg:clipPath> + #ifndef XP_UNIX - <svg:svg height="0"> <svg:clipPath id="windows-keyhole-forward-clip-path" clipPathUnits="objectBoundingBox"> <svg:path d="M 0,0 C 0.16,0.11 0.28,0.29 0.28,0.5 0.28,0.71 0.16,0.89 0,1 L 1,1 1,0 0,0 z"/> </svg:clipPath> <svg:clipPath id="windows-urlbar-back-button-clip-path" clipPathUnits="userSpaceOnUse"> <svg:path d="M 0,0 0,7.8 C 2.5,11 4,14 4,18 4,22 2.5,25 0,28 l 0,22 10000,0 0,-50 L 0,0 z"/> </svg:clipPath> - </svg:svg> #endif #ifdef XP_MACOSX - <svg:svg height="0"> <svg:clipPath id="osx-keyhole-forward-clip-path" clipPathUnits="objectBoundingBox"> <svg:path d="M 0,0 C 0.15,0.12 0.25,0.3 0.25,0.5 0.25,0.7 0.15,0.88 0,1 L 1,1 1,0 0,0 z"/> </svg:clipPath> <svg:clipPath id="osx-urlbar-back-button-clip-path" clipPathUnits="userSpaceOnUse"> - <svg:path d="m 0,-5 0,4.03 C 3.6,1.8 6,6.1 6,11 6,16 3.6,20 0,23 l 0,27 10000,0 0,-55 L 0,-5 z"/> - </svg:clipPath> - <svg:clipPath id="osx-tab-ontop-left-curve-clip-path" clipPathUnits="userSpaceOnUse"> - <svg:path d="M 9,0 C 7.3,0 6,1.3 6,3 l 0,14 c 0,3 -2.2,5 -5,5 l -1,0 0,1 12,0 0,-1 0,-19 0,-3 -3,0 z"/> - </svg:clipPath> - <svg:clipPath id="osx-tab-ontop-right-curve-clip-path" clipPathUnits="userSpaceOnUse"> - <svg:path d="m 0,0 0,3 0,19 0,1 12,0 0,-1 -1,0 C 8.2,22 6,20 6,17 L 6,3 C 6,1.3 4.7,0 3,0 L 0,0 z"/> + <svg:path d="m 0,-5 0,9.03 C 3.6,6.8 6,11.1 6,16 6,21 3.6,25 0,28 l 0,27 10000,0 0,-55 L 0,-5 z"/> </svg:clipPath> - <svg:clipPath id="osx-tab-onbottom-left-curve-clip-path" clipPathUnits="userSpaceOnUse"> - <svg:path d="m 0,0 0,1 1,0 c 2.8,0 5,2.2 5,5 l 0,14 c 0,2 1.3,3 3,3 l 3,0 0,-3 L 12,1 12,0 0,0 z"/> - </svg:clipPath> - <svg:clipPath id="osx-tab-onbottom-right-curve-clip-path" clipPathUnits="userSpaceOnUse"> - <svg:path d="m 0,0 0,1 0,19 0,3 3,0 c 1.7,0 3,-1 3,-3 L 6,6 C 6,3.2 8.2,1 11,1 L 12,1 12,0 0,0 z"/> - </svg:clipPath> +#endif </svg:svg> -#endif </vbox> # <iframe id="tab-view"> is dynamically appended as the 2nd child of #tab-view-deck. # Introducing the iframe dynamically, as needed, was found to be better than # starting with an empty iframe here in browser.xul from a Ts standpoint. </deck> </window>
--- a/browser/base/content/global-scripts.inc +++ b/browser/base/content/global-scripts.inc @@ -4,10 +4,11 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. <script type="application/javascript" src="chrome://global/content/printUtils.js"/> <script type="application/javascript" src="chrome://global/content/viewZoomOverlay.js"/> <script type="application/javascript" src="chrome://browser/content/places/browserPlacesViews.js"/> <script type="application/javascript" src="chrome://browser/content/browser.js"/> <script type="application/javascript" src="chrome://browser/content/downloads/downloads.js"/> <script type="application/javascript" src="chrome://browser/content/downloads/indicator.js"/> +<script type="application/javascript" src="chrome://browser/content/customizableui/panelUI.js"/> <script type="application/javascript" src="chrome://global/content/inlineSpellCheckUI.js"/> <script type="application/javascript" src="chrome://global/content/viewSourceUtils.js"/>
--- a/browser/base/content/newtab/newTab.xul +++ b/browser/base/content/newtab/newTab.xul @@ -26,16 +26,17 @@ value="&newtab.undo.removedLabel;" /> <xul:button id="newtab-undo-button" tabindex="-1" label="&newtab.undo.undoButton;" class="newtab-undo-button" /> <xul:button id="newtab-undo-restore-button" tabindex="-1" label="&newtab.undo.restoreButton;" class="newtab-undo-button" /> <xul:toolbarbutton id="newtab-undo-close-button" tabindex="-1" + class="close-icon" tooltiptext="&newtab.undo.closeTooltip;" /> </div> </div> <div id="newtab-horizontal-margin"> <div class="newtab-side-margin"/> <div id="newtab-grid">
--- a/browser/base/content/sync/notification.xml +++ b/browser/base/content/sync/notification.xml @@ -78,17 +78,17 @@ </implementation> </binding> <binding id="notification" extends="chrome://global/content/bindings/notification.xml#notification"> <content> <xul:hbox class="notification-inner outset" flex="1" xbl:inherits="type"> <xul:toolbarbutton ondblclick="event.stopPropagation();" - class="messageCloseButton tabbable" + class="messageCloseButton close-icon tabbable" xbl:inherits="hidden=hideclose" tooltiptext="&closeNotification.tooltip;" oncommand="document.getBindingParent(this).close()"/> <xul:hbox anonid="details" align="center" flex="1"> <xul:image anonid="messageImage" class="messageImage" xbl:inherits="src=image"/> <xul:description anonid="messageText" class="messageText" xbl:inherits="xbl:text=label"/> <!-- The children are the buttons defined by the notification. -->
--- a/browser/base/content/tabbrowser.css +++ b/browser/base/content/tabbrowser.css @@ -36,16 +36,17 @@ tabpanels { background-color: transparent; } .tab-drop-indicator { position: relative; z-index: 2; } +.tab-icon-image:not([src]):not([pinned]), .tab-throbber:not([busy]), .tab-throbber[busy] + .tab-icon-image { display: none; } .closing-tabs-spacer { pointer-events: none; }
--- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -135,18 +135,17 @@ ]]></getter> </property> <method name="updateWindowResizers"> <body><![CDATA[ if (!window.gShowPageResizers) return; - var show = document.getElementById("addon-bar").collapsed && - window.windowState == window.STATE_NORMAL; + var show = window.windowState == window.STATE_NORMAL; for (let i = 0; i < this.browsers.length; i++) { this.browsers[i].showWindowResizer = show; } ]]></body> </method> <method name="_setCloseKeyState"> <parameter name="aEnabled"/> @@ -1338,18 +1337,17 @@ b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu")); b.setAttribute("tooltip", this.getAttribute("contenttooltip")); if (Services.prefs.getPrefType("browser.tabs.remote") == Services.prefs.PREF_BOOL && Services.prefs.getBoolPref("browser.tabs.remote")) { b.setAttribute("remote", "true"); } - if (window.gShowPageResizers && document.getElementById("addon-bar").collapsed && - window.windowState == window.STATE_NORMAL) { + if (window.gShowPageResizers && window.windowState == window.STATE_NORMAL) { b.setAttribute("showresizer", "true"); } if (this.hasAttribute("autocompletepopup")) b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup")); b.setAttribute("autoscrollpopup", this._autoScrollPopup.id); // Create the browserStack container @@ -3194,20 +3192,16 @@ <method name="_propagateVisibility"> <body><![CDATA[ let visible = this.visible; document.getElementById("menu_closeWindow").hidden = !visible; document.getElementById("menu_close").setAttribute("label", this.tabbrowser.mStringBundle.getString(visible ? "tabs.closeTab" : "tabs.close")); - goSetCommandEnabled("cmd_ToggleTabsOnTop", visible); - - TabsOnTop.syncUI(); - TabsInTitlebar.allowedBy("tabs-visible", visible); ]]></body> </method> <method name="updateVisibility"> <body><![CDATA[ if (this.childNodes.length - this.tabbrowser._removingTabs.length == 1) this.visible = window.toolbar.visible; @@ -3549,19 +3543,17 @@ switch (aEvent.type) { case "load": this.updateVisibility(); break; case "resize": if (aEvent.target != window) break; - let sizemode = document.documentElement.getAttribute("sizemode"); - TabsInTitlebar.allowedBy("sizemode", - sizemode == "maximized" || sizemode == "fullscreen"); + TabsInTitlebar.updateAppearance(); var width = this.mTabstrip.boxObject.width; if (width != this.mTabstripWidth) { this.adjustTabstrip(); this._fillTrailingGap(); this._handleTabSelect(); this.mTabstripWidth = width; } @@ -3852,18 +3844,17 @@ } ]]></handler> <handler event="dblclick"><![CDATA[ #ifndef XP_MACOSX // When the tabbar has an unified appearance with the titlebar // and menubar, a double-click in it should have the same behavior // as double-clicking the titlebar - if (TabsInTitlebar.enabled || - (TabsOnTop.enabled && this.parentNode._dragBindingAlive)) + if (TabsInTitlebar.enabled || this.parentNode._dragBindingAlive) return; #endif if (event.button != 0 || event.originalTarget.localName != "box") return; // See hack note in the tabbrowser-close-tab-button binding @@ -4284,17 +4275,17 @@ * with the tabbar as its event target (and explicit/originalTarget), * which treats that as a mouse gesture for opening a new tab. * In this context, we're manually blocking the dblclick event * (see dblclick handler). */ var clickedOnce = false; function enableDblClick(event) { var target = event.originalTarget; - if (target.className == 'tab-close-button') + if (target.classList.contains("tab-close-button")) target._ignoredClick = true; if (!clickedOnce) { clickedOnce = true; return; } tabContainer._blockDblClick = false; tabContainer.removeEventListener("click", enableDblClick, true); } @@ -4340,17 +4331,17 @@ validate="never" role="presentation"/> <xul:label flex="1" xbl:inherits="value=label,crop,accesskey,fadein,pinned,selected" class="tab-text tab-label" role="presentation"/> <xul:toolbarbutton anonid="close-button" xbl:inherits="fadein,pinned,selected" - class="tab-close-button"/> + class="tab-close-button close-icon"/> </xul:hbox> </xul:stack> </content> <implementation> <property name="pinned" readonly="true"> <getter> return this.getAttribute("pinned") == "true";
--- a/browser/base/content/test/Makefile.in +++ b/browser/base/content/test/Makefile.in @@ -132,20 +132,17 @@ MOCHITEST_BROWSER_FILES = \ browser_bug585785.js \ browser_bug585830.js \ browser_bug590206.js \ browser_bug592338.js \ browser_bug594131.js \ browser_bug595507.js \ browser_bug596687.js \ browser_bug597218.js \ - browser_bug598923.js \ - browser_bug599325.js \ browser_bug609700.js \ - browser_bug616836.js \ browser_bug623155.js \ browser_bug623893.js \ browser_bug624734.js \ browser_bug647886.js \ browser_bug655584.js \ browser_bug664672.js \ browser_bug678392.js \ browser_bug678392-1.html \ @@ -163,24 +160,22 @@ MOCHITEST_BROWSER_FILES = \ browser_bug783614.js \ browser_bug797677.js \ browser_bug816527.js \ browser_bug817947.js \ browser_bug822367.js \ browser_bug832435.js \ browser_bug839103.js \ browser_canonizeURL.js \ - browser_customize.js \ browser_findbarClose.js \ browser_homeDrop.js \ browser_keywordBookmarklets.js \ browser_contextSearchTabPosition.js \ browser_ctrlTab.js \ browser_customize_popupNotification.js \ - browser_disablechrome.js \ browser_discovery.js \ browser_duplicateIDs.js \ browser_fullscreen-window-open.js \ file_fullscreen-window-open.html \ browser_gestureSupport.js \ browser_getshortcutoruri.js \ browser_hide_removing.js \ browser_overflowScroll.js \ @@ -238,17 +233,16 @@ MOCHITEST_BROWSER_FILES = \ browser_urlHighlight.js \ browser_visibleFindSelection.js \ browser_visibleTabs.js \ browser_visibleTabs_contextMenu.js \ browser_visibleTabs_bookmarkAllPages.js \ browser_visibleTabs_bookmarkAllTabs.js \ browser_visibleTabs_tabPreview.js \ bug592338.html \ - disablechrome.html \ discovery.html \ domplate_test.js \ file_bug822367_1.html \ file_bug822367_1.js \ file_bug822367_2.html \ file_bug822367_3.html \ file_bug822367_4.html \ file_bug822367_4.js \ @@ -282,19 +276,16 @@ MOCHITEST_BROWSER_FILES = \ zoom_test.html \ dummy_page.html \ file_bug550565_popup.html \ file_bug550565_favicon.ico \ browser_aboutHome.js \ app_bug575561.html \ app_subframe_bug575561.html \ browser_contentAreaClick.js \ - browser_addon_bar_close_button.js \ - browser_addon_bar_shortcut.js \ - browser_addon_bar_aomlistener.js \ test_bug628179.html \ browser_wyciwyg_urlbarCopying.js \ test_wyciwyg_copying.html \ authenticate.sjs \ browser_minimize.js \ browser_aboutSyncProgress.js \ browser_middleMouse_inherit.js \ redirect_bug623155.sjs \
deleted file mode 100644 --- a/browser/base/content/test/browser_addon_bar.js +++ /dev/null @@ -1,63 +0,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/. */ - -function test() { - waitForExplicitFinish(); - - let addonbar = document.getElementById("addon-bar"); - ok(addonbar.collapsed, "addon bar is collapsed by default"); - - let topMenu, toolbarMenu; - - function onTopMenuShown(event) { - ok(1, "top menu popupshown listener called"); - event.currentTarget.removeEventListener("popupshown", arguments.callee, false); - // open the customize or toolbars menu - toolbarMenu = document.getElementById("appmenu_customizeMenu") || - document.getElementById("viewToolbarsMenu").firstElementChild; - toolbarMenu.addEventListener("popupshown", onToolbarMenuShown, false); - toolbarMenu.addEventListener("popuphidden", onToolbarMenuHidden, false); - toolbarMenu.openPopup(); - } - - function onTopMenuHidden(event) { - ok(1, "top menu popuphidden listener called"); - event.currentTarget.removeEventListener("popuphidden", arguments.callee, false); - finish(); - } - - function onToolbarMenuShown(event) { - ok(1, "sub menu popupshown listener called"); - event.currentTarget.removeEventListener("popupshown", arguments.callee, false); - - // test the menu item's default state - let menuitem = document.getElementById("toggle_addon-bar"); - ok(menuitem, "found the menu item"); - is(menuitem.getAttribute("checked"), "false", "menuitem is not checked by default"); - - // click on the menu item - // TODO: there's got to be a way to check+command in one shot - menuitem.setAttribute("checked", "true"); - menuitem.click(); - - // now the addon bar should be visible and the menu checked - is(addonbar.getAttribute("collapsed"), "false", "addon bar is visible after executing the command"); - is(menuitem.getAttribute("checked"), "true", "menuitem is checked after executing the command"); - - toolbarMenu.hidePopup(); - } - - function onToolbarMenuHidden(event) { - ok(1, "toolbar menu popuphidden listener called"); - event.currentTarget.removeEventListener("popuphidden", arguments.callee, false); - topMenu.hidePopup(); - } - - // open the appmenu or view menu - topMenu = document.getElementById("appmenu-popup") || - document.getElementById("menu_viewPopup"); - topMenu.addEventListener("popupshown", onTopMenuShown, false); - topMenu.addEventListener("popuphidden", onTopMenuHidden, false); - topMenu.openPopup(); -}
deleted file mode 100644 --- a/browser/base/content/test/browser_addon_bar_aomlistener.js +++ /dev/null @@ -1,67 +0,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/. */ - -function test() { - - let addonbar = document.getElementById("addon-bar"); - ok(addonbar.collapsed, "addon bar is collapsed by default"); - - function addItem(id) { - let button = document.createElement("toolbarbutton"); - button.id = id; - let palette = document.getElementById("navigator-toolbox").palette; - palette.appendChild(button); - addonbar.insertItem(id, null, null, false); - } - - // call onInstalling - AddonsMgrListener.onInstalling(); - - // add item to the bar - let id = "testbutton"; - addItem(id); - - // call onInstalled - AddonsMgrListener.onInstalled(); - - // confirm bar is visible - ok(!addonbar.collapsed, "addon bar is not collapsed after toggle"); - - // call onUninstalling - AddonsMgrListener.onUninstalling(); - - // remove item from the bar - addonbar.currentSet = addonbar.currentSet.replace("," + id, ""); - - // call onUninstalled - AddonsMgrListener.onUninstalled(); - - // confirm bar is not visible - ok(addonbar.collapsed, "addon bar is collapsed after toggle"); - - // call onEnabling - AddonsMgrListener.onEnabling(); - - // add item to the bar - let id = "testbutton"; - addItem(id); - - // call onEnabled - AddonsMgrListener.onEnabled(); - - // confirm bar is visible - ok(!addonbar.collapsed, "addon bar is not collapsed after toggle"); - - // call onDisabling - AddonsMgrListener.onDisabling(); - - // remove item from the bar - addonbar.currentSet = addonbar.currentSet.replace("," + id, ""); - - // call onDisabled - AddonsMgrListener.onDisabled(); - - // confirm bar is not visible - ok(addonbar.collapsed, "addon bar is collapsed after toggle"); -}
deleted file mode 100644 --- a/browser/base/content/test/browser_addon_bar_close_button.js +++ /dev/null @@ -1,19 +0,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/. */ - -function test() { - let addonbar = document.getElementById("addon-bar"); - ok(addonbar.collapsed, "addon bar is collapsed by default"); - - // make add-on bar visible - setToolbarVisibility(addonbar, true); - ok(!addonbar.collapsed, "addon bar is not collapsed after toggle"); - - // click the close button - let closeButton = document.getElementById("addonbar-closebutton"); - EventUtils.synthesizeMouseAtCenter(closeButton, {}); - - // confirm addon bar is closed - ok(addonbar.collapsed, "addon bar is collapsed after clicking close button"); -}
deleted file mode 100644 --- a/browser/base/content/test/browser_addon_bar_shortcut.js +++ /dev/null @@ -1,18 +0,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/. */ - -function test() { - let addonbar = document.getElementById("addon-bar"); - ok(addonbar.collapsed, "addon bar is collapsed by default"); - - // show the add-on bar - EventUtils.synthesizeKey("/", { accelKey: true }, window); - ok(!addonbar.collapsed, "addon bar is not collapsed after toggle"); - - // hide the add-on bar - EventUtils.synthesizeKey("/", { accelKey: true }, window); - - // confirm addon bar is closed - ok(addonbar.collapsed, "addon bar is collapsed after toggle"); -}
--- a/browser/base/content/test/browser_bug462289.js +++ b/browser/base/content/test/browser_bug462289.js @@ -29,26 +29,19 @@ function step2() setTimeout(step3, 0); } function step3() { is(gBrowser.selectedTab, tab1, "2nd click on selected tab1 keeps tab selected"); isnot(document.activeElement, tab1, "2nd click on selected tab1 does not activate tab"); - if (gNavToolbox.getAttribute("tabsontop") == "true") { - ok(true, "[tabsontop=true] focusing URLBar then sending 1 Shift+Tab."); - gURLBar.focus(); - EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}); - } else { - ok(true, "[tabsontop=false] focusing SearchBar then sending Tab(s) until out of nav-bar."); - document.getElementById("searchbar").focus(); - while (focus_in_navbar()) - EventUtils.synthesizeKey("VK_TAB", { }); - } + ok(true, "focusing URLBar then sending 1 Shift+Tab."); + gURLBar.focus(); + EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}); is(gBrowser.selectedTab, tab1, "tab key to selected tab1 keeps tab selected"); is(document.activeElement, tab1, "tab key to selected tab1 activates tab"); EventUtils.synthesizeMouseAtCenter(tab1, {}); setTimeout(step4, 0); } function step4()
deleted file mode 100644 --- a/browser/base/content/test/browser_bug598923.js +++ /dev/null @@ -1,33 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -// Test: -// * if add-on is installed to the add-on bar, the bar is made visible. -// * if add-on is uninstalled from the add-on bar, and no more add-ons there, -// the bar is hidden. - -function test() { - let aml = AddonsMgrListener; - ok(aml, "AddonsMgrListener exists"); - // check is hidden - is(aml.addonBar.collapsed, true, "add-on bar is hidden initially"); - // aob gets the count - AddonsMgrListener.onInstalling(); - // add an item - let element = document.createElement("toolbaritem"); - element.id = "bug598923-addon-item"; - aml.addonBar.appendChild(element); - // aob checks the count, makes visible - AddonsMgrListener.onInstalled(); - // check is visible - is(aml.addonBar.collapsed, false, "add-on bar has been made visible"); - // aob gets the count - AddonsMgrListener.onUninstalling(); - // remove an item - aml.addonBar.removeChild(element); - // aob checks the count, makes hidden - AddonsMgrListener.onUninstalled(); - // check is hidden - is(aml.addonBar.collapsed, true, "add-on bar is hidden again"); -}
deleted file mode 100644 --- a/browser/base/content/test/browser_bug599325.js +++ /dev/null @@ -1,21 +0,0 @@ -function test() { - waitForExplicitFinish(); - - let addonBar = document.getElementById("addon-bar"); - ok(addonBar, "got addon bar"); - ok(!isElementVisible(addonBar), "addon bar initially hidden"); - - openToolbarCustomizationUI(function () { - ok(isElementVisible(addonBar), - "add-on bar is visible during toolbar customization"); - - closeToolbarCustomizationUI(onClose); - }); - - function onClose() { - ok(!isElementVisible(addonBar), - "addon bar is hidden after toolbar customization"); - - finish(); - } -}
deleted file mode 100644 --- a/browser/base/content/test/browser_bug616836.js +++ /dev/null @@ -1,4 +0,0 @@ -function test() { - is(document.querySelectorAll("#appmenu-popup [accesskey]").length, 0, - "there should be no items with access keys in the app menu popup"); -}
--- a/browser/base/content/test/browser_bug624734.js +++ b/browser/base/content/test/browser_bug624734.js @@ -6,18 +6,18 @@ function test() { waitForExplicitFinish(); let tab = gBrowser.selectedTab = gBrowser.addTab(); tab.linkedBrowser.addEventListener("load", (function(event) { tab.linkedBrowser.removeEventListener("load", arguments.callee, true); - is(BookmarkingUI.star.getAttribute("tooltiptext"), + is(BookmarkingUI.button.getAttribute("tooltiptext"), BookmarkingUI._unstarredTooltip, "Star icon should have the unstarred tooltip text"); - + gBrowser.removeCurrentTab(); finish(); }), true); tab.linkedBrowser.loadURI("http://example.com/browser/browser/base/content/test/dummy_page.html"); }
deleted file mode 100644 --- a/browser/base/content/test/browser_customize.js +++ /dev/null @@ -1,24 +0,0 @@ -function test() { - waitForExplicitFinish(); - - openToolbarCustomizationUI(customizationWindowLoaded); -} - -function customizationWindowLoaded(win) { - let x = win.screenX; - let iconModeList = win.document.getElementById("modelist"); - - iconModeList.addEventListener("popupshown", function popupshown() { - iconModeList.removeEventListener("popupshown", popupshown, false); - - executeSoon(function () { - is(win.screenX, x, - "toolbar customization window shouldn't move when the iconmode menulist is opened"); - iconModeList.open = false; - - closeToolbarCustomizationUI(finish); - }); - }, false); - - iconModeList.open = true; -}
deleted file mode 100644 --- a/browser/base/content/test/browser_disablechrome.js +++ /dev/null @@ -1,216 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -// Tests that the disablechrome attribute gets propogated to the main UI - -const HTTPSRC = "http://example.com/browser/browser/base/content/test/"; - -function is_element_hidden(aElement) { - var style = window.getComputedStyle(document.getElementById("nav-bar"), ""); - if (style.visibility != "visible" || style.display == "none") - return true; - - if (aElement.ownerDocument != aElement.parentNode) - return is_element_hidden(aElement.parentNode); - - return false; -} - -function is_chrome_hidden() { - is(document.documentElement.getAttribute("disablechrome"), "true", "Attribute should be set"); - if (TabsOnTop.enabled) - ok(is_element_hidden(document.getElementById("nav-bar")), "Toolbar should be hidden"); - else - ok(!is_element_hidden(document.getElementById("nav-bar")), "Toolbar should not be hidden"); -} - -function is_chrome_visible() { - isnot(document.getElementById("main-window").getAttribute("disablechrome"), "true", "Attribute should not be set"); - ok(!is_element_hidden(document.getElementById("nav-bar")), "Toolbar should not be hidden"); -} - -function load_page(aURL, aCanHide, aCallback) { - gNewBrowser.addEventListener("pageshow", function() { - // Filter out about:blank loads - if (gNewBrowser.currentURI.spec != aURL) - return; - - gNewBrowser.removeEventListener("pageshow", arguments.callee, false); - - if (aCanHide) - is_chrome_hidden(); - else - is_chrome_visible(); - - if (aURL == "about:addons") { - function check_after_init() { - if (aCanHide) - is_chrome_hidden(); - else - is_chrome_visible(); - - aCallback(); - } - - if (gNewBrowser.contentWindow.gIsInitializing) { - gNewBrowser.contentDocument.addEventListener("Initialized", function() { - gNewBrowser.contentDocument.removeEventListener("Initialized", arguments.callee, false); - - check_after_init(); - }, false); - } - else { - check_after_init(); - } - } - else { - executeSoon(aCallback); - } - }, false); - gNewBrowser.loadURI(aURL); -} - -var gOldTab; -var gNewTab; -var gNewBrowser; - -function test() { - // Opening the add-ons manager and waiting for it to load the discovery pane - // takes more time in windows debug builds - requestLongerTimeout(2); - - var gOldTabsOnTop = TabsOnTop.enabled; - registerCleanupFunction(function() { - TabsOnTop.enabled = gOldTabsOnTop; - }); - - waitForExplicitFinish(); - - gOldTab = gBrowser.selectedTab; - gNewTab = gBrowser.selectedTab = gBrowser.addTab("about:blank"); - gNewBrowser = gBrowser.selectedBrowser; - - info("Tabs on top"); - TabsOnTop.enabled = true; - - run_http_test_1(); -} - -function end_test() { - gBrowser.removeTab(gNewTab); - finish(); -} - -function test_url(aURL, aCanHide, aNextTest) { - is_chrome_visible(); - info("Page load"); - load_page(aURL, aCanHide, function() { - info("Switch away"); - gBrowser.selectedTab = gOldTab; - is_chrome_visible(); - - info("Switch back"); - gBrowser.selectedTab = gNewTab; - if (aCanHide) - is_chrome_hidden(); - else - is_chrome_visible(); - - gBrowser.removeTab(gNewTab); - gNewTab = gBrowser.selectedTab = gBrowser.addTab("about:blank"); - gNewBrowser = gBrowser.selectedBrowser; - - gBrowser.selectedTab = gOldTab; - - info("Background load"); - load_page(aURL, false, function() { - info("Switch back"); - gBrowser.selectedTab = gNewTab; - if (aCanHide) - is_chrome_hidden(); - else - is_chrome_visible(); - - load_page("about:blank", false, aNextTest); - }); - }); -} - -// Should never hide the chrome -function run_http_test_1() { - info("HTTP tests"); - test_url(HTTPSRC + "disablechrome.html", false, run_chrome_about_test); -} - -// Should hide the chrome -function run_chrome_about_test() { - info("Chrome about: tests"); - test_url("about:addons", true, function() { - info("Tabs on bottom"); - TabsOnTop.enabled = false; - run_http_test_2(); - }); -} - -// Should never hide the chrome -function run_http_test_2() { - info("HTTP tests"); - test_url(HTTPSRC + "disablechrome.html", false, run_chrome_about_test_2); -} - -// Should not hide the chrome -function run_chrome_about_test_2() { - info("Chrome about: tests"); - test_url("about:addons", true, run_http_test3); -} - -function run_http_test3() { - info("HTTP tests"); - test_url(HTTPSRC + "disablechrome.html", false, run_chrome_about_test_3); -} - -// Should not hide the chrome -function run_chrome_about_test_3() { - info("Chrome about: tests"); - test_url("about:Addons", true, function(){ - info("Tabs on top"); - TabsOnTop.enabled = true; - run_http_test4(); - }); -} - -function run_http_test4() { - info("HTTP tests"); - test_url(HTTPSRC + "disablechrome.html", false, run_chrome_about_test_4); -} - -function run_chrome_about_test_4() { - info("Chrome about: tests"); - test_url("about:Addons", true, run_http_test5); - } - -function run_http_test5() { - info("HTTP tests"); - test_url(HTTPSRC + "disablechrome.html", false, run_chrome_about_test_5); -} - -// Should hide the chrome -function run_chrome_about_test_5() { - info("Chrome about: tests"); - test_url("about:preferences", true, function(){ - info("Tabs on bottom"); - TabsOnTop.enabled = false; - run_http_test6(); - }); -} - -function run_http_test6() { - info("HTTP tests"); - test_url(HTTPSRC + "disablechrome.html", false, run_chrome_about_test_6); -} - -function run_chrome_about_test_6() { - info("Chrome about: tests"); - test_url("about:preferences", true, end_test); -} \ No newline at end of file
deleted file mode 100644 --- a/browser/base/content/test/disablechrome.html +++ /dev/null @@ -1,4 +0,0 @@ -<html> -<body> -</body> -</html>
--- a/browser/base/content/test/head.js +++ b/browser/base/content/test/head.js @@ -32,59 +32,37 @@ function updateTabContextMenu(tab) { tab = gBrowser.selectedTab; var evt = new Event(""); tab.dispatchEvent(evt); menu.openPopup(tab, "end_after", 0, 0, true, false, evt); is(TabContextMenu.contextTab, tab, "TabContextMenu context is the expected tab"); menu.hidePopup(); } -function findToolbarCustomizationWindow(aBrowserWin) { - if (!aBrowserWin) - aBrowserWin = window; - - let iframe = aBrowserWin.document.getElementById("customizeToolbarSheetIFrame"); - let win = iframe && iframe.contentWindow; - if (win) - return win; - - win = findChromeWindowByURI("chrome://global/content/customizeToolbar.xul"); - if (win && win.opener == aBrowserWin) - return win; - - throw Error("Failed to find the customization window"); -} - function openToolbarCustomizationUI(aCallback, aBrowserWin) { if (!aBrowserWin) aBrowserWin = window; - aBrowserWin.document.getElementById("cmd_CustomizeToolbars").doCommand(); - - aBrowserWin.gNavToolbox.addEventListener("beforecustomization", function UI_loaded() { - aBrowserWin.gNavToolbox.removeEventListener("beforecustomization", UI_loaded); + aBrowserWin.gCustomizeMode.enter(); - let win = findToolbarCustomizationWindow(aBrowserWin); - waitForFocus(function () { - aCallback(win); - }, win); + aBrowserWin.gNavToolbox.addEventListener("customizationready", function UI_loaded() { + aBrowserWin.gNavToolbox.removeEventListener("customizationready", UI_loaded); + executeSoon(function() { + aCallback(aBrowserWin) + }); }); } function closeToolbarCustomizationUI(aCallback, aBrowserWin) { - let win = findToolbarCustomizationWindow(aBrowserWin); - - win.addEventListener("unload", function unloaded() { - win.removeEventListener("unload", unloaded); + aBrowserWin.gNavToolbox.addEventListener("aftercustomization", function unloaded() { + aBrowserWin.gNavToolbox.removeEventListener("aftercustomization", unloaded); executeSoon(aCallback); }); - let button = win.document.getElementById("donebutton"); - button.focus(); - button.doCommand(); + aBrowserWin.gCustomizeMode.exit(); } function waitForCondition(condition, nextTest, errorMsg) { var tries = 0; var interval = setInterval(function() { if (tries >= 30) { ok(false, errorMsg); moveOn();
--- a/browser/base/content/urlbarBindings.xml +++ b/browser/base/content/urlbarBindings.xml @@ -932,26 +932,26 @@ pack="end" align="center"> <xul:button anonid="button" class="popup-notification-menubutton" type="menu-button" xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey"> <xul:menupopup anonid="menupopup" xbl:inherits="oncommand=menucommand"> <children/> - <xul:menuitem class="menuitem-iconic popup-notification-closeitem" + <xul:menuitem class="menuitem-iconic popup-notification-closeitem close-icon" label="&closeNotificationItem.label;" xbl:inherits="oncommand=closeitemcommand"/> </xul:menupopup> </xul:button> </xul:hbox> </xul:vbox> <xul:vbox pack="start"> <xul:toolbarbutton anonid="closebutton" - class="messageCloseButton popup-notification-closebutton tabbable" + class="messageCloseButton close-icon popup-notification-closebutton tabbable" xbl:inherits="oncommand=closebuttoncommand" tooltiptext="&closeNotification.tooltip;"/> </xul:vbox> </content> <implementation> <constructor><![CDATA[ this.cancelbtn.setAttribute("tooltiptext", gNavigatorBundle.getString("addonDownloadCancelTooltip")); @@ -1164,26 +1164,26 @@ style="visibility:hidden" width="16" height="16"/> <xul:button anonid="button" type="menu-button" class="popup-notification-menubutton" xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey"> <xul:menupopup anonid="menupopup" xbl:inherits="oncommand=menucommand"> <children/> - <xul:menuitem class="menuitem-iconic popup-notification-closeitem" + <xul:menuitem class="menuitem-iconic popup-notification-closeitem close-icon" label="&closeNotificationItem.label;" xbl:inherits="oncommand=closeitemcommand"/> </xul:menupopup> </xul:button> </xul:hbox> </xul:vbox> <xul:vbox pack="start"> <xul:toolbarbutton anonid="closebutton" - class="messageCloseButton popup-notification-closebutton tabbable" + class="messageCloseButton close-icon popup-notification-closebutton tabbable" xbl:inherits="oncommand=closebuttoncommand" tooltiptext="&closeNotification.tooltip;"/> </xul:vbox> </content> <implementation> <constructor><![CDATA[ // this.notification.options.identity is used to pass identity-specific info to the binding let origin = this.identity.origin @@ -1503,17 +1503,17 @@ <xul:vbox flex="1" align="stretch" class="popup-notification-main-box" xbl:inherits="popupid"> <xul:hbox class="click-to-play-plugins-notification-description-box" flex="1" align="start"> <xul:description class="click-to-play-plugins-outer-description" flex="1"> <html:span anonid="click-to-play-plugins-notification-description" /> <xul:label class="text-link click-to-play-plugins-notification-link" anonid="click-to-play-plugins-notification-link" /> </xul:description> <xul:toolbarbutton anonid="closebutton" - class="messageCloseButton popup-notification-closebutton tabbable" + class="messageCloseButton popup-notification-closebutton tabbable close-icon" xbl:inherits="oncommand=closebuttoncommand" tooltiptext="&closeNotification.tooltip;"/> </xul:hbox> <xul:grid anonid="click-to-play-plugins-notification-center-box" class="click-to-play-plugins-notification-center-box"> <xul:columns> <xul:column flex="1"/> <xul:column/> @@ -2003,17 +2003,17 @@ <xul:hbox align="center" flex="1"> <xul:image class="panel-promo-icon"/> <xul:description anonid="promo-message" class="panel-promo-message" flex="1"> <xul:description anonid="promo-link" class="plain text-link inline-link" onclick="document.getBindingParent(this).onLinkClick();"/> </xul:description> </xul:hbox> - <xul:toolbarbutton class="panel-promo-closebutton" + <xul:toolbarbutton class="panel-promo-closebutton close-icon" oncommand="document.getBindingParent(this).onCloseButtonCommand();" tooltiptext="&closeNotification.tooltip;"/> </xul:hbox> </content> <implementation implements="nsIDOMEventListener"> <constructor><![CDATA[ this._panel.addEventListener("popupshowing", this, false);
--- a/browser/base/jar.mn +++ b/browser/base/jar.mn @@ -8,18 +8,17 @@ browser.jar: % overlay chrome://global/content/console.xul chrome://browser/content/jsConsoleOverlay.xul % overlay chrome://mozapps/content/update/updates.xul chrome://browser/content/softwareUpdateOverlay.xul #endif #ifdef XP_WIN % overlay chrome://browser/content/browser.xul chrome://browser/content/win6BrowserOverlay.xul os=WINNT osversion>=6 #endif % overlay chrome://global/content/viewSource.xul chrome://browser/content/viewSourceOverlay.xul % overlay chrome://global/content/viewPartialSource.xul chrome://browser/content/viewSourceOverlay.xul -% style chrome://global/content/customizeToolbar.xul chrome://browser/content/browser.css -% style chrome://global/content/customizeToolbar.xul chrome://browser/skin/ + * content/browser/aboutDialog.xul (content/aboutDialog.xul) * content/browser/aboutDialog.js (content/aboutDialog.js) content/browser/aboutDialog.css (content/aboutDialog.css) content/browser/aboutRobots.xhtml (content/aboutRobots.xhtml) content/browser/abouthome/aboutHome.xhtml (content/abouthome/aboutHome.xhtml) content/browser/abouthome/aboutHome.js (content/abouthome/aboutHome.js) * content/browser/abouthome/aboutHome.css (content/abouthome/aboutHome.css) content/browser/abouthome/noise.png (content/abouthome/noise.png)
--- a/browser/branding/nightly/branding.nsi +++ b/browser/branding/nightly/branding.nsi @@ -3,17 +3,17 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. # NSIS branding defines for nightly builds. # The official release build branding.nsi is located in other-license/branding/firefox/ # The unofficial build branding.nsi is located in browser/branding/unofficial/ # BrandFullNameInternal is used for some registry and file system values # instead of BrandFullName and typically should not be modified. -!define BrandFullNameInternal "Nightly" +!define BrandFullNameInternal "UX" !define CompanyName "mozilla.org" !define URLInfoAbout "http://www.mozilla.org" !define URLUpdateInfo "http://www.mozilla.org/projects/firefox" !define URLStubDownload "http://download.mozilla.org/?product=firefox-nightly-latest&os=win&lang=${AB_CD}" !define URLManualDownload "https://www.mozilla.org/${AB_CD}/firefox/installer-help/?channel=nightly&installer_lang=${AB_CD}" !define Channel "nightly"
--- a/browser/branding/nightly/configure.sh +++ b/browser/branding/nightly/configure.sh @@ -1,5 +1,5 @@ # 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/. -MOZ_APP_DISPLAYNAME=Nightly +MOZ_APP_DISPLAYNAME=UX
--- a/browser/branding/nightly/locales/en-US/brand.dtd +++ b/browser/branding/nightly/locales/en-US/brand.dtd @@ -1,8 +1,8 @@ <!-- 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/. --> -<!ENTITY brandShortName "Nightly"> -<!ENTITY brandFullName "Nightly"> +<!ENTITY brandShortName "UX"> +<!ENTITY brandFullName "UX"> <!ENTITY vendorShortName "Mozilla"> <!ENTITY trademarkInfo.part1 " ">
--- a/browser/branding/nightly/locales/en-US/brand.properties +++ b/browser/branding/nightly/locales/en-US/brand.properties @@ -1,9 +1,9 @@ # 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/. -brandShortName=Nightly -brandFullName=Nightly +brandShortName=UX +brandFullName=UX vendorShortName=Mozilla syncBrandShortName=Sync
--- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -81,16 +81,18 @@ static RedirEntry kRedirMap[] = { { "preferences", "chrome://browser/content/preferences/in-content/preferences.xul", nsIAboutModule::ALLOW_SCRIPT }, { "downloads", "chrome://browser/content/downloads/contentAreaDownloadsView.xul", nsIAboutModule::ALLOW_SCRIPT }, #ifdef MOZ_SERVICES_HEALTHREPORT { "healthreport", "chrome://browser/content/abouthealthreport/abouthealth.xhtml", nsIAboutModule::ALLOW_SCRIPT }, #endif + { "customizing", "chrome://browser/content/customizableui/aboutCustomizing.xhtml", + nsIAboutModule::ALLOW_SCRIPT }, }; static const int kRedirTotal = NS_ARRAY_LENGTH(kRedirMap); static nsAutoCString GetAboutModuleName(nsIURI *aURI) { nsAutoCString path; aURI->GetPath(path);
--- a/browser/components/build/nsModule.cpp +++ b/browser/components/build/nsModule.cpp @@ -103,16 +103,17 @@ static const mozilla::Module::ContractID { NS_ABOUT_MODULE_CONTRACTID_PREFIX "home", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "newtab", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "permissions", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "preferences", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "downloads", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, #ifdef MOZ_SERVICES_HEALTHREPORT { NS_ABOUT_MODULE_CONTRACTID_PREFIX "healthreport", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, #endif + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "customizing", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, #if defined(XP_WIN) { NS_IEHISTORYENUMERATOR_CONTRACTID, &kNS_WINIEHISTORYENUMERATOR_CID }, #elif defined(XP_MACOSX) { NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID }, #endif { NULL } };
new file mode 100644 --- /dev/null +++ b/browser/components/customizableui/content/aboutCustomizing.xhtml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- 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/. --> + +<!DOCTYPE html [ + <!ENTITY % htmlDTD + PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "DTD/xhtml1-strict.dtd"> + %htmlDTD; + <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> + %brandDTD; + <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd"> + %browserDTD; +]> + +<html xmlns="http://www.w3.org/1999/xhtml" + disablefastfind="true"> + <head> + <title>&customizeMode.tabTitle;</title> + </head> + <body></body> +</html>
new file mode 100644 --- /dev/null +++ b/browser/components/customizableui/content/customizeMode.inc.xul @@ -0,0 +1,29 @@ +<!-- 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" value="&customizeMode.menuAndToolbars.header;"/> + <vbox id="customization-palette" flex="1"/> + <hbox pack="start"> + <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" flex="1"> + <hbox id="customization-panelHolder"/> + <box class="panel-inner-arrowcontentfooter" hidden="true"/> + </box> + </vbox> + <spacer flex="1"/> + </vbox> +</hbox>
new file mode 100644 --- /dev/null +++ b/browser/components/customizableui/content/jar.mn @@ -0,0 +1,11 @@ +# 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/aboutCustomizing.xhtml + content/browser/customizableui/panelUI.css + content/browser/customizableui/panelUI.js + content/browser/customizableui/panelUI.xml + content/browser/customizableui/toolbar.xml +
new file mode 100644 --- /dev/null +++ b/browser/components/customizableui/content/moz.build @@ -0,0 +1,6 @@ +# -*- Mode: python; c-basic-offset: 4; 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/. +
new file mode 100644 --- /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: hidden; +} + +.panel-subviews[panelopen] { + transition: transform 150ms; +} + +.panel-viewcontainer[panelopen]:-moz-any(:not([viewtype="main"]),[transitioning="true"]) { + transition: height 150ms; +}
new file mode 100644 --- /dev/null +++ b/browser/components/customizableui/content/panelUI.inc.xul @@ -0,0 +1,81 @@ +<!-- 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" + level="top" + hidden="true" + consumeoutsideclicks="true" + noautofocus="true"> + <panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView"> + <panelview id="PanelUI-mainView" contextmenu="customizationPanelContextMenu"> + <vbox id="PanelUI-contents" type="grid"/> + + <vbox id="PanelUI-mainView-spring" flex="1"/> + + <footer class="PanelUI-footer" align="center"> + <!-- The parentNode is used so that the footer is presented as the anchor + instead of just the button being the anchor. --> + <toolbarbutton id="PanelUI-help-btn" label="&helpMenu.label;" + onclick="PanelUI.showHelpView(this.parentNode);"/> + <spacer flex="1"/> + <toolbarbutton id="PanelUI-customize-btn" label="&appMenuCustomize.label;" + command="cmd_CustomizeToolbars"/> + </footer> + </panelview> + + <panelview id="PanelUI-history" flex="1"> + <label value="&appMenuHistory.label;"/> + <toolbarbutton id="appMenuClearRecentHistory" + label="&appMenuHistory.clearRecent.label;" + command="Tools:Sanitize"/> + <toolbarbutton id="appMenuRestoreLastSession" + label="&appMenuHistory.restoreSession.label;" + command="Browser:RestoreLastSession"/> + <vbox id="PanelUI-historyItems"/> + <label value="&appMenuHistory.showAll.label;" + id="PanelUI-historyMore" + class="text-link" + onclick="PlacesCommandHook.showPlacesOrganizer('History'); CustomizableUI.hidePanelForNode(this);"/> + </panelview> + + <panelview id="PanelUI-feeds" flex="1" oncommand="FeedHandler.subscribeToFeed(null, event);"></panelview> + + <panelview id="PanelUI-help" flex="1"> + <label value="&helpMenu.label;"/> + <vbox id="PanelUI-helpItems"/> + </panelview> + </panelmultiview> + <popupset id="customizationContextMenus"> + <menupopup id="customizationContextMenu"> + <menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode)" + accesskey="&customizeMenu.addToToolbar.accesskey;" + label="&customizeMenu.addToToolbar.label;"/> + <menuitem oncommand="gCustomizeMode.removeFromPanel(document.popupNode)" + accesskey="&customizeMenu.removeFromMenu.accesskey;" + label="&customizeMenu.removeFromMenu.label;"/> + <menuseparator/> + <menuitem command="cmd_CustomizeToolbars" + accesskey="&viewCustomizeToolbar.accesskey;" + label="&viewCustomizeToolbar.label;"/> + </menupopup> + + <menupopup id="customizationPanelContextMenu"> + <menuitem command="cmd_CustomizeToolbars" + accesskey="&customizeMenu.addMoreItems.accesskey;" + label="&customizeMenu.addMoreItems.label;"/> + </menupopup> + </popupset> +</panel> + +<panel id="widget-overflow" + role="group" + type="arrow" + level="top" + hidden="true" + consumeoutsideclicks="true"> + <vbox id="widget-overflow-list"> + </vbox> +</panel>
new file mode 100644 --- /dev/null +++ b/browser/components/customizableui/content/panelUI.js @@ -0,0 +1,280 @@ +/* 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"); +/** + * Maintains the state and dispatches events for the main menu panel. + */ + +const PanelUI = { + /** Panel events that we listen for. **/ + get kEvents() ["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-help", + menuButton: "PanelUI-menu-button", + panel: "PanelUI-popup" + }; + }, + + init: function() { + for (let [k, v] of Iterator(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); + }); + } + + for (let event of this.kEvents) { + this.panel.addEventListener(event, this); + } + + this.helpView.addEventListener("ViewShowing", this._onHelpViewShow, false); + this.helpView.addEventListener("ViewHiding", this._onHelpViewHide, false); + }, + + uninit: function() { + for (let event of this.kEvents) { + this.panel.removeEventListener(event, this); + } + this.helpView.removeEventListener("ViewShowing", this._onHelpViewShow); + this.helpView.removeEventListener("ViewHiding", this._onHelpViewHide); + }, + + /** + * 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.multiView.setMainView(aMainView); + }, + + /** + * Opens the menu panel if it's closed, or closes it if it's + * open. 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 that triggers the toggle. + */ + toggle: function(aEvent) { + if (this.panel.state == "open") { + this.hide(); + } else if (this.panel.state == "closed") { + this.ensureRegistered(); + this.panel.hidden = false; + + let anchor = aEvent.target; + let iconAnchor = + document.getAnonymousElementByAttribute(anchor, "class", + "toolbarbutton-icon"); + this.panel.openPopup(iconAnchor || anchor, "bottomcenter topright"); + } + }, + + /** + * If the menu panel is being shown, hide it. + */ + hide: function() { + this.panel.hidePopup(); + }, + + handleEvent: function(aEvent) { + switch (aEvent.type) { + case "popupshowing": + // Fall through + case "popupshown": + // Fall through + case "popuphiding": + // Fall through + case "popuphidden": { + this._updatePanelButton(aEvent.target); + break; + } + } + }, + + /** + * Registering the menu panel is done lazily for performance reasons. This + * method is exposed so that CustomizationMode can force registration in the + * event that customization mode is started before the panel has had a chance + * to register itself. + * + * @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. + */ + ensureRegistered: function(aCustomizing=false) { + if (aCustomizing) { + CustomizableUI.registerMenuPanel(this.contents); + } else { + this.beginBatchUpdate(); + CustomizableUI.registerMenuPanel(this.contents); + this.endBatchUpdate(); + } + }, + + /** + * Switch the panel to the main view if it's not already + * in that view. + */ + showMainView: function() { + this.multiView.showMainView(); + }, + + /** + * Switch the panel to the help view if it's not already + * in that view. + */ + showHelpView: function(aAnchor) { + this.multiView.showSubView("PanelUI-help", 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: function(aViewId, aAnchor, aPlacementArea) { + 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 { + // Emit the ViewShowing event so that the widget definition has a chance + // to lazily populate the subview with things. + let evt = document.createEvent("CustomEvent"); + evt.initCustomEvent("ViewShowing", true, true, viewNode); + viewNode.dispatchEvent(evt); + if (evt.defaultPrevented) { + return; + } + + let tempPanel = document.createElement("panel"); + tempPanel.setAttribute("type", "arrow"); + tempPanel.setAttribute("id", "customizationui-widget-panel"); + document.getElementById(CustomizableUI.AREA_NAVBAR).appendChild(tempPanel); + + let multiView = document.createElement("panelmultiview"); + tempPanel.appendChild(multiView); + multiView.setMainView(viewNode); + tempPanel.addEventListener("command", PanelUI._onWidgetPanelCommand); + + tempPanel.addEventListener("popuphidden", function panelRemover() { + tempPanel.removeEventListener("popuphidden", panelRemover); + tempPanel.removeEventListener("command", PanelUI._onWidgetPanelCommand); + this.multiView.appendChild(viewNode); + tempPanel.parentElement.removeChild(tempPanel); + }.bind(this)); + + let iconAnchor = + document.getAnonymousElementByAttribute(aAnchor, "class", + "toolbarbutton-icon"); + + tempPanel.openPopup(iconAnchor || aAnchor, "bottomcenter topright"); + } + }, + + /** + * 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.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.multiView.ignoreMutations = false; + }, + + /** + * 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"; + }, + + _onWidgetPanelCommand: function(aEvent) { + if (!aEvent.originalTarget.hasAttribute("noautoclose")) { + aEvent.currentTarget.hidePopup(); + } + }, + + _onHelpViewCommand: function(aEvent) { + if (!aEvent.originalTarget.hasAttribute("noautoclose")) { + PanelUI.hide(); + } + }, + + _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)); + } + fragment.appendChild(button); + } + items.appendChild(fragment); + + this.addEventListener("command", PanelUI._onHelpViewCommand); + }, + + _onHelpViewHide: function(aEvent) { + this.removeEventListener("command", PanelUI._onHelpViewCommand); + } +};
new file mode 100644 --- /dev/null +++ b/browser/components/customizableui/content/panelUI.xml @@ -0,0 +1,349 @@ +<?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="toolbarbutton" display="xul:button" + extends="chrome://global/content/bindings/button.xml#button-base"> + <resources> + <stylesheet src="chrome://global/skin/toolbarbutton.css"/> + </resources> + + <content> + <children includes="observes|template|menupopup|panel|tooltip"/> + <xul:hbox align="center" flex="1"> + <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/> + <xul:vbox flex="1"> + <xul:label class="toolbarbutton-text" crop="right" flex="1" + xbl:inherits="value=label,accesskey,crop"/> + <xul:hbox pack="end"> + <xul:label class="toolbarbutton-acceltext" crop="middle" + xbl:inherits="value=acceltext"/> + </xul:hbox> + </xul:vbox> + </xul:hbox> + </content> + + <implementation implements="nsIAccessibleProvider"> + <property name="accessibleType" readonly="true"> + <getter> + return Components.interfaces.nsIAccessibleProvider.XULToolbarButton; + </getter> + </property> + </implementation> + </binding> + + <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" viewtype="main" class="panel-viewstack"> + <xul:vbox anonid="mainViewContainer" class="panel-mainview"/> + + <!-- 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 document.getElementById(this._mainViewId);"/> + + <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("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("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._transitioning = true; + + this._viewContainer.addEventListener("transitionend", function trans() { + this._viewContainer.removeEventListener("transitionend", trans); + this._transitioning = false; + }.bind(this)); + this._viewContainer.style.height = this._mainViewHeight + "px"; + + this.setAttribute("viewtype", "main"); + } + + this._mainViewObserver.observe(this._mainView, { + attributes: true, + characterData: true, + childList: true, + subtree: true + }); + + this._shiftMainView(); + ]]></body> + </method> + + <method name="showSubView"> + <parameter name="aViewId"/> + <parameter name="aAnchor"/> + <body><![CDATA[ + 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 evt = document.createEvent("CustomEvent"); + evt.initCustomEvent("ViewShowing", true, true, viewNode); + viewNode.dispatchEvent(evt); + if (evt.defaultPrevented) { + return; + } + + this._currentSubView = viewNode; + + // Now we have to transition 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; + + this._transitioning = true; + this._viewContainer.addEventListener("transitionend", function trans() { + this._viewContainer.removeEventListener("transitionend", trans); + this._transitioning = false; + }.bind(this)); + this._viewContainer.style.height = this._subViews.scrollHeight + "px"; + + this._subViewObserver.observe(viewNode, { + attributes: true, + characterData: true, + childList: true, + subtree: true + }); + ]]></body> + </method> + + <method name="_shiftMainView"> + <parameter name="aAnchor"/> + <body><![CDATA[ + if (aAnchor) { + // We need to find the left 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 leftEdge = anchorRect.left - mainViewRect.left; + let center = aAnchor.clientWidth / 2; + let target = leftEdge + center; + this._mainViewContainer.style.transform = "translateX(-" + target + "px)"; + aAnchor.classList.add("panel-multiview-anchor"); + } else { + this._mainViewContainer.style.transform = ""; + if (this.anchorElement) + this.anchorElement.classList.remove("panel-multiview-anchor"); + } + this.anchorElement = aAnchor; + ]]></body> + </method> + + <method name="handleEvent"> + <parameter name="aEvent"/> + <body><![CDATA[ + switch(aEvent.type) { + case "click": + if (aEvent.originalTarget == this._clickCapturer) { + this.showMainView(); + } + break; + case "overflow": + // 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"); + this._syncContainerWithMainView(); + break; + case "popuphidden": + this.removeAttribute("panelopen"); + this.showMainView(); + break; + } + ]]></body> + </method> + + <method name="_syncContainerWithSubView"> + <body><![CDATA[ + if (!this.ignoreMutations && this.showingSubView) { + this._viewContainer.style.height = + this._subViews.scrollHeight + "px"; + } + ]]></body> + </method> + <method name="_syncContainerWithMainView"> + <body><![CDATA[ + if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) { + this._viewContainer.style.height = + this._mainView.scrollHeight + "px"; + } + ]]></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>
new file mode 100644 --- /dev/null +++ b/browser/components/customizableui/content/toolbar.xml @@ -0,0 +1,447 @@ +<?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"> + <resources> + <stylesheet src="chrome://global/skin/toolbar.css"/> + </resources> + <implementation implements="nsIAccessibleProvider"> + <property name="accessibleType" readonly="true"> + <getter> + return Components.interfaces.nsIAccessibleProvider.XULToolbar; + </getter> + </property> + + <constructor><![CDATA[ + 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; + + // 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); + } + } + } + + CustomizableUI.registerToolbar(this); + ]]></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" readonly="true"> + <getter><![CDATA[ + if (!this._customizationTarget) + return ""; + + return [node.id for (node of this._customizationTarget.children)].join(','); + ]]></getter> + </property> + + + </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 () !!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) { + // Don't move while customizing. + return this._dragBindingAlive && + this.getAttribute("customizing") != "true"; + }; + } 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"); + // 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); + } + } + } + CustomizableUI.registerToolbar(this); + 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; + if (!toolbar._isModifying) { + 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; + ]]></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; + + try { + aNode.setAttribute("removable", "true"); + + 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); + } catch (ex) { + Cu.reportError(ex); + // This will throw if the node is too big, or can't be moved there for + // some reason. Try to remove it anyway: + 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; + // We will now have moved stuff around; kick off an aftercustomization event + // so add-ons know we've just moved their stuff: + if (window.gCustomizeMode) { + window.gCustomizeMode.dispatchToolboxEvent("aftercustomization"); + } + return node; + ]]></body> + </method> + <property name="customizationTarget" readonly="true"> + <getter><![CDATA[ + return this; + ]]></getter> + </property> + <property name="currentSet"> + <getter><![CDATA[ + return [node.id for (node of this.children)].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 (x of newButtons) { + this._currentSetMigrated.add(x); + this.insertItem(x); + } + ]]></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>
new file mode 100644 --- /dev/null +++ b/browser/components/customizableui/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; c-basic-offset: 4; 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/. + +PARALLEL_DIRS += [ + 'content', + 'src', + 'test', +]
new file mode 100644 --- /dev/null +++ b/browser/components/customizableui/src/CustomizableUI.jsm @@ -0,0 +1,2184 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +this.EXPORTED_SYMBOLS = ["CustomizableUI"]; + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/NetUtil.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "CustomizableWidgets", + "resource:///modules/CustomizableWidgets.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", + "resource://gre/modules/DeferredTask.jsm"); +XPCOMUtils.defineLazyGetter(this, "gWidgetsBundle", function() { + const kUrl = "chrome://browser/locale/customizableui/customizableWidgets.properties"; + return Services.strings.createBundle(kUrl); +}); +XPCOMUtils.defineLazyServiceGetter(this, "gELS", + "@mozilla.org/eventlistenerservice;1", "nsIEventListenerService"); + +const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + +const kSpecialWidgetPfx = "customizableui-special-"; + +const kCustomizationContextMenu = "customizationContextMenu"; +const kContextMenuBackup = "customization-old-context"; + + +const kPrefCustomizationState = "browser.uiCustomization.state"; +const kPrefCustomizationAutoAdd = "browser.uiCustomization.autoAdd"; +const kPrefCustomizationDebug = "browser.uiCustomization.debug"; + +/** + * The keys are the handlers that are fired when the event type (the value) + * is fired on the subview. A widget that provides a subview has the option + * of providing onViewShowing and onViewHiding event handlers. + */ +const kSubviewEvents = [ + "ViewShowing", + "ViewHiding" +]; + +/** + * gPalette is a map of every widget that CustomizableUI.jsm knows about, keyed + * on their IDs. + */ +let gPalette = new Map(); + +/** + * gAreas maps area IDs to Sets of properties about those areas. An area is a + * place where a widget can be put. + */ +let gAreas = new Map(); + +/** + * gPlacements maps area IDs to Arrays of widget IDs, indicating that the widgets + * are placed within that area (either directly in the area node, or in the + * customizationTarget of the node). + */ +let gPlacements = new Map(); + +/** + * gFuturePlacements represent placements that will happen for areas that have + * not yet loaded (due to lazy-loading). This can occur when add-ons register + * widgets. + */ +let gFuturePlacements = new Map(); + +//XXXunf Temporary. Need a nice way to abstract functions to build widgets +// of these types. +let gSupportedWidgetTypes = new Set(["button", "view", "custom"]); + +/** + * gSeenWidgets remembers which widgets the user has seen for the first time + * before. This way, if a new widget is created, and the user has not seen it + * before, it can be put in its default location. Otherwise, it remains in the + * palette. + */ +let gSeenWidgets = new Set(); + +let gSavedState = null; +let gRestoring = false; +let gDirty = false; +let gInBatch = false; +let gResetting = false; + +/** + * gBuildAreas maps area IDs to actual area nodes within browser windows. + */ +let gBuildAreas = new Map(); + +/** + * gBuildWindows is a map of windows that have registered build areas, mapped + * to a Set of known toolboxes in that window. + */ +let gBuildWindows = new Map(); + +let gNewElementCount = 0; +let gWrapperCache = new WeakMap(); +let gListeners = new Set(); + +let gModuleName = "[CustomizableUI]"; +#include logging.js + +let CustomizableUIInternal = { + initialize: function() { + LOG("Initializing"); + + this.addListener(this); + this._defineBuiltInWidgets(); + this.loadSavedState(); + + this.registerArea(CustomizableUI.AREA_PANEL, { + anchor: "PanelUI-menu-button", + type: CustomizableUI.TYPE_MENU_PANEL, + defaultPlacements: [ + "edit-controls", + "zoom-controls", + "new-window-button", + "privatebrowsing-button", + "save-page-button", + "print-button", + "history-panelmenu", + "fullscreen-button", + "find-button", + "preferences-button", + "add-ons-button", + ] + }); + this.registerArea(CustomizableUI.AREA_NAVBAR, { + legacy: true, + type: CustomizableUI.TYPE_TOOLBAR, + overflowable: true, + defaultPlacements: [ + "unified-back-forward-button", + "urlbar-container", + "reload-button", + "stop-button", + "search-container", + "webrtc-status-button", + "bookmarks-menu-button", + "downloads-button", + "home-button", + "social-share-button", + ] + }); + this.registerArea(CustomizableUI.AREA_MENUBAR, { + legacy: true, + type: CustomizableUI.TYPE_TOOLBAR, + defaultPlacements: [ + "menubar-items", + ] + }); + this.registerArea(CustomizableUI.AREA_TABSTRIP, { + legacy: true, + type: CustomizableUI.TYPE_TOOLBAR, + defaultPlacements: [ + "tabbrowser-tabs", + "new-tab-button", + "alltabs-button", + "tabs-closebutton", + ] + }); + this.registerArea(CustomizableUI.AREA_BOOKMARKS, { + legacy: true, + type: CustomizableUI.TYPE_TOOLBAR, + defaultPlacements: [ + "personal-bookmarks", + ] + }); + + this.registerArea(CustomizableUI.AREA_ADDONBAR, { + type: CustomizableUI.TYPE_TOOLBAR, + legacy: true, + defaultPlacements: ["addonbar-closebutton", "status-bar"] + }); + }, + + _defineBuiltInWidgets: function() { + //XXXunf Need to figure out how to auto-add new builtin widgets in new + // app versions to already customized areas. + for (let widgetDefinition of CustomizableWidgets) { + this.createBuiltinWidget(widgetDefinition); + } + }, + + wrapWidget: function(aWidgetId) { + let provider = this.getWidgetProvider(aWidgetId); + if (!provider) { + return null; + } + + if (provider == CustomizableUI.PROVIDER_API) { + let widget = gPalette.get(aWidgetId); + if (!widget.wrapper) { + widget.wrapper = new WidgetGroupWrapper(widget); + } + return widget.wrapper; + } + + // PROVIDER_SPECIAL gets treated the same as PROVIDER_XUL. + return new XULWidgetGroupWrapper(aWidgetId); + }, + + registerArea: function(aName, aProperties) { + if (typeof aName != "string" || !/^[a-z0-9-_]{1,}$/i.test(aName)) { + throw new Error("Invalid area name"); + } + if (gAreas.has(aName)) { + throw new Error("Area already registered"); + } + + let props = new Map(); + for (let key in aProperties) { + //XXXgijs for special items, we need to make sure they have an appropriate ID + // so we aren't perpetually in a non-default state: + if (key == "defaultPlacements" && Array.isArray(aProperties[key])) { + props.set(key, aProperties[key].map(x => this.isSpecialWidget(x) ? this.ensureSpecialWidgetId(x) : x )); + } else { + props.set(key, aProperties[key]); + } + } + gAreas.set(aName, props); + + if (props.get("legacy")) { + // Guarantee this area exists in gFuturePlacements, to avoid checking it in + // various places elsewhere. + gFuturePlacements.set(aName, new Set()); + } else { + this.restoreStateForArea(aName); + } + }, + + unregisterArea: function(aName) { + if (typeof aName != "string" || !/^[a-z0-9-_]{1,}$/i.test(aName)) { + throw new Error("Invalid area name"); + } + if (!gAreas.has(aName)) { + throw new Error("Area not registered"); + } + + // Move all the widgets out + this.beginBatchUpdate(); + let placements = gPlacements.get(aName); + placements.forEach(this.removeWidgetFromArea, this); + + // Delete all remaining traces. + gAreas.delete(aName); + gPlacements.delete(aName); + gFuturePlacements.delete(aName); + this.endBatchUpdate(true); + }, + + registerToolbar: function(aToolbar) { + let document = aToolbar.ownerDocument; + let area = aToolbar.id; + + if (!gAreas.has(area)) { + throw new Error("Unknown customization area: " + area); + } + + if (this.isBuildAreaRegistered(area, aToolbar)) { + return; + } + + let areaProperties = gAreas.get(area); + + if (!gPlacements.has(area) && areaProperties.has("legacy")) { + let legacyState = aToolbar.getAttribute("currentset"); + if (legacyState) { + legacyState = legacyState.split(",").filter(s => s); + } + + // Manually restore the state here, so the legacy state can be converted. + this.restoreStateForArea(area, legacyState); + } + + if (areaProperties.has("overflowable")) { + aToolbar.overflowable = new OverflowableToolbar(aToolbar); + } + + this.registerBuildArea(area, aToolbar); + + let placements = gPlacements.get(area); + this.buildArea(area, placements, aToolbar); + aToolbar.setAttribute("currentset", placements.join(",")); + }, + + buildArea: function(aArea, aPlacements, aAreaNode) { + let document = aAreaNode.ownerDocument; + let window = document.defaultView; + let container = aAreaNode.customizationTarget; + + if (!container) { + throw new Error("Expected area " + aArea + + " to have a customizationTarget attribute."); + } + + this.beginBatchUpdate(); + + let currentNode = container.firstChild; + for (let id of aPlacements) { + if (currentNode && currentNode.id == id) { + this._addParentFlex(currentNode); + this.setLocationAttributes(currentNode, container, aArea); + + // Normalize removable attribute. It defaults to false if the widget is + // originally defined as a child of a build area. + if (!currentNode.hasAttribute("removable")) { + currentNode.setAttribute("removable", this.isWidgetRemovable(id)); + } + + currentNode = currentNode.nextSibling; + continue; + } + + let [provider, node] = this.getWidgetNode(id, window); + if (!node) { + LOG("Unknown widget: " + id); + continue; + } + + this.ensureButtonContextMenu(node, aArea == CustomizableUI.AREA_PANEL); + + this.insertWidgetBefore(node, currentNode, container, aArea); + this._addParentFlex(node); + if (gResetting) + this.notifyListeners("onWidgetReset", id); + } + + if (currentNode) { + let palette = aAreaNode.toolbox ? aAreaNode.toolbox.palette : null; + let limit = currentNode.previousSibling; + let node = container.lastChild; + while (node && node != limit) { + let previousSibling = node.previousSibling; + // Nodes opt-in to removability. If they're removable, and we haven't + // seen them in the placements array, then we toss them into the palette + // if one exists. If no palette exists, we just remove the node. If the + // node is not removable, we leave it where it is. However, we can only + // safely touch elements that have an ID - both because we depend on + // IDs, and because such elements are not intended to be widgets + // (eg, titlebar-placeholder elements). + if (node.id) { + if (this.isWidgetRemovable(node.id)) { + if (palette) { + palette.appendChild(node); + } else { + container.removeChild(node); + } + } else if (node.getAttribute("skipintoolbarset") != "true") { + this.setLocationAttributes(currentNode, container, aArea); + node.setAttribute("removable", false); + LOG("Adding non-removable widget to placements of " + aArea + ": " + + node.id); + gPlacements.get(aArea).push(node.id); + gDirty = true; + } + } + node = previousSibling; + } + } + + this.endBatchUpdate(); + }, + + ensureButtonsClosePanel: function(aPanel) { + gELS.addSystemEventListener(aPanel, "click", this, false); + gELS.addSystemEventListener(aPanel, "keypress", this, false); + }, + + removePanelCloseListeners: function(aPanel) { + gELS.removeSystemEventListener(aPanel, "click", this, false); + gELS.removeSystemEventListener(aPanel, "keypress", this, false); + }, + + ensureButtonContextMenu: function(aNode, ourContextMenu) { + if (ourContextMenu) { + let currentCtxt = aNode.getAttribute("context"); + // Need to save widget's own context menu if we're replacing it: + if (currentCtxt && currentCtxt != kCustomizationContextMenu) { + aNode.setAttribute(kContextMenuBackup, currentCtxt); + } + aNode.setAttribute("context", kCustomizationContextMenu); + } else if (aNode.getAttribute("context") == kCustomizationContextMenu) { + let oldCtxt = aNode.getAttribute(kContextMenuBackup); + if (oldCtxt) { + aNode.setAttribute("context", oldCtxt); + aNode.removeAttribute(kContextMenuBackup); + } else { + aNode.removeAttribute("context"); + } + } + }, + + getWidgetProvider: function(aWidgetId) { + if (this.isSpecialWidget(aWidgetId)) { + return CustomizableUI.PROVIDER_SPECIAL; + } + if (gPalette.has(aWidgetId)) { + return CustomizableUI.PROVIDER_API; + } + + // We fall back to the XUL provider, but we don't know for sure (at this + // point) whether it exists there either. So the API is technically lying. + // Ideally, it would be able to return an error value (or throw an + // exception) if it really didn't exist. Our code calling this function + // handles that fine, but this is a public API. + return CustomizableUI.PROVIDER_XUL; + }, + + getWidgetNode: function(aWidgetId, aWindow) { + let document = aWindow.document; + + if (this.isSpecialWidget(aWidgetId)) { + let widgetNode = document.getElementById(aWidgetId) || + this.createSpecialWidget(aWidgetId, document); + return [ CustomizableUI.PROVIDER_SPECIAL, widgetNode]; + } + + let widget = gPalette.get(aWidgetId); + if (widget) { + // If we have an instance of this widget already, just use that. + if (widget.instances.has(document)) { + LOG("An instance of widget " + aWidgetId + " already exists in this " + + "document. Reusing."); + return [ CustomizableUI.PROVIDER_API, + widget.instances.get(document) ]; + } + + return [ CustomizableUI.PROVIDER_API, + this.buildWidget(document, widget) ]; + } + + LOG("Searching for " + aWidgetId + " in toolbox."); + let node = this.findWidgetInWindow(aWidgetId, aWindow); + if (node) { + return [ CustomizableUI.PROVIDER_XUL, node ]; + } + + LOG("No node for " + aWidgetId + " found."); + return []; + }, + + registerMenuPanel: function(aPanel) { + if (this.isBuildAreaRegistered(CustomizableUI.AREA_PANEL, aPanel)) { + return; + } + + let document = aPanel.ownerDocument; + + for (let btn of aPanel.querySelectorAll("toolbarbutton")) { + this.ensureButtonContextMenu(btn, true); + } + + aPanel.toolbox = document.getElementById("navigator-toolbox"); + aPanel.customizationTarget = aPanel; + + this.ensureButtonsClosePanel(aPanel); + + let placements = gPlacements.get(CustomizableUI.AREA_PANEL); + this.buildArea(CustomizableUI.AREA_PANEL, placements, aPanel); + this.registerBuildArea(CustomizableUI.AREA_PANEL, aPanel); + }, + + onWidgetAdded: function(aWidgetId, aArea, aPosition) { + let areaNodes = gBuildAreas.get(aArea); + if (!areaNodes) { + return; + } + + let placements = gPlacements.get(aArea); + if (!placements) { + ERROR("Could not find any placements for " + aArea + + " when adding a widget."); + return; + } + + let nextNodeId = placements[aPosition + 1]; + + // Go through each of the nodes associated with this area and move the + // widget to the requested location. + for (let areaNode of areaNodes) { + let window = areaNode.ownerDocument.defaultView; + let container = areaNode.customizationTarget; + let [provider, widgetNode] = this.getWidgetNode(aWidgetId, window); + + this.ensureButtonContextMenu(widgetNode, aArea == CustomizableUI.AREA_PANEL); + + let nextNode = nextNodeId ? container.querySelector(idToSelector(nextNodeId)) + : null; + this.insertWidgetBefore(widgetNode, nextNode, container, aArea); + this._addParentFlex(widgetNode); + } + }, + + onWidgetRemoved: function(aWidgetId, aArea) { + let areaNodes = gBuildAreas.get(aArea); + if (!areaNodes) { + return; + } + + for (let areaNode of areaNodes) { + let container = areaNode.customizationTarget; + let widgetNode = container.ownerDocument.getElementById(aWidgetId); + if (!widgetNode) { + ERROR("Widget not found, unable to remove"); + continue; + } + + this._removeParentFlex(widgetNode); + + if (gPalette.has(aWidgetId) || this.isSpecialWidget(aWidgetId)) { + container.removeChild(widgetNode); + } else { + this.removeLocationAttributes(widgetNode); + areaNode.toolbox.palette.appendChild(widgetNode); + } + } + }, + + onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) { + let areaNodes = gBuildAreas.get(aArea); + if (!areaNodes) { + return; + } + + let placements = gPlacements.get(aArea); + if (!placements) { + ERROR("Could not find any placements for " + aArea + + " when moving a widget."); + return; + } + + let nextNodeId = placements[aNewPosition + 1]; + + for (let areaNode of areaNodes) { + let window = areaNode.ownerDocument.defaultView; + let container = areaNode.customizationTarget; + let [provider, widgetNode] = this.getWidgetNode(aWidgetId, window); + if (!widgetNode) { + ERROR("Widget not found, unable to move"); + continue; + } + + let nextNode = nextNodeId ? container.querySelector(idToSelector(nextNodeId)) + : null; + this.insertWidgetBefore(widgetNode, nextNode, container, aArea); + } + }, + + isBuildAreaRegistered: function(aArea, aInstance) { + if (!gBuildAreas.has(aArea)) { + return false; + } + return gBuildAreas.get(aArea).has(aInstance); + }, + + registerBuildArea: function(aArea, aNode) { + // We ensure that the window is registered to have its customization data + // cleaned up when unloading. + let window = aNode.ownerDocument.defaultView; + this.registerBuildWindow(window); + + // Also register this build area's toolbox. + if (aNode.toolbox) { + gBuildWindows.get(window).add(aNode.toolbox); + } + + if (!gBuildAreas.has(aArea)) { + gBuildAreas.set(aArea, new Set()); + } + + gBuildAreas.get(aArea).add(aNode); + }, + + registerBuildWindow: function(aWindow) { + if (!gBuildWindows.has(aWindow)) { + gBuildWindows.set(aWindow, new Set()); + } + + aWindow.addEventListener("unload", this, false); + }, + + unregisterBuildWindow: function(aWindow) { + gBuildWindows.delete(aWindow); + let document = aWindow.document; + + for (let [areaId, areaNodes] of gBuildAreas) { + let areaProperties = gAreas.get(areaId); + for (let node of areaNodes) { + if (node.ownerDocument == document) { + if (areaProperties.has("overflowable")) { + node.overflowable.uninit(); + node.overflowable = null; + } + areaNodes.delete(node); + } + } + } + + for (let [,widget] of gPalette) { + widget.instances.delete(document); + this.notifyListeners("onWidgetInstanceRemoved", widget.id, document); + } + }, + + setLocationAttributes: function(aNode, aContainer, aArea) { + let props = gAreas.get(aArea); + if (!props) { + throw new Error("Expected area " + aArea + " to have a properties Map " + + "associated with it."); + } + + aNode.setAttribute("customizableui-areatype", props.get("type") || ""); + aNode.setAttribute("customizableui-anchorid", props.get("anchor") || ""); + }, + + removeLocationAttributes: function(aNode) { + aNode.removeAttribute("customizableui-areatype"); + aNode.removeAttribute("customizableui-anchorid"); + }, + + insertWidgetBefore: function(aNode, aNextNode, aContainer, aArea) { + this.setLocationAttributes(aNode, aContainer, aArea); + aContainer.insertBefore(aNode, aNextNode); + }, + + handleEvent: function(aEvent) { + switch (aEvent.type) { + case "click": + case "keypress": + this.maybeAutoHidePanel(aEvent); + break; + case "unload": { + let window = aEvent.currentTarget; + window.removeEventListener("unload", this); + this.unregisterBuildWindow(window); + break; + } + } + }, + + isSpecialWidget: function(aId) { + return (aId.startsWith(kSpecialWidgetPfx) || + aId.startsWith("separator") || + aId.startsWith("spring") || + aId.startsWith("spacer")); + }, + + ensureSpecialWidgetId: function(aId) { + let nodeType = aId.match(/spring|spacer|separator/)[0]; + // If the ID we were passed isn't a generated one, generate one now: + if (nodeType == aId) { + // Due to timers resolution Date.now() can be the same for + // elements created in small timeframes. So ids are + // differentiated through a unique count suffix. + return kSpecialWidgetPfx + aId + Date.now() + (++gNewElementCount); + } + return aId; + }, + + createSpecialWidget: function(aId, aDocument) { + let nodeName = "toolbar" + aId.match(/spring|spacer|separator/)[0]; + let node = aDocument.createElementNS(kNSXUL, nodeName); + node.id = this.ensureSpecialWidgetId(aId); + if (nodeName == "toolbarspring") { + node.flex = 1; + } + return node; + }, + + /* Find a XUL-provided widget in a window. Don't try to use this + * for an API-provided widget or a special widget. + */ + findWidgetInWindow: function(aId, aWindow) { + if (!gBuildWindows.has(aWindow)) { + throw new Error("Build window not registered"); + } + + let document = aWindow.document; + + // look for a node with the same id, as the node may be + // in a different toolbar. + let node = document.getElementById(aId); + if (node) { + let parent = node.parentNode; + while (parent && !(parent.customizationTarget || + parent.localName == "toolbarpaletteitem")) { + parent = parent.parentNode; + } + + if ((parent && parent.customizationTarget == node.parentNode && + gBuildWindows.get(aWindow).has(parent.toolbox)) || + (parent && parent.localName == "toolbarpaletteitem")) { + // Normalize the removable attribute. For backwards compat, if + // the widget is not defined in a toolbox palette then absence + // of the "removable" attribute means it is not removable. + if (!node.hasAttribute("removable")) { + // If we first see this in customization mode, it may be in the + // customization palette instead of the toolbox palette. + node.setAttribute("removable", !parent.customizationTarget); + } + + return node; + } + } + + let toolboxes = gBuildWindows.get(aWindow); + for (let toolbox of toolboxes) { + if (toolbox.palette) { + // Attempt to locate a node with a matching ID within + // the palette. + let node = toolbox.palette.querySelector(idToSelector(aId)); + if (node) { + // Normalize the removable attribute. For backwards compat, this + // is optional if the widget is defined in the toolbox palette, + // and defaults to *true*, unlike if it was defined elsewhere. + if (!node.hasAttribute("removable")) { + node.setAttribute("removable", true); + } + return node; + } + } + } + return null; + }, + + buildWidget: function(aDocument, aWidget) { + if (typeof aWidget == "string") { + aWidget = gPalette.get(aWidget); + } + if (!aWidget) { + throw new Error("buildWidget was passed a non-widget to build."); + } + + LOG("Building " + aWidget.id + " of type " + aWidget.type); + + let node; + if (aWidget.type == "custom") { + if (aWidget.onBuild) { + try { + node = aWidget.onBuild(aDocument); + } catch (ex) { + ERROR("Custom widget with id " + aWidget.id + " threw an error: " + ex.message); + } + } + if (!node || !(node instanceof aDocument.defaultView.XULElement)) + ERROR("Custom widget with id " + aWidget.id + " does not return a valid node"); + } + else { + node = aDocument.createElementNS(kNSXUL, "toolbarbutton"); + + node.setAttribute("id", aWidget.id); + node.setAttribute("widget-id", aWidget.id); + node.setAttribute("widget-type", aWidget.type); + if (aWidget.disabled) { + node.setAttribute("disabled", true); + } + node.setAttribute("removable", aWidget.removable); + node.setAttribute("label", this.getLocalizedProperty(aWidget, "label")); + node.setAttribute("tooltiptext", this.getLocalizedProperty(aWidget, "tooltiptext")); + //XXXunf Need to hook this up to a <key> element or something. + let shortcut = this.getLocalizedProperty(aWidget, "shortcut"); + if (shortcut) { + node.setAttribute("acceltext", shortcut); + } + node.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional"); + + let commandHandler = this.handleWidgetCommand.bind(this, aWidget, node); + node.addEventListener("command", commandHandler, false); + let clickHandler = this.handleWidgetClick.bind(this, aWidget, node); + node.addEventListener("click", clickHandler, false); + + // If the widget has a view, and has view showing / hiding listeners, + // hook those up to this widget. + if (aWidget.type == "view" && + (aWidget.onViewShowing || aWidget.onViewHiding)) { + LOG("Widget " + aWidget.id + " has a view with showing and hiding events. Auto-registering event handlers."); + let viewNode = aDocument.getElementById(aWidget.viewId); + + if (!viewNode) { + ERROR("Could not find the view node with id: " + aWidget.viewId); + throw new Error("Could not find the view node with id: " + aWidget.viewId); + } + + // PanelUI relies on the .PanelUI-subView class to be able to show only + // one sub-view at a time. + viewNode.classList.add("PanelUI-subView"); + + for (let eventName of kSubviewEvents) { + let handler = "on" + eventName; + if (typeof aWidget[handler] == "function") { + viewNode.addEventListener(eventName, aWidget[handler], false); + } + } + + LOG("Widget " + aWidget.id + " showing and hiding event handlers set."); + } + + if (aWidget.onCreated) { + aWidget.onCreated(node); + } + } + + aWidget.instances.set(aDocument, node); + return node; + }, + + getLocalizedProperty: function(aWidget, aProp, aFormatArgs, aDef) { + if (typeof aWidget == "string") { + aWidget = gPalette.get(aWidget); + } + if (!aWidget) { + throw new Error("getLocalizedProperty was passed a non-widget to work with."); + } + if (typeof aWidget[aProp] == "string") { + return aWidget[aProp]; + } + let def = aDef || ""; + let name = aWidget.id + "." + aProp; + try { + if (Array.isArray(aFormatArgs) && aFormatArgs.length) { + return gWidgetsBundle.formatStringFromName(name, aFormatArgs, + aFormatArgs.length) || def; + } + return gWidgetsBundle.GetStringFromName(name) || def; + } catch(ex) { + ERROR("Could not localize property '" + name + "'."); + } + return def; + }, + + handleWidgetCommand: function(aWidget, aNode, aEvent) { + LOG("handleWidgetCommand"); + + if (aWidget.type == "button") { + this.maybeAutoHidePanel(aEvent); + + if (aWidget.onCommand) { + try { + aWidget.onCommand.call(null, aEvent); + } catch (e) { + ERROR(e); + } + } else { + //XXXunf Need to think this through more, and formalize. + Services.obs.notifyObservers(aNode, + "customizedui-widget-command", + aWidget.id); + } + } else if (aWidget.type == "view") { + let ownerWindow = aNode.ownerDocument.defaultView; + ownerWindow.PanelUI.showSubView(aWidget.viewId, aNode, + this.getPlacementOfWidget(aNode.id).area); + } + }, + + handleWidgetClick: function(aWidget, aNode, aEvent) { + LOG("handleWidgetClick"); + if (aWidget.type == "button") { + this.maybeAutoHidePanel(aEvent); + } + + if (aWidget.onClick) { + try { + aWidget.onClick.call(null, aEvent); + } catch(e) { + Cu.reportError(e); + } + } else { + //XXXunf Need to think this through more, and formalize. + Services.obs.notifyObservers(aNode, "customizedui-widget-click", aWidget.id); + } + }, + + /* + * If people put things in the panel which need more than single-click interaction, + * we don't want to close it. Right now we check for text inputs and menu buttons. + * Anything else we should take care of? + */ + _isOnInteractiveElement: function(aEvent) { + let target = aEvent.originalTarget; + let panel = aEvent.currentTarget; + let inInput = false; + let inMenu = false; + while (!inInput && !inMenu && target != aEvent.currentTarget) { + inInput = target.localName == "input"; + inMenu = target.type == "menu"; + target = target.parentNode; + } + return inMenu || inInput; + }, + + hidePanelForNode: function(aNode) { + let panel = aNode; + while (panel && panel.localName != "panel") + panel = panel.parentNode; + if (panel) { + panel.hidePopup(); + } + }, + + maybeAutoHidePanel: function(aEvent) { + if (aEvent.type == "keypress") { + if (aEvent.keyCode != aEvent.DOM_VK_ENTER && + aEvent.keyCode != aEvent.DOM_VK_RETURN) { + return; + } + // If the user hit enter/return, we don't check preventDefault - it makes sense + // that this was prevented, but we probably still want to close the panel. + // If consumers don't want this to happen, they should specify noautoclose. + + } else { // mouse events: + if (aEvent.defaultPrevented || aEvent.button != 0) { + return; + } + let isInteractive = this._isOnInteractiveElement(aEvent); + LOG("maybeAutoHidePanel: interactive ? " + isInteractive); + if (isInteractive) { + return; + } + } + + if (aEvent.target.getAttribute("noautoclose") == "true" || + aEvent.target.getAttribute("widget-type") == "view") { + return; + } + + // If we get here, we can actually hide the popup: + this.hidePanelForNode(aEvent.target); + }, + + getUnusedWidgets: function(aWindowPalette) { + // We use a Set because there can be overlap between the widgets in + // gPalette and the items in the palette, especially after the first + // customization, since programmatically generated widgets will remain + // in the toolbox palette. + let widgets = new Set(); + + // It's possible that some widgets have been defined programmatically and + // have not been overlayed into the palette. We can find those inside + // gPalette. + for (let [id, widget] of gPalette) { + if (!widget.currentArea) { + widgets.add(id); + } + } + + LOG("Iterating the actual nodes of the window palette"); + for (let node of aWindowPalette.children) { + LOG("In palette children: " + node.id); + if (node.id && !this.getPlacementOfWidget(node.id)) { + widgets.add(node.id); + } + } + + return [...widgets]; + }, + + getPlacementOfWidget: function(aWidgetId) { + for (let [area, placements] of gPlacements) { + let index = placements.indexOf(aWidgetId); + if (index != -1) { + return { area: area, position: index }; + } + } + + return null; + }, + + addWidgetToArea: function(aWidgetId, aArea, aPosition) { + if (!gAreas.has(aArea)) { + throw new Error("Unknown customization area: " + aArea); + } + + // If this is a lazy area that hasn't been restored yet, we can't yet modify + // it - would would at least like to add to it. So we keep track of it in + // gFuturePlacements, and use that to add it when restoring the area. We + // throw away aPosition though, as that can only be bogus if the area hasn't + // yet been restorted (caller can't possibly know where its putting the + // widget in relation to other widgets). + if (this.isAreaLazy(aArea)) { + gFuturePlacements.get(aArea).add(aWidgetId); + return; + } + + if (this.isSpecialWidget(aWidgetId)) { + aWidgetId = this.ensureSpecialWidgetId(aWidgetId); + } + + let oldPlacement = this.getPlacementOfWidget(aWidgetId); + if (oldPlacement && oldPlacement.area == aArea) { + this.moveWidgetWithinArea(aWidgetId, aPosition); + return; + } + + // Do nothing if the widget is not allowed to move to the target area. + if (!this.canWidgetMoveToArea(aWidgetId, aArea)) { + return; + } + + if (oldPlacement) { + this.removeWidgetFromArea(aWidgetId); + } + + if (!gPlacements.has(aArea)) { + gPlacements.set(aArea, [aWidgetId]); + aPosition = 0; + } else { + let placements = gPlacements.get(aArea); + if (typeof aPosition != "number") { + aPosition = placements.length; + } + if (aPosition < 0) { + aPosition = 0; + } + placements.splice(aPosition, 0, aWidgetId); + } + + let widget = gPalette.get(aWidgetId); + if (widget) { + widget.currentArea = aArea; + widget.currentPosition = aPosition; + } + + gDirty = true; + this.saveState(); + + this.notifyListeners("onWidgetAdded", aWidgetId, aArea, aPosition); + }, + + removeWidgetFromArea: function(aWidgetId) { + let oldPlacement = this.getPlacementOfWidget(aWidgetId); + if (!oldPlacement) { + return; + } + + if (!this.isWidgetRemovable(aWidgetId)) { + return; + } + + let placements = gPlacements.get(oldPlacement.area); + let position = placements.indexOf(aWidgetId); + if (position != -1) { + placements.splice(position, 1); + } + + let widget = gPalette.get(aWidgetId); + if (widget) { + widget.currentArea = null; + widget.currentPosition = null; + } + + gDirty = true; + this.saveState(); + + this.notifyListeners("onWidgetRemoved", aWidgetId, oldPlacement.area); + }, + + moveWidgetWithinArea: function(aWidgetId, aPosition) { + let oldPlacement = this.getPlacementOfWidget(aWidgetId); + if (!oldPlacement) { + return; + } + + let placements = gPlacements.get(oldPlacement.area); + if (typeof aPosition != "number") { + aPosition = placements.length; + } else if (aPosition < 0) { + aPosition = 0; + } else if (aPosition > placements.length) { + aPosition = placements.length; + } + + if (aPosition == oldPlacement.position) { + return; + } + + placements.splice(oldPlacement.position, 1); + // If we just removed the item from *before* where it is now added, + // we need to compensate the position offset for that: + if (oldPlacement.position < aPosition) { + aPosition--; + } + placements.splice(aPosition, 0, aWidgetId); + + let widget = gPalette.get(aWidgetId); + if (widget) { + widget.currentPosition = aPosition; + } + + gDirty = true; + this.saveState(); + + this.notifyListeners("onWidgetMoved", aWidgetId, oldPlacement.area, + oldPlacement.position, aPosition); + }, + + // Note that this does not populate gPlacements, which is done lazily so that + // the legacy state can be migrated, which is only available once a browser + // window is openned. + // The panel area is an exception here, since it has no legacy state and is + // built lazily - and therefore wouldn't otherwise result in restoring its + // state immediately when a browser window opens, which is important for + // other consumers of this API. + loadSavedState: function() { + let state = null; + try { + state = Services.prefs.getCharPref(kPrefCustomizationState); + } catch (e) { + LOG("No saved state found"); + // This will fail if nothing has been customized, so silently fall back to + // the defaults. + } + + if (!state) { + return; + } + try { + gSavedState = JSON.parse(state); + } catch(e) { + LOG("Error loading saved UI customization state, falling back to defaults."); + } + + if (!("placements" in gSavedState)) { + gSavedState.placements = {}; + } + + gSeenWidgets = new Set(gSavedState.seen || []); + }, + + restoreStateForArea: function(aArea, aLegacyState) { + if (gPlacements.has(aArea)) { + // Already restored. + return; + } + + this.beginBatchUpdate(); + gRestoring = true; + + let restored = false; + gPlacements.set(aArea, []); + + if (gSavedState && aArea in gSavedState.placements) { + LOG("Restoring " + aArea + " from saved state"); + let placements = gSavedState.placements[aArea]; + for (let id of placements) + this.addWidgetToArea(id, aArea); + gDirty = false; + restored = true; + } + + if (!restored && aLegacyState) { + LOG("Restoring " + aArea + " from legacy state"); + for (let id of aLegacyState) + this.addWidgetToArea(id, aArea); + // Don't override dirty state, to ensure legacy state is saved here and + // therefore only used once. + restored = true; + } + + if (!restored) { + LOG("Restoring " + aArea + " from default state"); + let defaults = gAreas.get(aArea).get("defaultPlacements"); + if (defaults) { + for (let id of defaults) + this.addWidgetToArea(id, aArea); + } + gDirty = false; + } + + // Finally, add widgets to the area that were added before the it was able + // to be restored. This can occur when add-ons register widgets for a + // lazily-restored area before it's been restored. + if (gFuturePlacements.has(aArea)) { + for (let id of gFuturePlacements.get(aArea)) + this.addWidgetToArea(id, aArea); + } + + LOG("Placements for " + aArea + ":\n\t" + gPlacements.get(aArea).join("\n\t")); + + gRestoring = false; + this.endBatchUpdate(); + }, + + saveState: function() { + if (gInBatch || !gDirty) { + return; + } + let state = { placements: gPlacements, + seen: gSeenWidgets }; + + LOG("Saving state."); + let serialized = JSON.stringify(state, this.serializerHelper); + LOG("State saved as: " + serialized); + Services.prefs.setCharPref(kPrefCustomizationState, serialized); + gDirty = false; + }, + + serializerHelper: function(aKey, aValue) { + if (typeof aValue == "object" && aValue.constructor.name == "Map") { + let result = {}; + for (let [mapKey, mapValue] of aValue) + result[mapKey] = mapValue; + return result; + } + + if (typeof aValue == "object" && aValue.constructor.name == "Set") { + return [...aValue]; + } + + return aValue; + }, + + beginBatchUpdate: function() { + gInBatch = true; + }, + + endBatchUpdate: function(aForceSave) { + gInBatch = false; + if (aForceSave === true) { + gDirty = true; + } + this.saveState(); + }, + + addListener: function(aListener) { + gListeners.add(aListener); + }, + + removeListener: function(aListener) { + if (aListener == this) { + return; + } + + gListeners.delete(aListener); + }, + + notifyListeners: function(aEvent, ...aArgs) { + if (gRestoring) { + return; + } + + for (let listener of gListeners) { + try { + if (aEvent in listener) { + listener[aEvent].apply(listener, aArgs); + } + } catch (e) { + ERROR(e + " -- " + e.fileName + ":" + e.lineNumber); + } + } + }, + + createWidget: function(aProperties) { + let widget = this.normalizeWidget(aProperties, CustomizableUI.SOURCE_EXTERNAL); + //XXXunf This should probably throw. + if (!widget) { + return; + } + + gPalette.set(widget.id, widget); + this.notifyListeners("onWidgetCreated", widget.id); + + if (widget.defaultArea) { + let area = gAreas.get(widget.defaultArea); + //XXXgijs this won't have any effect for legacy items. Sort of OK because + // consumers can modify currentset? Maybe? + if (area.has("defaultPlacements")) { + area.get("defaultPlacements").push(widget.id); + } else { + area.set("defaultPlacements", [widget.id]); + } + } + + // Look through previously saved state to see if we're restoring a widget. + let seenAreas = new Set(); + for (let [area, placements] of gPlacements) { + seenAreas.add(area); + let index = gPlacements.get(area).indexOf(widget.id); + if (index != -1) { + widget.currentArea = area; + widget.currentPosition = index; + break; + } + } + + // Also look at saved state data directly in areas that haven't yet been + // restored. Can't rely on this for restored areas, as they may have + // changed. + if (!widget.currentArea && gSavedState) { + for (let area of Object.keys(gSavedState.placements)) { + if (seenAreas.has(area)) { + continue; + } + + let index = gSavedState.placements[area].indexOf(widget.id); + if (index != -1) { + widget.currentArea = area; + widget.currentPosition = index; + break; + } + } + } + + // If we're restoring the widget to it's old placement, fire off the + // onWidgetAdded event - our own handler will take care of adding it to + // any build areas. + if (widget.currentArea) { + this.notifyListeners("onWidgetAdded", widget.id, widget.currentArea, + widget.currentPosition); + } else { + let autoAdd = true; + try { + autoAdd = Services.prefs.getBoolPref(kPrefCustomizationAutoAdd); + } catch (e) {} + + // If the widget doesn't have an existing placement, and it hasn't been + // seen before, then add it to its default area so it can be used. + if (autoAdd && !widget.currentArea && !gSeenWidgets.has(widget.id)) { + this.beginBatchUpdate(); + gSeenWidgets.add(widget.id); + + if (widget.defaultArea) { + if (this.isAreaLazy(widget.defaultArea)) { + gFuturePlacements.get(widget.defaultArea).add(widget.id); + } else { + this.addWidgetToArea(widget.id, widget.defaultArea); + } + } + + this.endBatchUpdate(true); + } + } + + return widget.id; + }, + + createBuiltinWidget: function(aData) { + // This should only ever be called on startup, before any windows are + // opened - so we know there's no build areas to handle. Also, builtin + // widgets are expected to be (mostly) static, so shouldn't affect the + // current placement settings. + let widget = this.normalizeWidget(aData, CustomizableUI.SOURCE_BUILTIN); + if (!widget) { + ERROR("Error creating builtin widget: " + aData.id); + return; + } + + LOG("Creating built-in widget with id: " + widget.id); + gPalette.set(widget.id, widget); + }, + + // Returns true if the area will eventually lazily restore (but hasn't yet). + isAreaLazy: function(aArea) { + if (gPlacements.has(aArea)) { + return false; + } + return gAreas.get(aArea).has("legacy"); + }, + + //XXXunf Log some warnings here, when the data provided isn't up to scratch. + normalizeWidget: function(aData, aSource) { + let widget = { + source: aSource || "addon", + instances: new Map(), + currentArea: null, + removable: false, + defaultArea: null, + allowedAreas: [], + shortcut: null, + tooltiptext: null, + }; + + if (typeof aData.id != "string" || !/^[a-z0-9-_]{1,}$/i.test(aData.id)) { + ERROR("Given an illegal id in normalizeWidget: " + aData.id); + return null; + } + + const kReqStringProps = ["id"]; + for (let prop of kReqStringProps) { + if (typeof aData[prop] != "string") { + ERROR("Missing required property '" + prop + "' in normalizeWidget: " + + aData.id); + return null; + } + widget[prop] = aData[prop]; + } + + const kOptStringProps = ["label", "tooltiptext", "shortcut"]; + for (let prop of kOptStringProps) { + if (typeof aData[prop] == "string") { + widget[prop] = aData[prop]; + } + } + + if ("removable" in aData && typeof aData.removable == "boolean") { + widget.removable = aData.removable; + } + + if (aData.defaultArea && gAreas.has(aData.defaultArea)) { + widget.defaultArea = aData.defaultArea; + } + + if (Array.isArray(aData.allowedAreas)) { + widget.allowedAreas = + [area for (area of aData.allowedAreas) if (gAreas.has(area))]; + } + + if ("type" in aData && gSupportedWidgetTypes.has(aData.type)) { + widget.type = aData.type; + } else { + widget.type = "button"; + } + + widget.disabled = aData.disabled === true; + + widget.onClick = typeof aData.onClick == "function" ? aData.onClick : null; + + widget.onCreated = typeof aData.onCreated == "function" ? aData.onCreated : null; + + if (widget.type == "button") { + widget.onCommand = typeof aData.onCommand == "function" ? + aData.onCommand : + null; + } else if (widget.type == "view") { + if (typeof aData.viewId != "string") { + ERROR("Expected a string for widget " + widget.id + " viewId, but got " + + aData.viewId); + return null; + } + widget.viewId = aData.viewId; + + widget.onViewShowing = typeof aData.onViewShowing == "function" ? + aData.onViewShowing : + null; + widget.onViewHiding = typeof aData.onViewHiding == "function" ? + aData.onViewHiding : + null; + } else if (widget.type == "custom") { + widget.onBuild = typeof aData.onBuild == "function" ? + aData.onBuild : + null; + } + + if (gPalette.has(widget.id)) { + return null; + } + + return widget; + }, + + destroyWidget: function(aWidgetId) { + let widget = gPalette.get(aWidgetId); + if (!widget) { + return; + } + + // This will not remove the widget from gPlacements - we want to keep the + // setting so the widget gets put back in it's old position if/when it + // returns. + + let area = widget.currentArea; + if (area) { + let buildArea = gBuildAreas.get(area); + for (let buildNode of buildArea) { + let widgetNode = buildNode.ownerDocument.getElementById(aWidgetId); + if (widgetNode) { + widgetNode.parentNode.removeChild(widgetNode); + } + for (let eventName of kSubviewEvents) { + let handler = "on" + eventName; + if (typeof widget[handler] == "function") { + viewNode.removeEventListener(eventName, widget[handler], false); + } + } + } + } + + gPalette.delete(aWidgetId); + + this.notifyListeners("onWidgetDestroyed", aWidgetId); + }, + + registerManifest: function(aBaseLocation, aData) { + let tokens = aData.split(/\s+/); + let directive = tokens.shift(); + if (directive != "widget") { + return; + } + + for (let [id, widget] of gPalette) { + if (widget.source == aBaseLocation.spec) { + return; // Already registered. + } + } + + let uri = NetUtil.newURI(tokens.shift(), null, aBaseLocation); + + dump("\tNew widget! " + uri.spec + "\n"); + + let data = ""; + try { + if (uri.schemeIs("jar")) { + data = this.readManifestFromJar(uri); + } else { + data = this.readManifestFromFile(uri); + } + } + catch (e) { + ERROR(e); + return; + } + data = JSON.parse(data); + data.source = aBaseLocation.spec; + + this.createWidget(data); + }, + + // readManifestFromJar and readManifestFromFile from ChromeManifestParser.jsm. + readManifestFromJar: function(aURI) { + let data = ""; + let entries = []; + let readers = []; + + try { + // Deconstrict URI, which can be nested jar: URIs. + let uri = aURI.clone(); + while (uri instanceof Ci.nsIJARURI) { + entries.push(uri.JAREntry); + uri = uri.JARFile; + } + + // Open the base jar. + let reader = Cc["@mozilla.org/libjar/zip-reader;1"] + .createInstance(Ci.nsIZipReader); + reader.open(uri.QueryInterface(Ci.nsIFileURL).file); + readers.push(reader); + + // Open the nested jars. + for (let i = entries.length - 1; i > 0; i--) { + let innerReader = Cc["@mozilla.org/libjar/zip-reader;1"]. + createInstance(Ci.nsIZipReader); + innerReader.openInner(reader, entries[i]); + readers.push(innerReader); + reader = innerReader; + } + + // First entry is the actual file we want to read. + let zis = reader.getInputStream(entries[0]); + data = NetUtil.readInputStreamToString(zis, zis.available()); + } + finally { + // Close readers in reverse order. + for (let i = readers.length - 1; i >= 0; i--) { + readers[i].close(); + //XXXunf Don't think this is needed, but need to double check. + //flushJarCache(readers[i].file); + } + } + + return data; + }, + + readManifestFromFile: function(aURI) { + let file = aURI.QueryInterface(Ci.nsIFileURL).file; + if (!file.exists() || !file.isFile()) { + return ""; + } + + let data = ""; + let fis = Cc["@mozilla.org/network/file-input-stream;1"] + .createInstance(Ci.nsIFileInputStream); + try { + fis.init(file, -1, -1, false); + data = NetUtil.readInputStreamToString(fis, fis.available()); + } finally { + fis.close(); + } + return data; + }, + + getCustomizeTargetForArea: function(aArea, aWindow) { + let buildAreaNodes = gBuildAreas.get(aArea); + if (!buildAreaNodes) { + throw new Error("No build area nodes registered for " + aArea); + } + + for (let node of buildAreaNodes) { + if (node.ownerDocument.defaultView === aWindow) { + return node.customizationTarget ? node.customizationTarget : node; + } + } + + throw new Error("Could not find any window nodes for area " + aArea); + }, + + reset: function() { + gResetting = true; + Services.prefs.clearUserPref(kPrefCustomizationState); + LOG("State reset"); + + // Reset placements to make restoring default placements possible. + gPlacements = new Map(); + // Clear the saved state to ensure that defaults will be used. + gSavedState = null; + // Restore the state for each area to its defaults + for (let [areaId,] of gAreas) { + this.restoreStateForArea(areaId); + } + + // Rebuild each registered area (across windows) to reflect the state that + // was reset above. + for (let [areaId, areaNodes] of gBuildAreas) { + let placements = gPlacements.get(areaId); + for (let areaNode of areaNodes) { + this.buildArea(areaId, placements, areaNode); + } + } + gResetting = false; + }, + + _addParentFlex: function(aElement) { + // If necessary, add flex to accomodate new child. + if (aElement.hasAttribute("flex")) { + let parent = aElement.parentNode; + let parentFlex = parent.hasAttribute("flex") ? parseInt(parent.getAttribute("flex"), 10) : 0; + let elementFlex = parseInt(aElement.getAttribute("flex"), 10); + parent.setAttribute("flex", parentFlex + elementFlex); + } + }, + + _removeParentFlex: function(aElement) { + if (aElement.parentNode.hasAttribute("flex") && aElement.hasAttribute("flex")) { + let parent = aElement.parentNode; + let parentFlex = parseInt(parent.getAttribute("flex"), 10); + let elementFlex = parseInt(aElement.getAttribute("flex"), 10); + parent.setAttribute("flex", Math.max(0, parentFlex - elementFlex)); + } + }, + + isWidgetRemovable: function(aWidgetId) { + let provider = this.getWidgetProvider(aWidgetId); + + if (provider == CustomizableUI.PROVIDER_API) { + return gPalette.get(aWidgetId).removable; + } + + if (provider == CustomizableUI.PROVIDER_XUL) { + if (gBuildWindows.size == 0) { + // We don't have any build windows to look at, so just assume for now + // that its removable. + return true; + } + + // Pick any of the build windows to look at. + let [window,] = [...gBuildWindows][0]; + let [, node] = this.getWidgetNode(aWidgetId, window); + return node.getAttribute("removable") == "true"; + } + + // Otherwise this is a special widget, which are always removable. + return true; + }, + + canWidgetMoveToArea: function(aWidgetId, aArea) { + let placement = this.getPlacementOfWidget(aWidgetId); + if (placement && placement.area != aArea && + !this.isWidgetRemovable(aWidgetId)) { + return false; + } + return true; + }, + + get inDefaultState() { + for (let [areaId, props] of gAreas) { + let defaultPlacements = props.get("defaultPlacements"); + // Areas without default placements (like legacy ones?) get skipped + if (!defaultPlacements) { + continue; + } + + let currentPlacements = gPlacements.get(areaId); + // We're excluding all of the placement IDs for items that do not exist, + // because we don't want to consider them when determining if we're + // in the default state. This way, if an add-on introduces a widget + // and is then uninstalled, the leftover placement doesn't cause us to + // automatically assume that the buttons are not in the default state. + let buildAreaNodes = gBuildAreas.get(areaId); + if (buildAreaNodes && buildAreaNodes.size) { + let container = [...buildAreaNodes][0]; + // Clone the array so we don't modify the actual placements... + currentPlacements = [...currentPlacements]; + // Loop backwards through the placements so we can easily remove items: + let itemIndex = currentPlacements.length; + while (itemIndex--) { + if (!container.querySelector(idToSelector(currentPlacements[itemIndex]))) { + currentPlacements.splice(itemIndex, 1); + } + } + } + LOG("Checking default state for " + areaId + ":\n" + currentPlacements.join("\n") + + " vs. " + defaultPlacements.join("\n")); + + if (currentPlacements.length != defaultPlacements.length) { + return false; + } + + for (let i = 0; i < currentPlacements.length; ++i) { + if (currentPlacements[i] != defaultPlacements[i]) { + LOG("Found " + currentPlacements[i] + " in " + areaId + " where " + + defaultPlacements[i] + " was expected!"); + return false; + } + } + } + + return true; + } +}; +Object.freeze(CustomizableUIInternal); + +this.CustomizableUI = { + get AREA_PANEL() "PanelUI-contents", + get AREA_NAVBAR() "nav-bar", + get AREA_MENUBAR() "toolbar-menubar", + get AREA_TABSTRIP() "TabsToolbar", + get AREA_BOOKMARKS() "PersonalToolbar", + get AREA_ADDONBAR() "addon-bar", + + get PROVIDER_XUL() "xul", + get PROVIDER_API() "api", + get PROVIDER_SPECIAL() "special", + + get SOURCE_BUILTIN() "builtin", + get SOURCE_EXTERNAL() "external", + + get TYPE_BUTTON() "button", + get TYPE_MENU_PANEL() "menu-panel", + get TYPE_TOOLBAR() "toolbar", + + addListener: function(aListener) { + CustomizableUIInternal.addListener(aListener); + }, + removeListener: function(aListener) { + CustomizableUIInternal.removeListener(aListener); + }, + registerArea: function(aName, aProperties) { + CustomizableUIInternal.registerArea(aName, aProperties); + }, + //XXXunf registerToolbarNode / registerToolbarInstance ? + registerToolbar: function(aToolbar) { + CustomizableUIInternal.registerToolbar(aToolbar); + }, + registerMenuPanel: function(aPanel) { + CustomizableUIInternal.registerMenuPanel(aPanel); + }, + unregisterArea: function(aName) { + CustomizableUIInternal.unregisterArea(aName); + }, + addWidgetToArea: function(aWidgetId, aArea, aPosition) { + CustomizableUIInternal.addWidgetToArea(aWidgetId, aArea, aPosition); + }, + removeWidgetFromArea: function(aWidgetId) { + CustomizableUIInternal.removeWidgetFromArea(aWidgetId); + }, + moveWidgetWithinArea: function(aWidgetId, aPosition) { + CustomizableUIInternal.moveWidgetWithinArea(aWidgetId, aPosition); + }, + beginBatchUpdate: function() { + CustomizableUIInternal.beginBatchUpdate(); + }, + endBatchUpdate: function(aForceSave) { + CustomizableUIInternal.endBatchUpdate(aForceSave); + }, + createWidget: function(aProperties) { + return CustomizableUIInternal.wrapWidget( + CustomizableUIInternal.createWidget(aProperties) + ); + }, + destroyWidget: function(aWidgetId) { + CustomizableUIInternal.destroyWidget(aWidgetId); + }, + getWidget: function(aWidgetId) { + return CustomizableUIInternal.wrapWidget(aWidgetId); + }, + getUnusedWidgets: function(aWindowPalette) { + return CustomizableUIInternal.getUnusedWidgets(aWindowPalette).map( + CustomizableUIInternal.wrapWidget, + CustomizableUIInternal + ); + }, + getWidgetIdsInArea: function(aArea) { + if (!gAreas.has(aArea)) { + throw new Error("Unknown customization area: " + aArea); + } + if (!gPlacements.has(aArea)) { + throw new Error("Area not yet restored"); + } + + return gPlacements.get(aArea); + }, + getWidgetsInArea: function(aArea) { + return this.getWidgetIdsInArea(aArea).map( + CustomizableUIInternal.wrapWidget, + CustomizableUIInternal + ); + }, + get areas() { + return [area for ([area, props] of gAreas)]; + }, + getCustomizeTargetForArea: function(aArea, aWindow) { + return CustomizableUIInternal.getCustomizeTargetForArea(aArea, aWindow); + }, + reset: function() { + CustomizableUIInternal.reset(); + }, + getPlacementOfWidget: function(aWidgetId) { + return CustomizableUIInternal.getPlacementOfWidget(aWidgetId); + }, + isWidgetRemovable: function(aWidgetId) { + return CustomizableUIInternal.isWidgetRemovable(aWidgetId); + }, + canWidgetMoveToArea: function(aWidgetId, aArea) { + return CustomizableUIInternal.canWidgetMoveToArea(aWidgetId, aArea); + }, + get inDefaultState() { + return CustomizableUIInternal.inDefaultState; + }, + getLocalizedProperty: function(aWidget, aProp, aFormatArgs, aDef) { + return CustomizableUIInternal.getLocalizedProperty(aWidget, aProp, + aFormatArgs, aDef); + }, + hidePanelForNode: function(aNode) { + CustomizableUIInternal.hidePanelForNode(aNode); + }, + isSpecialWidget: function(aWidgetId) { + return CustomizableUIInternal.isSpecialWidget(aWidgetId); + } +}; +Object.freeze(this.CustomizableUI); + + +/** + * All external consumers of widgets are really interacting with these wrappers + * which provide a common interface. + */ + +/** + * WidgetGroupWrapper is the common interface for interacting with an entire + * widget group - AKA, all instances of a widget across a series of windows. + * This particular wrapper is only used for widgets created via the provider + * API. + */ +function WidgetGroupWrapper(aWidget) { + this.isGroup = true; + + const kBareProps = ["id", "source", "type", "disabled", "label", "tooltiptext"]; + for (let prop of kBareProps) { + let propertyName = prop; + this.__defineGetter__(propertyName, function() aWidget[propertyName]); + } + + this.__defineGetter__("provider", function() CustomizableUI.PROVIDER_API); + + this.__defineSetter__("disabled", function(aValue) { + aValue = !!aValue; + aWidget.disabled = aValue; + for (let [,instance] of aWidget.instances) { + instance.disabled = aValue; + } + }); + + this.forWindow = function WidgetGroupWrapper_forWindow(aWindow) { + let instance = aWidget.instances.get(aWindow.document); + if (!instance) { + instance = CustomizableUIInternal.buildWidget(aWindow.document, + aWidget); + } + + let wrapper = gWrapperCache.get(instance); + if (!wrapper) { + wrapper = new WidgetSingleWrapper(aWidget, instance); + gWrapperCache.set(instance, wrapper); + } + return wrapper; + }; + + Object.freeze(this); +} + +/** + * A WidgetSingleWrapper is a wrapper around a single instance of a widget in + * a particular window. + */ +function WidgetSingleWrapper(aWidget, aNode) { + this.isGroup = false; + + this.node = aNode; + this.provider = CustomizableUI.PROVIDER_API; + + const kGlobalProps = ["id", "type"]; + for (let prop of kGlobalProps) { + this[prop] = aWidget[prop]; + } + + const nodeProps = ["label", "tooltiptext"]; + for (let prop of nodeProps) { + let propertyName = prop; + // Look at the node for these, instead of the widget data, to ensure the + // wrapper always reflects this live instance. + this.__defineGetter__(propertyName, + function() aNode.getAttribute(propertyName)); + } + + this.__defineGetter__("disabled", function() aNode.disabled); + this.__defineSetter__("disabled", function(aValue) { + aNode.disabled = !!aValue; + }); + + this.__defineGetter__("anchor", function() { + let anchorId = aNode.getAttribute("customizableui-anchorid"); + return anchorId ? aNode.ownerDocument.getElementById(anchorId) + : aNode; + }); + + this.__defineGetter__("areaType", function() { + return aNode.getAttribute("customizableui-areatype") || ""; + }); + + + Object.freeze(this); +} + +/** + * XULWidgetGroupWrapper is the common interface for interacting with an entire + * widget group - AKA, all instances of a widget across a series of windows. + * This particular wrapper is only used for widgets created via the old-school + * XUL method (overlays, or programmatically injecting toolbaritems, or other + * such things). + */ +//XXXunf Going to need to hook this up to some events to keep it all live. +function XULWidgetGroupWrapper(aWidgetId) { + this.isGroup = true; + + let nodes = []; + + let placement = CustomizableUIInternal.getPlacementOfWidget(aWidgetId); + if (placement) { + let buildAreas = gBuildAreas.get(placement.area) || []; + for (let areaNode of buildAreas) + nodes.push(areaNode.ownerDocument.getElementById(aWidgetId)); + } + + this.id = aWidgetId; + this.type = "custom"; + this.provider = CustomizableUI.PROVIDER_XUL; + + this.forWindow = function XULWidgetGroupWrapper_forWindow(aWindow) { + let instance = aWindow.document.getElementById(aWidgetId); + if (!instance) { + // Toolbar palettes aren't part of the document, so elements in there + // won't be found via document.getElementById(). + instance = aWindow.gNavToolbox.palette.querySelector(idToSelector(aWidgetId)); + } + + let wrapper = gWrapperCache.get(instance); + if (!wrapper) { + wrapper = new XULWidgetSingleWrapper(aWidgetId, instance); + gWrapperCache.set(instance, wrapper); + } + return wrapper; + }; + + Object.freeze(this); +} + +/** + * A XULWidgetSingleWrapper is a wrapper around a single instance of a XUL + * widget in a particular window. + */ +function XULWidgetSingleWrapper(aWidgetId, aNode) { + this.isGroup = false; + + this.id = aWidgetId; + this.type = "custom"; + this.provider = CustomizableUI.PROVIDER_XUL; + + this.node = aNode; + + this.__defineGetter__("anchor", function() { + let anchorId = aNode.getAttribute("customizableui-anchorid"); + return anchorId ? aNode.ownerDocument.getElementById(anchorId) + : aNode; + }); + + this.__defineGetter__("areaType", function() { + return aNode.getAttribute("customizableui-areatype") || ""; + }); + + Object.freeze(this); +} + +const LAZY_RESIZE_INTERVAL_MS = 200; + +function OverflowableToolbar(aToolbarNode) { + this._toolbar = aToolbarNode; + this._collapsed = []; + this._enabled = true; + + this._toolbar.setAttribute("overflowable", "true"); + Services.obs.addObserver(this, "browser-delayed-startup-finished", false); +} + +OverflowableToolbar.prototype = { + observe: function(aSubject, aTopic, aData) { + if (aTopic == "browser-delayed-startup-finished" && + aSubject == this._toolbar.ownerDocument.defaultView) { + Services.obs.removeObserver(this, "browser-delayed-startup-finished"); + this.init(); + } + }, + + init: function() { + this._target = this._toolbar.customizationTarget; + let doc = this._toolbar.ownerDocument; + this._list = doc.getElementById("widget-overflow-list"); + this._toolbar.customizationTarget.addEventListener("overflow", this); + + let window = doc.defaultView; + window.addEventListener("resize", this); + window.gNavToolbox.addEventListener("customizationstarting", this); + window.gNavToolbox.addEventListener("aftercustomization", this); + + let chevronId = this._toolbar.getAttribute("overflowbutton"); + this._chevron = doc.getElementById(chevronId); + this._chevron.addEventListener("command", this); + + this._panel = doc.getElementById("widget-overflow"); + this._panel.addEventListener("popuphiding", this); + CustomizableUIInternal.ensureButtonsClosePanel(this._panel); + + this.initialized = true; + + // The toolbar could initialize in an overflowed state, in which case + // the 'overflow' event may have been fired before the handler was registered. + this._onOverflow(); + }, + + uninit: function() { + if (!this.initialized) { + return; + } + this._disable(); + + this._toolbar.removeAttribute("overflowable"); + this._toolbar.customizationTarget.removeEventListener("overflow", this); + let window = this._toolbar.ownerDocument.defaultView; + window.removeEventListener("resize", this); + window.gNavToolbox.removeEventListener("customizationstarting", this); + window.gNavToolbox.removeEventListener("aftercustomization", this); + this._chevron.removeEventListener("command", this); + this._panel.removeEventListener("popuphiding", this); + CustomizableUIInternal.removePanelCloseListeners(this._panel); + }, + + handleEvent: function(aEvent) { + switch(aEvent.type) { + case "overflow": + this._onOverflow(); + break; + case "resize": + this._onResize(aEvent); + break; + case "command": + this._onClickChevron(aEvent); + break; + case "popuphiding": + this._onPanelHiding(aEvent); + break; + case "customizationstarting": + this._disable(); + break; + case "aftercustomization": + this._enable(); + break; + } + }, + + _onClickChevron: function(aEvent) { + if (this._chevron.open) + this._panel.hidePopup(); + else { + let doc = aEvent.target.ownerDocument; + this._panel.hidden = false; + let anchor = doc.getAnonymousElementByAttribute(this._chevron, "class", "toolbarbutton-icon"); + this._panel.openPopup(anchor || this._chevron, "bottomcenter topright"); + } + this._chevron.open = !this._chevron.open; + }, + + _onPanelHiding: function(aEvent) { + this._chevron.open = false; + }, + + _onOverflow: function() { + if (!this._enabled) + return; + + let child = this._target.lastChild; + + while(child && this._target.clientWidth < this._target.scrollWidth) { + let prevChild = child.previousSibling; + + if (!child.hasAttribute("nooverflow")) { + this._collapsed.push({child: child, minSize: this._target.clientWidth}); + child.classList.add("overflowedItem"); + + this._list.insertBefore(child, this._list.firstChild); + this._toolbar.setAttribute("overflowing", "true"); + } + child = prevChild; + }; + }, + + _onResize: function(aEvent) { + if (!this._lazyResizeHandler) { + this._lazyResizeHandler = new DeferredTask(this._onLazyResize.bind(this), + LAZY_RESIZE_INTERVAL_MS); + } + this._lazyResizeHandler.start(); + }, + + _moveItemsBackToTheirOrigin: function(shouldMoveAllItems) { + for (let i = this._collapsed.length - 1; i >= 0; i--) { + let {child, minSize} = this._collapsed[i]; + + if (!shouldMoveAllItems && + this._target.clientWidth <= minSize) { + return; + } + + this._collapsed.pop(); + this._target.appendChild(child); + child.classList.remove("overflowedItem"); + } + + if (!this._collapsed.length) { + this._toolbar.removeAttribute("overflowing"); + } + }, + + _onLazyResize: function() { + if (!this._enabled) + return; + + this._moveItemsBackToTheirOrigin(); + }, + + _disable: function() { + this._enabled = false; + this._moveItemsBackToTheirOrigin(true); + if (this._lazyResizeHandler) { + this._lazyResizeHandler.cancel(); + } + }, + + _enable: function() { + this._enabled = true; + this._onOverflow(); + } +}; + +// When IDs contain special characters, we need to escape them for use with querySelector: +function idToSelector(aId) { + return "#" + aId.replace(/[ !"'#$%&\(\)*+\-,.\/:;<=>?@\[\\\]^`{|}~]/g, '\\$&'); +} + +CustomizableUIInternal.initialize();
new file mode 100644 --- /dev/null +++ b/browser/components/customizableui/src/CustomizableWidgets.jsm @@ -0,0 +1,520 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +this.EXPORTED_SYMBOLS = ["CustomizableWidgets"]; + +Cu.import("resource:///modules/CustomizableUI.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", + "resource://gre/modules/PlacesUtils.jsm"); + +const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; +const kPrefCustomizationDebug = "browser.uiCustomization.debug"; +const kWidePanelItemClass = "panel-combined-item"; + +let gModuleName = "[CustomizableWidgets]"; +#include logging.js + +function setAttributes(aNode, aAttrs) { + for (let [name, value] of Iterator(aAttrs)) { + if (!value) { + if (aNode.hasAttribute(name)) + aNode.removeAttribute(name); + } else { + if (name == "label" || name == "tooltiptext") + value = CustomizableUI.getLocalizedProperty(aAttrs, name); + aNode.setAttribute(name, value); + } + } +} + +// This function is called whenever an item gets moved in the menu panel. It +// adjusts the position of widgets within the panel to reduce single-column +// buttons from being placed in a row by themselves. +function adjustPosition(aNode) { + // TODO(bug 885574): Merge this constant with the one in CustomizeMode.jsm, + // maybe just use a pref for this. + const kColumnsInMenuPanel = 3; + + // Make sure that there are n % columns = 0 narrow buttons before the widget. + let prevSibling = aNode.previousElementSibling; + let previousSiblingCount = 0; + while (prevSibling) { + if (!prevSibling.classList.contains(kWidePanelItemClass)) { + previousSiblingCount++; + } + prevSibling = prevSibling.previousElementSibling; + } + if (previousSiblingCount % kColumnsInMenuPanel) { + let previousElement = aNode.previousElementSibling; + if (!previousElement || + previousElement.classList.contains(kWidePanelItemClass)) { + return; + } + + let position = Array.prototype.indexOf.call(aNode.parentNode.children, aNode); + // We don't need to move all of the items in this pass, because + // this move will trigger adjustPosition to get called again. The + // function will stop recursing when it finds that there is no + // more work that is needed. + CustomizableUI.moveWidgetWithinArea(aNode.id, position - 1); + } +} + +const CustomizableWidgets = [{ + id: "history-panelmenu", + type: "view", + viewId: "PanelUI-history", + removable: true, + defaultArea: CustomizableUI.AREA_PANEL, + allowedAreas: [CustomizableUI.AREA_PANEL, CustomizableUI.AREA_NAVBAR], + onViewShowing: function(aEvent) { + // Populate our list of history + const kMaxResults = 15; + let doc = aEvent.detail.ownerDocument; + + let options = PlacesUtils.history.getNewQueryOptions(); + options.excludeQueries = true; + options.includeHidden = false; + options.resultType = options.RESULTS_AS_URI; + options.queryType = options.QUERY_TYPE_HISTORY; + options.sortingMode = options.SORT_BY_DATE_DESCENDING; + options.maxResults = kMaxResults; + let query = PlacesUtils.history.getNewQuery(); + + let items = doc.getElementById("PanelUI-historyItems"); + // Clear previous history items. + while (items.firstChild) { + items.removeChild(items.firstChild); + } + + PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase) + .asyncExecuteLegacyQueries([query], 1, options, { + handleResult: function (aResultSet) { + let fragment = doc.createDocumentFragment(); + for (let row, i = 0; (row = aResultSet.getNextRow()); i++) { + try { + let uri = row.getResultByIndex(1); + let title = row.getResultByIndex(2); + let icon = row.getResultByIndex(6); + + let item = doc.createElementNS(kNSXUL, "toolbarbutton"); + item.setAttribute("label", title || uri); + item.addEventListener("click", function(aEvent) { + if (aEvent.button == 0) { + doc.defaultView.openUILink(uri, aEvent); + CustomizableUI.hidePanelForNode(item); + } + }); + if (icon) + item.setAttribute("image", "moz-anno:favicon:" + icon); + fragment.appendChild(item); + } catch (e) { + ERROR("Error while showing history subview: " + e); + } + } + items.appendChild(fragment); + }, + handleError: function (aError) { + LOG("History view tried to show but had an error: " + aError); + }, + handleCompletion: function (aReason) { + LOG("History view is being shown!"); + }, + }); + }, + onViewHiding: function(aEvent) { + LOG("History view is being hidden!"); + } + }, { + id: "privatebrowsing-button", + removable: true, + defaultArea: CustomizableUI.AREA_PANEL, + allowedAreas: [CustomizableUI.AREA_PANEL], + onCommand: function(e) { + if (e.target && e.target.ownerDocument && e.target.ownerDocument.defaultView) { + let win = e.target.ownerDocument.defaultView; + if (typeof win.OpenBrowserWindow == "function") { + win.OpenBrowserWindow({private: true}); + } + } + } + }, { + id: "save-page-button", + removable: true, + defaultArea: CustomizableUI.AREA_PANEL, + allowedAreas: [CustomizableUI.AREA_PANEL], + onCommand: function(aEvent) { + let win = aEvent.target && + aEvent.target.ownerDocument && + aEvent.target.ownerDocument.defaultView; + if (win && typeof win.saveDocument == "function") { + win.saveDocument(win.content.document); + } + } + }, { + id: "find-button", + removable: true, + defaultArea: CustomizableUI.AREA_PANEL, + allowedAreas: [CustomizableUI.AREA_PANEL], + onCommand: function(aEvent) { + let win = aEvent.target && + aEvent.target.ownerDocument && + aEvent.target.ownerDocument.defaultView; + if (win && win.gFindBar) { + win.gFindBar.onFindCommand(); + } + } + }, { + id: "open-file-button", + removable: true, + defaultArea: CustomizableUI.AREA_PANEL, + allowedAreas: [CustomizableUI.AREA_PANEL], + onCommand: function(aEvent) { + let win = aEvent.target + && aEvent.target.ownerDocument + && aEvent.target.ownerDocument.defaultView; + if (win && typeof win.BrowserOpenFileWindow == "function") { + win.BrowserOpenFileWindow(); + } + } + }, { + id: "developer-button", + removable: true, + defaultArea: CustomizableUI.AREA_PANEL, + allowedAreas: [CustomizableUI.AREA_PANEL], + onCommand: function(aEvent) { + let win = aEvent.target && + aEvent.target.ownerDocument && + aEvent.target.ownerDocument.defaultView; + if (win && win.gDevToolsBrowser) { + win.gDevToolsBrowser.toggleToolboxCommand(win.gBrowser); + } + } + }, { + id: "add-ons-button", + removable: true, + defaultArea: CustomizableUI.AREA_PANEL, + allowedAreas: [CustomizableUI.AREA_PANEL], + onCommand: function(aEvent) { + let win = aEvent.target && + aEvent.target.ownerDocument && + aEvent.target.ownerDocument.defaultView; + if (win && typeof win.BrowserOpenAddonsMgr == "function") { + win.BrowserOpenAddonsMgr(); + } + } + }, { + id: "preferences-button", + removable: true, + defaultArea: CustomizableUI.AREA_PANEL, + allowedAreas: [CustomizableUI.AREA_PANEL], + onCommand: function(aEvent) { + let win = aEvent.target && + aEvent.target.ownerDocument && + aEvent.target.ownerDocument.defaultView; + if (win && typeof win.openPreferences == "function") { + win.openPreferences(); + } + } + }, { + id: "zoom-controls", + type: "custom", + removable: true, + defaultArea: CustomizableUI.AREA_PANEL, + allowedAreas: [CustomizableUI.AREA_PANEL, CustomizableUI.AREA_NAVBAR], + onBuild: function(aDocument) { + let inPanel = (this.currentArea == CustomizableUI.AREA_PANEL); + let noautoclose = inPanel ? "true" : null; + let flex = inPanel ? "1" : null; + let cls = inPanel ? "panel-combined-button" : "toolbarbutton-1"; + let buttons = [{ + id: "zoom-out-button", + noautoclose: noautoclose, + command: "cmd_fullZoomReduce", + flex: flex, + class: cls, + label: true, + tooltiptext: true + }, { + id: "zoom-reset-button", + noautoclose: noautoclose, + command: "cmd_fullZoomReset", + flex: flex, + class: cls, + tooltiptext: true + }, { + id: "zoom-in-button", + noautoclose: noautoclose, + command: "cmd_fullZoomEnlarge", + flex: flex, + class: cls, + label: true, + tooltiptext: true + }]; + + let node = aDocument.createElementNS(kNSXUL, "toolbaritem"); + node.setAttribute("id", "zoom-controls"); + node.setAttribute("title", CustomizableUI.getLocalizedProperty(this, "tooltiptext")); + // Set this as an attribute in addition to the property to make sure we can style correctly. + node.setAttribute("removable", "true"); + if (inPanel) + node.setAttribute("flex", "1"); + node.classList.add("chromeclass-toolbar-additional"); + node.classList.add(kWidePanelItemClass); + + buttons.forEach(function(aButton) { + let btnNode = aDocument.createElementNS(kNSXUL, "toolbarbutton"); + setAttributes(btnNode, aButton); + node.appendChild(btnNode); + }); + + // The middle node is the 'Reset Zoom' button. + let zoomResetButton = node.childNodes[1]; + let window = aDocument.defaultView; + function updateZoomResetButton() { + zoomResetButton.setAttribute("label", CustomizableUI.getLocalizedProperty( + buttons[1], "label", [Math.floor(window.ZoomManager.zoom * 100)] + )); + }; + + // Register ourselves with the service so we know when the zoom prefs change. + Services.obs.addObserver(updateZoomResetButton, "browser-fullZoom:zoomChange", false); + Services.obs.addObserver(updateZoomResetButton, "browser-fullZoom:zoomReset", false); + Services.obs.addObserver(updateZoomResetButton, "browser-fullZoom:locationChange", false); + + updateZoomResetButton(); + if (!inPanel) + zoomResetButton.setAttribute("hidden", "true"); + + function updateWidgetStyle(aInPanel) { + let attrs = { + noautoclose: aInPanel ? "true" : null, + flex: aInPanel ? "1" : null, + class: aInPanel ? "panel-combined-button" : "toolbarbutton-1" + }; + for (let i = 0, l = node.childNodes.length; i < l; ++i) { + setAttributes(node.childNodes[i], attrs); + } + zoomResetButton.setAttribute("hidden", aInPanel ? "false" : "true"); + if (aInPanel) + node.setAttribute("flex", "1"); + else if (node.hasAttribute("flex")) + node.removeAttribute("flex"); + } + + let listener = { + onWidgetAdded: function(aWidgetId, aArea, aPosition) { + if (this.currentArea == CustomizableUI.AREA_PANEL) { + adjustPosition(node); + } + + if (aWidgetId != this.id) + return; + + updateWidgetStyle(aArea == CustomizableUI.AREA_PANEL); + }.bind(this), + + onWidgetRemoved: function(aWidgetId, aPrevArea) { + if (this.currentArea == CustomizableUI.AREA_PANEL) { + adjustPosition(node); + } + + if (aWidgetId != this.id) + return; + + // When a widget is demoted to the palette ('removed'), it's visual + // style should change. + updateWidgetStyle(false); + zoomResetButton.setAttribute("hidden", "true"); + }.bind(this), + + onWidgetReset: function(aWidgetId) { + if (aWidgetId != this.id) + return; + updateWidgetStyle(this.currentArea == CustomizableUI.AREA_PANEL); + }.bind(this), + + onWidgetMoved: function(aWidgetId, aArea) { + if (this.currentArea == CustomizableUI.AREA_PANEL) { + adjustPosition(node); + } + + if (aWidgetId != this.id) + return; + updateWidgetStyle(aArea == CustomizableUI.AREA_PANEL); + }.bind(this), + + onWidgetInstanceRemoved: function(aWidgetId, aDoc) { + if (aWidgetId != this.id || aDoc != aDocument) + return; + + CustomizableUI.removeListener(listener); + Services.obs.removeObserver(updateZoomResetButton, "browser-fullZoom:zoomChange"); + Services.obs.removeObserver(updateZoomResetButton, "browser-fullZoom:zoomReset"); + }.bind(this) + }; + CustomizableUI.addListener(listener); + + return node; + } + }, { + id: "edit-controls", + type: "custom", + removable: true, + defaultArea: CustomizableUI.AREA_PANEL, + allowedAreas: [CustomizableUI.AREA_PANEL, CustomizableUI.AREA_NAVBAR], + onBuild: function(aDocument) { + let inPanel = (this.currentArea == CustomizableUI.AREA_PANEL); + let flex = inPanel ? "1" : null; + let cls = inPanel ? "panel-combined-button" : "toolbarbutton-1"; + let buttons = [{ + id: "cut-button", + command: "cmd_cut", + flex: flex, + class: cls, + label: true, + tooltiptext: true + }, { + id: "copy-button", + command: "cmd_copy", + flex: flex, + class: cls, + label: true, + tooltiptext: true + }, { + id: "paste-button", + command: "cmd_paste", + flex: flex, + class: cls, + label: true, + tooltiptext: true + }]; + + let node = aDocument.createElementNS(kNSXUL, "toolbaritem"); + node.setAttribute("id", "edit-controls"); + node.setAttribute("title", CustomizableUI.getLocalizedProperty(this, "tooltiptext")); + // Set this as an attribute in addition to the property to make sure we can style correctly. + node.setAttribute("removable", "true"); + if (inPanel) + node.setAttribute("flex", "1"); + node.classList.add("chromeclass-toolbar-additional"); + node.classList.add(kWidePanelItemClass); + + buttons.forEach(function(aButton) { + let btnNode = aDocument.createElementNS(kNSXUL, "toolbarbutton"); + setAttributes(btnNode, aButton); + node.appendChild(btnNode); + }); + + function updateWidgetStyle(aInPanel) { + let attrs = { + flex: aInPanel ? "1" : null, + class: aInPanel ? "panel-combined-button" : "toolbarbutton-1" + }; + for (let i = 0, l = node.childNodes.length; i < l; ++i) { + setAttributes(node.childNodes[i], attrs); + } + if (aInPanel) + node.setAttribute("flex", "1"); + else if (node.hasAttribute("flex")) + node.removeAttribute("flex"); + } + + let listener = { + onWidgetAdded: function(aWidgetId, aArea, aPosition) { + if (this.currentArea == CustomizableUI.AREA_PANEL) { + adjustPosition(node); + } + + if (aWidgetId != this.id) + return; + updateWidgetStyle(aArea == CustomizableUI.AREA_PANEL); + }.bind(this), + + onWidgetRemoved: function(aWidgetId, aPrevArea) { + if (this.currentArea == CustomizableUI.AREA_PANEL) { + adjustPosition(node); + } + + if (aWidgetId != this.id) + return; + // When a widget is demoted to the palette ('removed'), it's visual + // style should change. + updateWidgetStyle(false); + }.bind(this), + + onWidgetReset: function(aWidgetId) { + if (aWidgetId != this.id) + return; + updateWidgetStyle(this.currentArea == CustomizableUI.AREA_PANEL); + }.bind(this), + + onWidgetMoved: function(aWidgetId, aArea) { + if (this.currentArea == CustomizableUI.AREA_PANEL) { + adjustPosition(node); + } + + if (aWidgetId != this.id) + return; + updateWidgetStyle(aArea == CustomizableUI.AREA_PANEL); + }.bind(this), + + onWidgetInstanceRemoved: function(aWidgetId, aDoc) { + if (aWidgetId != this.id || aDoc != aDocument) + return; + CustomizableUI.removeListener(listener); + }.bind(this) + }; + CustomizableUI.addListener(listener); + + return node; + } + }, + { + id: "feed-button", + type: "view",