Merge mozilla-central and ux

This commit is contained in:
Gijs Kruitbosch 2013-11-12 09:27:50 +01:00
commit af6f8f6fef
292 changed files with 15048 additions and 5603 deletions

View File

@ -96,7 +96,7 @@
gQueue.push(new focusElmWhileSubdocIsFocused("link"));
gQueue.push(new synthTab(editableDoc, new focusChecker(editableDoc)));
if (WIN) {
if (WIN || LINUX) {
// Alt key is used to active menubar and focus menu item on Windows,
// other platforms requires setting a ui.key.menuAccessKeyFocuses
// preference.

View File

@ -156,7 +156,7 @@
// Alt key is used to active menubar and focus menu item on Windows,
// other platforms requires setting a ui.key.menuAccessKeyFocuses
// preference.
if (WIN) {
if (WIN || LINUX) {
gQueue.push(new focusFileMenu());
gQueue.push(new focusEditMenu());
gQueue.push(new leaveMenubar());

View File

@ -11,7 +11,7 @@ module.metadata = {
const { Cu } = require('chrome');
const { Loader } = require('sdk/test/loader');
const data = require('./fixtures');
const { data } = require('sdk/self');
const { open, focus, close } = require('sdk/window/helpers');
const { setTimeout } = require('sdk/timers');
const { getMostRecentBrowserWindow } = require('sdk/window/utils');

View File

@ -444,11 +444,10 @@ 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
pref("browser.tabs.drawInTitlebar", true);
#else
#ifdef UNIX_BUT_NOT_MAC
pref("browser.tabs.drawInTitlebar", false);
#else
pref("browser.tabs.drawInTitlebar", true);
#endif
// Where to show tab close buttons:
@ -1327,3 +1326,6 @@ pref("geo.wifi.uri", "https://www.googleapis.com/geolocation/v1/geolocate?key=%G
// Necko IPC security checks only needed for app isolation for cookies/cache/etc:
// currently irrelevant for desktop e10s
pref("network.disable.ipc.security", true);
// CustomizableUI debug logging.
pref("browser.uiCustomization.debug", false);

View File

@ -178,50 +178,6 @@ const gXPInstallObserver = {
}
};
/*
* 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) {
@ -415,3 +371,60 @@ var LightWeightThemeWebInstaller = {
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);
if (!themeData)
return;
this.updateStyleSheet("url(" + themeData.headerURL + ")");
},
};

View File

@ -1,406 +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="&copyButton.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="&copyCmd.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_devAppMgr"
observes="devtoolsMenuBroadcaster_DevAppMgr"/>
<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);"/>
<menuitem id="appmenu_keyboardShortcuts"
label="&helpKeyboardShortcuts.label;"
oncommand="openHelpLink('keyboard-shortcuts')"
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>

View File

@ -0,0 +1,97 @@
# -*- 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();
// Update the urlbar
if (gURLBar) {
URLBarSetURI();
XULBrowserWindow.asyncUpdateUI();
BookmarkingUI.updateStarState();
}
// 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");
gBrowser.selectedBrowser.focus();
}
}

View File

@ -8,66 +8,53 @@
* 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");
if (isSubview) {
item.setAttribute("tabindex", "0");
}
container.appendChild(item);
}
return true;
},
@ -76,7 +63,7 @@ var FeedHandler = {
* 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

View File

@ -17,7 +17,7 @@ var 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);
@ -517,15 +517,6 @@ var FullScreen = {
// 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"));
@ -539,18 +530,10 @@ var FullScreen = {
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);
}
if (el.hasAttribute("saved-context")) {
el.setAttribute("context", el.getAttribute("saved-context"));
el.removeAttribute("saved-context");
}
restoreAttr("mode");
restoreAttr("iconsize");
restoreAttr("context");
el.removeAttribute("inFullscreen");
}
} else {
@ -571,11 +554,9 @@ var FullScreen = {
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);

View File

@ -397,6 +397,7 @@ var FullZoom = {
* @param browser The zoom of this browser will be saved. Required.
*/
_applyZoomToPref: function FullZoom__applyZoomToPref(browser) {
Services.obs.notifyObservers(null, "browser-fullZoom:zoomChange", "");
if (!this.siteSpecific ||
gInPrintPreviewMode ||
browser.contentDocument.mozSyntheticDocument)
@ -417,6 +418,7 @@ var FullZoom = {
* @param browser The zoom of this browser will be removed. Required.
*/
_removePref: function FullZoom__removePref(browser) {
Services.obs.notifyObservers(null, "browser-fullZoom:zoomReset", "");
if (browser.contentDocument.mozSyntheticDocument)
return;
let ctxt = this._loadContextFromWindow(browser.contentWindow);

View File

@ -185,11 +185,6 @@
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;"

View File

@ -134,9 +134,6 @@ var StarUI = {
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");
@ -745,9 +742,10 @@ var BookmarksEventHandler = {
// Handles special drag and drop functionality for Places menus that are not
// part of a Places view (e.g. the bookmarks menu in the menubar).
var PlacesMenuDNDHandler = {
_springLoadDelay: 350, // milliseconds
_springLoadDelayMs: 350,
_closeDelayMs: 500,
_loadTimer: null,
_closerTimer: null,
_closeTimer: null,
/**
* Called when the user enters the <menu> element during a drag.
@ -768,7 +766,7 @@ var PlacesMenuDNDHandler = {
this._loadTimer = null;
popup.setAttribute("autoopened", "true");
popup.showPopup(popup);
}, this._springLoadDelay, Ci.nsITimer.TYPE_ONE_SHOT);
}, this._springLoadDelayMs, Ci.nsITimer.TYPE_ONE_SHOT);
event.preventDefault();
event.stopPropagation();
},
@ -781,7 +779,8 @@ var PlacesMenuDNDHandler = {
onDragLeave: function PMDH_onDragLeave(event) {
// Handle menu-button separate targets.
if (event.relatedTarget === event.currentTarget ||
event.relatedTarget.parentNode === event.currentTarget)
(event.relatedTarget &&
event.relatedTarget.parentNode === event.currentTarget))
return;
// Closing menus in a Places popup is handled by the view itself.
@ -807,7 +806,7 @@ var PlacesMenuDNDHandler = {
popup.removeAttribute("autoopened");
popup.hidePopup();
}
}, this._springLoadDelay, Ci.nsITimer.TYPE_ONE_SHOT);
}, this._closeDelayMs, Ci.nsITimer.TYPE_ONE_SHOT);
},
/**
@ -875,30 +874,52 @@ let PlacesToolbarHelper = {
if (!viewElt || viewElt._placesView)
return;
// If the bookmarks toolbar item is hidden because the parent toolbar is
// collapsed or hidden (i.e. in a popup), spare the initialization. Also,
// there is no need to initialize the toolbar if customizing because
// init() will be called when the customization is done.
let toolbar = viewElt.parentNode.parentNode;
if (toolbar.collapsed ||
getComputedStyle(toolbar, "").display == "none" ||
this._isCustomizing)
// If the bookmarks toolbar item is:
// - not in a toolbar, or;
// - the toolbar is collapsed, or;
// - the toolbar is hidden some other way:
// don't initialize. Also, there is no need to initialize the toolbar if
// customizing, because that will happen when the customization is done.
let toolbar = this._getParentToolbar(viewElt);
if (!toolbar || toolbar.collapsed || this._isCustomizing ||
getComputedStyle(toolbar, "").display == "none")
return;
new PlacesToolbar(this._place);
},
customizeStart: function PTH_customizeStart() {
let viewElt = this._viewElt;
if (viewElt && viewElt._placesView)
viewElt._placesView.uninit();
this._isCustomizing = true;
try {
let viewElt = this._viewElt;
if (viewElt && viewElt._placesView)
viewElt._placesView.uninit();
} finally {
this._isCustomizing = true;
}
},
customizeDone: function PTH_customizeDone() {
this._isCustomizing = false;
this.init();
},
onPlaceholderCommand: function () {
let widgetGroup = CustomizableUI.getWidget("personal-bookmarks");
let widget = widgetGroup.forWindow(window);
if (widget.overflowed ||
widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
PlacesCommandHook.showPlacesOrganizer("BookmarksToolbar");
}
},
_getParentToolbar: function(element) {
while (element) {
if (element.localName == "toolbar") {
return element;
}
element = element.parentNode;
}
return null;
}
};
@ -906,31 +927,33 @@ let PlacesToolbarHelper = {
//// 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");
let widgetGroup = CustomizableUI.getWidget("bookmarks-menu-button");
if (widgetGroup.areaType == CustomizableUI.TYPE_TOOLBAR) {
return widgetGroup.forWindow(window).node;
}
return this._button;
return null;
},
get star() {
if (!this._star) {
this._star = document.getElementById("star-button");
}
return this._star;
let button = this.button;
return button && document.getAnonymousElementByAttribute(button, "anonid",
"button");
},
get anchor() {
if (this.star && isElementVisible(this.star)) {
// Anchor to the icon, so the panel looks more natural.
return this.star;
}
return null;
let widget = CustomizableUI.getWidget("bookmarks-menu-button")
.forWindow(window);
if (widget.overflowed)
return widget.anchor;
let star = this.star;
return star && document.getAnonymousElementByAttribute(star, "class",
"toolbarbutton-icon");
},
STATUS_UPDATING: -1,
@ -939,9 +962,9 @@ let BookmarkingUI = {
get status() {
if (this._pendingStmt)
return this.STATUS_UPDATING;
return this.star &&
this.star.hasAttribute("starred") ? this.STATUS_STARRED
: this.STATUS_UNSTARRED;
let button = this.button;
return button && button.hasAttribute("starred") ? this.STATUS_STARRED
: this.STATUS_UNSTARRED;
},
get _starredTooltip()
@ -974,6 +997,16 @@ let BookmarkingUI = {
if (event.target != event.currentTarget)
return;
let widget = CustomizableUI.getWidget("bookmarks-menu-button")
.forWindow(window);
if (widget.overflowed) {
// Don't open a popup in the overflow popup, rather just open the Library.
event.preventDefault();
widget.node.removeAttribute("noautoclose");
PlacesCommandHook.showPlacesOrganizer("BookmarksMenu");
return;
}
if (!this._popupNeedsUpdate)
return;
this._popupNeedsUpdate = false;
@ -990,14 +1023,6 @@ let BookmarkingUI = {
let personalToolbar = document.getElementById("PersonalToolbar");
viewToolbarMenuitem.setAttribute("checked", !personalToolbar.collapsed);
}
let toolbarMenuitem = getPlacesAnonymousElement("toolbar-autohide");
if (toolbarMenuitem) {
// If bookmarks items are visible, hide Bookmarks Toolbar menu and the
// separator after it.
toolbarMenuitem.collapsed = toolbarMenuitem.nextSibling.collapsed =
isElementVisible(document.getElementById("personal-bookmarks"));
}
},
/**
@ -1010,29 +1035,31 @@ let BookmarkingUI = {
if (aState == "invalid") {
this.star.setAttribute("disabled", "true");
this.star.removeAttribute("starred");
this.button.removeAttribute("starred");
this.button.setAttribute("buttontooltiptext", "");
}
else {
this.star.removeAttribute("disabled");
}
this._updateToolbarStyle();
},
_updateToolbarStyle: function BUI__updateToolbarStyle() {
if (!this.button) {
let button = this.button;
if (!button)
return;
}
let personalToolbar = document.getElementById("PersonalToolbar");
let onPersonalToolbar = this.button.parentNode == personalToolbar ||
this.button.parentNode.parentNode == personalToolbar;
let onPersonalToolbar = button.parentNode == personalToolbar ||
button.parentNode.parentNode == personalToolbar;
if (onPersonalToolbar) {
this.button.classList.add("bookmark-item");
this.button.classList.remove("toolbarbutton-1");
button.classList.add("bookmark-item");
button.classList.remove("toolbarbutton-1");
}
else {
this.button.classList.remove("bookmark-item");
this.button.classList.add("toolbarbutton-1");
button.classList.remove("bookmark-item");
button.classList.add("toolbarbutton-1");
}
},
@ -1040,9 +1067,9 @@ let BookmarkingUI = {
// When an element with a placesView attached is removed and re-inserted,
// XBL reapplies the binding causing any kind of issues and possible leaks,
// so kill current view and let popupshowing generate a new one.
if (this.button && this.button._placesView) {
this.button._placesView.uninit();
}
let button = this.button;
if (button && button._placesView)
button._placesView.uninit();
},
customizeStart: function BUI_customizeStart() {
@ -1054,7 +1081,6 @@ let BookmarkingUI = {
},
customizeDone: function BUI_customizeDone() {
delete this._button;
this.onToolbarVisibilityChange();
this._updateToolbarStyle();
},
@ -1074,7 +1100,7 @@ let BookmarkingUI = {
},
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;
}
@ -1123,17 +1149,17 @@ let BookmarkingUI = {
},
_updateStar: function BUI__updateStar() {
if (!this.star) {
let button = this.button;
if (!button)
return;
}
if (this._itemIds.length > 0) {
this.star.setAttribute("starred", "true");
this.star.setAttribute("tooltiptext", this._starredTooltip);
button.setAttribute("starred", "true");
button.setAttribute("buttontooltiptext", this._starredTooltip);
}
else {
this.star.removeAttribute("starred");
this.star.setAttribute("tooltiptext", this._unstarredTooltip);
button.removeAttribute("starred");
button.setAttribute("buttontooltiptext", this._unstarredTooltip);
}
},
@ -1141,15 +1167,71 @@ let BookmarkingUI = {
if (aEvent.target != aEvent.currentTarget) {
return;
}
// Handle special case when the button is in the panel.
let widgetGroup = CustomizableUI.getWidget("bookmarks-menu-button");
let widget = widgetGroup.forWindow(window);
if (widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
let view = document.getElementById("PanelUI-bookmarks");
view.addEventListener("ViewShowing", this.onPanelMenuViewShowing);
view.addEventListener("ViewHiding", this.onPanelMenuViewHiding);
widget.node.setAttribute("noautoclose", "true");
PanelUI.showSubView("PanelUI-bookmarks", widget.node,
CustomizableUI.AREA_PANEL);
return;
}
else if (widget.overflowed) {
// Allow to close the panel if the page is already bookmarked, cause
// we are going to open the edit bookmark panel.
if (this._itemIds.length > 0)
widget.node.removeAttribute("noautoclose");
else
widget.node.setAttribute("noautoclose", "true");
}
// Ignore clicks on the star if we are updating its state.
if (!this._pendingStmt) {
PlacesCommandHook.bookmarkCurrentPage(this._itemIds.length > 0);
}
},
onPanelMenuViewShowing: function BUI_onViewShowing(aEvent) {
// Update checked status of the toolbar toggle.
let viewToolbar = document.getElementById("panelMenu_viewBookmarksToolbar");
let personalToolbar = document.getElementById("PersonalToolbar");
if (personalToolbar.collapsed)
viewToolbar.removeAttribute("checked");
else
viewToolbar.setAttribute("checked", "true");
// Setup the Places view.
this._panelMenuView = new PlacesPanelMenuView("place:folder=BOOKMARKS_MENU",
"panelMenu_bookmarksMenu",
"panelMenu_bookmarksMenu");
},
onPanelMenuViewHiding: function BUI_onViewHiding(aEvent) {
this._panelMenuView.uninit();
delete this._panelMenuView;
},
onPanelMenuViewCommand: function BUI_onPanelMenuViewCommand(aEvent, aView) {
let target = aEvent.originalTarget;
if (!target._placesNode)
return;
if (PlacesUtils.nodeIsContainer(target._placesNode))
PlacesCommandHook.showPlacesOrganizer([ "BookmarksMenu", target._placesNode.itemId ]);
else
PlacesUIUtils.openNodeWithEvent(target._placesNode, aEvent, aView);
PanelUI.hide();
},
// 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) {
@ -1160,6 +1242,10 @@ let BookmarkingUI = {
},
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) {
@ -1170,6 +1256,10 @@ let BookmarkingUI = {
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

View File

@ -32,7 +32,6 @@
<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()"/>
@ -109,13 +108,13 @@
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:SharePage" oncommand="SocialShare.sharePage();" disabled="true"/>
<command id="Social:ToggleSidebar" oncommand="Social.toggleSidebar();" hidden="true"/>
<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');"/>
<command id="MenuPanel:Toggle" oncommand="PanelUI.toggle(event);"/>
</commandset>
<commandset id="placesCommands">
@ -412,6 +411,13 @@
#endif
<key id="key_undoCloseWindow" command="History:UndoCloseWindow" key="&newNavigatorCmd.key;" modifiers="accel,shift"/>
<key id="key_menuButton" command="MenuPanel:Toggle"
#ifdef XP_MACOSX
key="&toggleMenuPanelMac.key;" modifiers="accel,shift"/>
#else
key="&toggleMenuPanel.key;" modifiers="accel"/>
#endif
#ifdef XP_GNOME
#define NUM_SELECT_TAB_MODIFIER alt
#else
@ -428,8 +434,6 @@
#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

View File

@ -795,9 +795,7 @@ SocialShare = {
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));
}
@ -1130,9 +1128,7 @@ SocialToolbar = {
});
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() {

View File

@ -426,16 +426,13 @@ let TabView = {
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);
},
// ----------

View File

@ -21,6 +21,63 @@ searchbar {
-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-menubar {
-moz-binding: url("chrome://browser/content/customizableui/toolbar.xml#toolbar-menubar-stub");
}
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");
}
@ -47,21 +104,19 @@ tabbrowser {
.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,
opacity 50ms ease-out 20ms /* hide the tab for the first 20ms of the max-width transition */;
max-width 230ms ease-out;
}
.tabbrowser-tab:not([pinned]):not([fadein]) {
max-width: 0.1px;
min-width: 0.1px;
opacity: 0 !important;
visibility: hidden;
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;
}
.tab-throbber:not([fadein]):not([pinned]),
@ -94,20 +149,13 @@ 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:not([overflowing]) > .overflow-button,
toolbar[customizing] > .overflow-button {
display: none;
}
%ifdef CAN_DRAW_IN_TITLEBAR
@ -125,45 +173,71 @@ toolbar[printpreview="true"] {
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
.bookmarks-toolbar-customize,
#wrapper-personal-bookmarks > #personal-bookmarks > #PlacesToolbar > hbox > #PlacesToolbarItems {
%ifdef XP_WIN
#main-window[sizemode="maximized"] #titlebar-buttonbox {
-moz-appearance: -moz-window-button-box-maximized;
}
%endif
%endif
#bookmarks-toolbar-placeholder,
toolbarpaletteitem > #personal-bookmarks > #PlacesToolbar,
#personal-bookmarks[cui-areatype="menu-panel"] > #PlacesToolbar,
#personal-bookmarks[cui-areatype="toolbar"].overflowedItem > #PlacesToolbar {
display: none;
}
#wrapper-personal-bookmarks[place="toolbar"] > #personal-bookmarks > #PlacesToolbar > .bookmarks-toolbar-customize {
toolbarpaletteitem > #personal-bookmarks > #bookmarks-toolbar-placeholder,
#personal-bookmarks[cui-areatype="menu-panel"] > #bookmarks-toolbar-placeholder,
#personal-bookmarks[cui-areatype="toolbar"].overflowedItem > #bookmarks-toolbar-placeholder {
display: -moz-box;
}
#main-window[disablechrome] #navigator-toolbox[tabsontop="true"] > toolbar:not(#toolbar-menubar):not(#TabsToolbar) {
#zoom-controls[cui-areatype="toolbar"]:not(.overflowedItem) > #zoom-reset-button > .toolbarbutton-text {
display: -moz-box;
}
#wrapper-urlbar-container > #urlbar-container > #urlbar-wrapper > #urlbar > toolbarbutton,
#urlbar-reload-button:not([displaystop]) + #urlbar-stop-button,
#urlbar-reload-button[displaystop] {
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] {
visibility: collapse;
}
#feed-button > .toolbarbutton-menu-dropmarker {
display: none;
}
#feed-menu > .feed-menuitem:-moz-locale-dir(rtl) {
#PanelUI-feeds > .feed-toolbarbutton:-moz-locale-dir(rtl) {
direction: rtl;
}
#panelMenu_bookmarksMenu {
overflow-x: hidden;
overflow-y: auto;
}
#panelMenu_bookmarksMenu > .bookmark-item {
max-width: none;
}
#urlbar-container {
min-width: 50ch;
}
#search-container {
min-width: 25ch;
}
#main-window:-moz-lwtheme {
background-repeat: no-repeat;
background-position: top right;
@ -180,27 +254,6 @@ toolbar[mode="icons"] > #reload-button[displaystop] {
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");
}
@ -211,18 +264,6 @@ splitmenu {
-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;
@ -257,7 +298,7 @@ panel[noactions] > richlistbox > richlistitem[type~="action"] > .ac-url-box > .a
display: none;
}
#wrapper-urlbar-container > #urlbar-container > #urlbar {
#wrapper-urlbar-container > #urlbar-container > #urlbar-wrapper > #urlbar {
-moz-user-input: disabled;
cursor: grab;
}
@ -270,9 +311,7 @@ panel[noactions] > richlistbox > richlistitem[type~="action"] > .ac-url-box > .a
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#urlbar-rich-result-popup");
}
#urlbar-container[combined] > #urlbar > #urlbar-icons > #go-button,
#urlbar[pageproxystate="invalid"] > #urlbar-icons > .urlbar-icon:not(#go-button),
#urlbar[pageproxystate="valid"] > #urlbar-icons > #go-button,
#urlbar[pageproxystate="invalid"] > #urlbar-icons > .urlbar-icon,
#urlbar[pageproxystate="invalid"][focused="true"] > #urlbar-go-button ~ toolbarbutton,
#urlbar[pageproxystate="valid"] > #urlbar-go-button,
#urlbar:not([focused="true"]) > #urlbar-go-button {
@ -316,18 +355,20 @@ 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;
}
@ -352,7 +393,6 @@ window[chromehidden~="toolbar"] toolbar:not(.toolbar-primary):not(.chromeclass-m
}
#navigator-toolbox ,
#status-bar ,
#mainPopupSet {
min-width: 1px;
}
@ -451,14 +491,6 @@ window[chromehidden~="toolbar"] toolbar:not(.toolbar-primary):not(.chromeclass-m
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,
@ -523,17 +555,6 @@ window[chromehidden~="toolbar"] toolbar:not(.toolbar-primary):not(.chromeclass-m
-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;
}
@ -775,3 +796,14 @@ chatbox:-moz-full-screen-ancestor > .chat-titlebar {
.contentSelectDropdown-ingroup {
-moz-margin-start: 2em;
}
/* Give this menupopup an arrow panel styling */
#BMB_bookmarksPopup {
-moz-appearance: none;
-moz-binding: url("chrome://browser/content/places/menu.xml#places-popup-arrow");
background: transparent;
border: none;
transition: opacity 300ms;
/* The popup inherits -moz-image-region from the button, must reset it */
-moz-image-region: auto;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -9,5 +9,6 @@
<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"/>

View File

@ -31,6 +31,7 @@
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>

View File

@ -83,7 +83,7 @@
<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()"/>

View File

@ -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/. -->
<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>

After

Width:  |  Height:  |  Size: 731 B

View File

@ -41,6 +41,7 @@ tabpanels {
z-index: 2;
}
.tab-icon-image:not([src]):not([pinned]),
.tab-throbber:not([busy]),
.tab-throbber[busy] + .tab-icon-image {
display: none;

View File

@ -207,8 +207,7 @@
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;
}
@ -999,7 +998,7 @@
this.mCurrentTab = this.tabContainer.selectedItem;
this.showTab(this.mCurrentTab);
var backForwardContainer = document.getElementById("unified-back-forward-button");
var backForwardContainer = document.getElementById("urlbar-container");
if (backForwardContainer) {
backForwardContainer.setAttribute("switchingtabs", "true");
window.addEventListener("MozAfterPaint", function removeSwitchingtabsAttr() {
@ -1495,8 +1494,7 @@
if (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");
}
@ -1920,6 +1918,11 @@
aTab.closing = true;
this._removingTabs.push(aTab);
this._visibleTabs = null; // invalidate cache
// Invalidate hovered tab state tracking for this closing tab.
if (this.tabContainer._hoveredTab == aTab)
aTab._mouseleave();
if (newTab)
this.addTab(BROWSER_NEW_TAB_URL, {skipAnimation: true});
else
@ -3238,6 +3241,28 @@
return !tab.pinned && !tab.hidden;
]]></body>
</method>
<field name="_tabMarginLeft">null</field>
<field name="_tabMarginRight">null</field>
<method name="_adjustElementStartAndEnd">
<parameter name="aTab"/>
<parameter name="tabStart"/>
<parameter name="tabEnd"/>
<body><![CDATA[
if (this._tabMarginLeft === null || this._tabMarginRight === null) {
let tabMiddle = document.getAnonymousElementByAttribute(aTab, "class", "tab-background-middle");
let tabMiddleStyle = window.getComputedStyle(tabMiddle, null);
this._tabMarginLeft = parseFloat(tabMiddleStyle.marginLeft);
this._tabMarginRight = parseFloat(tabMiddleStyle.marginRight);
}
if (this._tabMarginLeft < 0) {
tabStart = tabStart + this._tabMarginLeft;
}
if (this._tabMarginRight < 0) {
tabEnd = tabEnd - this._tabMarginRight;
}
return [tabStart, tabEnd];
]]></body>
</method>
</implementation>
<handlers>
@ -3259,8 +3284,11 @@
<handler event="overflow"><![CDATA[
if (event.detail == 0)
return; // Ignore vertical events
var tabs = document.getBindingParent(this);
var numberOfTabs = tabs.tabbrowser.visibleTabs.length;
if (numberOfTabs == 1)
return;
tabs.setAttribute("overflow", "true");
tabs._positionPinnedTabs();
tabs._handleTabSelect(false);
@ -3357,6 +3385,15 @@
<field name="_afterSelectedTab">null</field>
<field name="_beforeHoveredTab">null</field>
<field name="_afterHoveredTab">null</field>
<field name="_hoveredTab">null</field>
<property name="_isCustomizing" readonly="true">
<getter>
let root = document.documentElement;
return root.getAttribute("customizing") == "true" ||
root.getAttribute("customize-exiting") == "true";
</getter>
</property>
<method name="_setPositionalAttributes">
<body><![CDATA[
@ -3387,6 +3424,12 @@
this._lastTab.removeAttribute("last-visible-tab");
this._lastTab = visibleTabs[lastVisible];
this._lastTab.setAttribute("last-visible-tab", "true");
let hoveredTab = this._hoveredTab;
if (hoveredTab) {
hoveredTab._mouseleave();
hoveredTab._mouseenter();
}
]]></body>
</method>
@ -3465,10 +3508,6 @@
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>
@ -3820,16 +3859,16 @@
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;
if (this.tabbrowser.visibleTabs.length > 1) {
var width = this.mTabstrip.boxObject.width;
if (width != this.mTabstripWidth) {
this.adjustTabstrip();
this._fillTrailingGap();
this._handleTabSelect();
this.mTabstripWidth = width;
}
}
this.tabbrowser.updateWindowResizers();
@ -4123,8 +4162,7 @@
// 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
@ -4247,7 +4285,7 @@
<handler event="dragstart"><![CDATA[
var tab = this._getDragTargetTab(event);
if (!tab)
if (!tab || this._isCustomizing)
return;
let dt = event.dataTransfer;
@ -4491,7 +4529,7 @@
var dt = event.dataTransfer;
var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
if (dt.mozUserCancelled || dt.dropEffect != "none") {
if (dt.mozUserCancelled || dt.dropEffect != "none" || this._isCustomizing) {
delete draggedTab._dragData;
return;
}
@ -4623,7 +4661,7 @@
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>
@ -4644,6 +4682,54 @@
<field name="mCorrespondingMenuitem">null</field>
<field name="closing">false</field>
<field name="lastAccessed">0</field>
<method name="_mouseenter">
<body><![CDATA[
if (this.closing)
return;
let tabContainer = this.parentNode;
let visibleTabs = tabContainer.tabbrowser.visibleTabs;
let tabIndex = visibleTabs.indexOf(this);
if (tabIndex == 0) {
tabContainer._beforeHoveredTab = null;
} else {
let candidate = visibleTabs[tabIndex - 1];
if (!candidate.selected) {
tabContainer._beforeHoveredTab = candidate;
candidate.setAttribute("beforehovered", "true");
}
}
if (tabIndex == visibleTabs.length - 1) {
tabContainer._afterHoveredTab = null;
} else {
let candidate = visibleTabs[tabIndex + 1];
if (!candidate.selected) {
tabContainer._afterHoveredTab = candidate;
candidate.setAttribute("afterhovered", "true");
}
}
tabContainer._hoveredTab = this;
]]></body>
</method>
<method name="_mouseleave">
<body><![CDATA[
let tabContainer = this.parentNode;
if (tabContainer._beforeHoveredTab) {
tabContainer._beforeHoveredTab.removeAttribute("beforehovered");
tabContainer._beforeHoveredTab = null;
}
if (tabContainer._afterHoveredTab) {
tabContainer._afterHoveredTab.removeAttribute("afterhovered");
tabContainer._afterHoveredTab = null;
}
tabContainer._hoveredTab = null;
]]></body>
</method>
</implementation>
<handlers>
@ -4652,47 +4738,14 @@
if (anonid == "close-button")
this.mOverCloseButton = true;
let tab = event.target;
if (tab.closing)
return;
let tabContainer = this.parentNode;
let visibleTabs = tabContainer.tabbrowser.visibleTabs;
let tabIndex = visibleTabs.indexOf(tab);
if (tabIndex == 0) {
tabContainer._beforeHoveredTab = null;
} else {
let candidate = visibleTabs[tabIndex - 1];
if (!candidate.selected) {
tabContainer._beforeHoveredTab = candidate;
candidate.setAttribute("beforehovered", "true");
}
}
if (tabIndex == visibleTabs.length - 1) {
tabContainer._afterHoveredTab = null;
} else {
let candidate = visibleTabs[tabIndex + 1];
if (!candidate.selected) {
tabContainer._afterHoveredTab = candidate;
candidate.setAttribute("afterhovered", "true");
}
}
this._mouseenter();
]]></handler>
<handler event="mouseout"><![CDATA[
let anonid = event.originalTarget.getAttribute("anonid");
if (anonid == "close-button")
this.mOverCloseButton = false;
let tabContainer = this.parentNode;
if (tabContainer._beforeHoveredTab) {
tabContainer._beforeHoveredTab.removeAttribute("beforehovered");
tabContainer._beforeHoveredTab = null;
}
if (tabContainer._afterHoveredTab) {
tabContainer._afterHoveredTab.removeAttribute("afterhovered");
tabContainer._afterHoveredTab = null;
}
this._mouseleave();
]]></handler>
<handler event="dragstart" phase="capturing">
this.style.MozUserFocus = '';

View File

@ -17,7 +17,6 @@ support-files =
bug792517.html
bug792517.sjs
bug839103.css
disablechrome.html
discovery.html
domplate_test.js
download_page.html
@ -99,9 +98,6 @@ run-if = crashreporter
[browser_aboutHome.js]
[browser_aboutSyncProgress.js]
[browser_addKeywordSearch.js]
[browser_addon_bar_aomlistener.js]
[browser_addon_bar_close_button.js]
[browser_addon_bar_shortcut.js]
[browser_alltabslistener.js]
[browser_blob-channelname.js]
[browser_bookmark_titles.js]
@ -182,10 +178,7 @@ run-if = toolkit == "cocoa"
[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]
@ -231,11 +224,9 @@ run-if = toolkit == "cocoa"
[browser_contentAreaClick.js]
[browser_contextSearchTabPosition.js]
[browser_ctrlTab.js]
[browser_customize.js]
[browser_customize_popupNotification.js]
[browser_datareporting_notification.js]
run-if = datareporting
[browser_disablechrome.js]
[browser_discovery.js]
[browser_duplicateIDs.js]
[browser_drag.js]
@ -318,6 +309,7 @@ skip-if = true # disabled until the tree view is added
[browser_visibleTabs_bookmarkAllTabs.js]
[browser_visibleTabs_contextMenu.js]
[browser_visibleTabs_tabPreview.js]
[browser_windowopen_reflows.js]
[browser_wyciwyg_urlbarCopying.js]
[browser_zbug569342.js]
[browser_registerProtocolHandler_notification.js]

View File

@ -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();
}

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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");
}

View File

@ -34,16 +34,9 @@ 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");

View File

@ -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");
}

View File

@ -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();
}
}

View File

@ -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");
}

View File

@ -4,6 +4,15 @@
// Bug 624734 - Star UI has no tooltip until bookmarked page is visited
function finishTest() {
is(BookmarkingUI.button.getAttribute("buttontooltiptext"),
BookmarkingUI._unstarredTooltip,
"Star icon should have the unstarred tooltip text");
gBrowser.removeCurrentTab();
finish();
}
function test() {
waitForExplicitFinish();
@ -11,12 +20,11 @@ function test() {
tab.linkedBrowser.addEventListener("load", (function(event) {
tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
is(BookmarkingUI.star.getAttribute("tooltiptext"),
BookmarkingUI._unstarredTooltip,
"Star icon should have the unstarred tooltip text");
gBrowser.removeCurrentTab();
finish();
if (BookmarkingUI.status == BookmarkingUI.STATUS_UPDATING) {
waitForCondition(function() BookmarkingUI.status != BookmarkingUI.STATUS_UPDATING, finishTest, "BookmarkingUI was updating for too long");
} else {
finishTest();
}
}), true);
tab.linkedBrowser.loadURI("http://example.com/browser/browser/base/content/test/general/dummy_page.html");

View File

@ -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;
}

View File

@ -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/general/";
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);
}

View File

@ -7,8 +7,8 @@ function rect(ele) ele.getBoundingClientRect();
function width(ele) rect(ele).width;
function left(ele) rect(ele).left;
function right(ele) rect(ele).right;
function isLeft(ele, msg) is(left(ele), left(scrollbox), msg);
function isRight(ele, msg) is(right(ele), right(scrollbox), msg);
function isLeft(ele, msg) is(left(ele) + tabstrip._tabMarginLeft, left(scrollbox), msg);
function isRight(ele, msg) is(right(ele) - tabstrip._tabMarginRight, right(scrollbox), msg);
function elementFromPoint(x) tabstrip._elementFromPoint(x);
function nextLeftElement() elementFromPoint(left(scrollbox) - 1);
function nextRightElement() elementFromPoint(right(scrollbox) + 1);
@ -62,7 +62,11 @@ function runOverflowTests(aEvent) {
EventUtils.synthesizeMouse(upButton, 1, 1, {});
isLeft(element, "Scrolled one tab to the left with a single click");
element = elementFromPoint(left(scrollbox) - width(scrollbox));
let elementPoint = left(scrollbox) - width(scrollbox);
element = elementFromPoint(elementPoint);
if (elementPoint == right(element)) {
element = element.nextSibling;
}
EventUtils.synthesizeMouse(upButton, 1, 1, {clickCount: 2});
isLeft(element, "Scrolled one page of tabs with a double click");

View File

@ -0,0 +1,115 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const EXPECTED_REFLOWS = [
// handleEvent flushes layout to get the tabstrip width after a resize.
"handleEvent@chrome://browser/content/tabbrowser.xml|",
// Loading a tab causes a reflow.
"loadTabs@chrome://browser/content/tabbrowser.xml|" +
"loadOneOrMoreURIs@chrome://browser/content/browser.js|" +
"gBrowserInit._delayedStartup@chrome://browser/content/browser.js|",
// Selecting the address bar causes a reflow.
"select@chrome://global/content/bindings/textbox.xml|" +
"focusAndSelectUrlBar@chrome://browser/content/browser.js|" +
"gBrowserInit._delayedStartup@chrome://browser/content/browser.js|",
// Focusing the content area causes a reflow.
"gBrowserInit._delayedStartup@chrome://browser/content/browser.js|",
// Sometimes sessionstore collects data during this test, which causes a sync reflow
// (https://bugzilla.mozilla.org/show_bug.cgi?id=892154 will fix this)
"ssi_getWindowDimension@resource:///modules/sessionstore/SessionStore.jsm",
];
if (Services.appinfo.OS == "Darwin") {
// TabsInTitlebar._update causes a reflow on OS X trying to do calculations
// since layout info is already dirty. This doesn't seem to happen before
// MozAfterPaint on other platforms.
EXPECTED_REFLOWS.push("rect@chrome://browser/content/browser.js|" +
"TabsInTitlebar._update@chrome://browser/content/browser.js|" +
"updateAppearance@chrome://browser/content/browser.js|" +
"handleEvent@chrome://browser/content/tabbrowser.xml|");
// _onOverflow causes a reflow getting widths.
EXPECTED_REFLOWS.push("OverflowableToolbar.prototype._onOverflow@resource:///modules/CustomizableUI.jsm|" +
"OverflowableToolbar.prototype.init@resource:///modules/CustomizableUI.jsm|" +
"OverflowableToolbar.prototype.observe@resource:///modules/CustomizableUI.jsm|" +
"gBrowserInit._delayedStartup@chrome://browser/content/browser.js|");
// Same as above since in packaged builds there are no function names and the resource URI includes "app"
EXPECTED_REFLOWS.push("@resource://app/modules/CustomizableUI.jsm|" +
"@resource://app/modules/CustomizableUI.jsm|" +
"@resource://app/modules/CustomizableUI.jsm|" +
"gBrowserInit._delayedStartup@chrome://browser/content/browser.js|");
}
/*
* This test ensures that there are no unexpected
* uninterruptible reflows when opening new windows.
*/
function test() {
waitForExplicitFinish();
// Add a reflow observer and open a new window
let win = OpenBrowserWindow();
let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
docShell.addWeakReflowObserver(observer);
// Wait until the mozafterpaint event occurs.
waitForMozAfterPaint(win, function paintListener() {
// Remove reflow observer and clean up.
docShell.removeWeakReflowObserver(observer);
win.close();
finish();
});
}
let observer = {
reflow: function (start, end) {
// Gather information about the current code path.
let stack = new Error().stack;
let path = stack.split("\n").slice(1).map(line => {
return line.replace(/:\d+$/, "");
}).join("|");
let pathWithLineNumbers = (new Error().stack).split("\n").slice(1).join("|");
// Stack trace is empty. Reflow was triggered by native code.
if (path === "") {
return;
}
// Check if this is an expected reflow.
for (let expectedStack of EXPECTED_REFLOWS) {
if (path.startsWith(expectedStack) ||
// Accept an empty function name for gBrowserInit._delayedStartup or TabsInTitlebar._update to workaround bug 906578.
path.startsWith(expectedStack.replace(/(^|\|)(gBrowserInit\._delayedStartup|TabsInTitlebar\._update)@/, "$1@"))) {
ok(true, "expected uninterruptible reflow '" + expectedStack + "'");
return;
}
}
ok(false, "unexpected uninterruptible reflow '" + pathWithLineNumbers + "'");
},
reflowInterruptible: function (start, end) {
// We're not interested in interruptible reflows.
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
Ci.nsISupportsWeakReference])
};
function waitForMozAfterPaint(win, callback) {
win.addEventListener("MozAfterPaint", function onEnd(event) {
if (event.target != win)
return;
win.removeEventListener("MozAfterPaint", onEnd);
executeSoon(callback);
});
}

View File

@ -1,4 +0,0 @@
<html>
<body>
</body>
</html>

View File

@ -37,49 +37,27 @@ function updateTabContextMenu(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.gCustomizeMode.enter();
aBrowserWin.gNavToolbox.addEventListener("beforecustomization", function UI_loaded() {
aBrowserWin.gNavToolbox.removeEventListener("beforecustomization", UI_loaded);
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) {

View File

@ -937,7 +937,7 @@
<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>
@ -946,7 +946,7 @@
</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>
@ -1169,7 +1169,7 @@
<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>
@ -1178,7 +1178,7 @@
</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>
@ -1508,7 +1508,7 @@
<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>
@ -2027,7 +2027,7 @@
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>

View File

@ -13,8 +13,7 @@ browser.jar:
#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)

View File

@ -8,7 +8,7 @@
# 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"

View File

@ -2,4 +2,4 @@
# 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

View File

@ -2,7 +2,7 @@
- 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 " ">

View File

@ -2,8 +2,8 @@
# 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

View File

@ -92,6 +92,8 @@ static RedirEntry kRedirMap[] = {
#endif
{ "app-manager", "chrome://browser/content/devtools/app-manager/index.xul",
nsIAboutModule::ALLOW_SCRIPT },
{ "customizing", "chrome://browser/content/customizableui/aboutCustomizing.xhtml",
nsIAboutModule::ALLOW_SCRIPT },
};
static const int kRedirTotal = NS_ARRAY_LENGTH(kRedirMap);

View File

@ -110,6 +110,7 @@ static const mozilla::Module::ContractIDEntry kBrowserContracts[] = {
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "healthreport", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#endif
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "app-manager", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ 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)

View File

@ -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>

View File

@ -0,0 +1,28 @@
<!-- 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" side="top" flex="1">
<hbox id="customization-panelHolder"/>
<box class="panel-inner-arrowcontentfooter" hidden="true"/>
</box>
</vbox>
</vbox>
</hbox>

View File

@ -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

View File

@ -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/.

View File

@ -0,0 +1,31 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
.panel-viewstack[viewtype="main"] > .panel-clickcapturer {
pointer-events: none;
}
.panel-mainview,
.panel-viewcontainer,
.panel-viewstack {
overflow: hidden;
}
.panel-viewstack {
position: relative;
}
.panel-subviews {
-moz-stack-sizing: ignore;
transform: translateX(0);
overflow-y: auto;
}
.panel-subviews[panelopen] {
transition: transform 150ms;
}
.panel-viewcontainer[panelopen]:-moz-any(:not([viewtype="main"]),[transitioning="true"]) {
transition: height 150ms;
}

View File

@ -0,0 +1,153 @@
<!-- 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"
noautofocus="true">
<panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView">
<panelview id="PanelUI-mainView" contextmenu="customizationPanelContextMenu">
<vbox id="PanelUI-contents-scroller">
<vbox id="PanelUI-contents"/>
</vbox>
<footer id="PanelUI-footer">
<!-- 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" label="&helpMenu.label;" tabindex="0"
oncommand="PanelUI.showHelpView(this.parentNode);"/>
<toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;" tabindex="0"
oncommand="gCustomizeMode.toggle();"/>
<toolbarbutton id="PanelUI-quit" tabindex="0"
#ifdef XP_WIN
label="&quitApplicationCmdWin.label;"
#else
label="&quitApplicationCmd.label;"
#endif
command="cmd_quitApplication"/>
</footer>
</panelview>
<panelview id="PanelUI-history" flex="1">
<label value="&appMenuHistory.label;"/>
<toolbarbutton id="appMenuClearRecentHistory" tabindex="0"
label="&appMenuHistory.clearRecent.label;"
command="Tools:Sanitize"/>
<toolbarbutton id="appMenuRestoreLastSession" tabindex="0"
label="&appMenuHistory.restoreSession.label;"
command="Browser:RestoreLastSession"/>
<menuseparator id="PanelUI-recentlyClosedTabs-separator"/>
<vbox id="PanelUI-recentlyClosedTabs"/>
<menuseparator id="PanelUI-recentlyClosedWindows-separator"/>
<vbox id="PanelUI-recentlyClosedWindows"/>
<menuseparator id="PanelUI-historyItems-separator"/>
<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-bookmarks" flex="1">
<toolbarbutton id="panelMenuBookmarkThisPage"
label="&bookmarkThisPageCmd.label;"
command="Browser:AddBookmarkAs"
onclick="PanelUI.hide();"/>
<toolbarseparator/>
<toolbarbutton id="panelMenu_showAllBookmarks"
label="&showAllBookmarks2.label;"
command="Browser:ShowAllBookmarks"
onclick="PanelUI.hide();"/>
<toolbarbutton id="panelMenu_viewBookmarksSidebar"
label="&viewBookmarksSidebar2.label;"
oncommand="toggleSidebar('viewBookmarksSidebar'); PanelUI.hide();">
<observes element="viewBookmarksSidebar" attribute="checked"/>
</toolbarbutton>
<toolbarbutton id="panelMenu_viewBookmarksToolbar"
label="&viewBookmarksToolbar.label;"
type="checkbox"
toolbarId="PersonalToolbar"
oncommand="onViewToolbarCommand(event); PanelUI.hide();"/>
<toolbarseparator/>
<toolbarbutton id="panelMenu_bookmarksToolbar"
label="&personalbarCmd.label;"
oncommand="PlacesCommandHook.showPlacesOrganizer('BookmarksToolbar'); PanelUI.hide();"/>
<toolbarbutton id="panelMenu_unsortedBookmarks"
label="&unsortedBookmarksCmd.label;"
oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks'); PanelUI.hide();"/>
<toolbarseparator/>
<toolbaritem id="panelMenu_bookmarksMenu"
flex="1"
orient="vertical"
smoothscroll="false"
onclick="if (event.button == 1) BookmarkingUI.onPanelMenuViewCommand(event, this._placesView);"
oncommand="BookmarkingUI.onPanelMenuViewCommand(event, this._placesView);"
flatList="true"
tooltip="bhTooltip">
<!-- bookmarks menu items -->
</toolbaritem>
</panelview>
<panelview id="PanelUI-feeds" flex="1" oncommand="FeedHandler.subscribeToFeed(null, event);"></panelview>
<panelview id="PanelUI-helpView" flex="1">
<label value="&helpMenu.label;"/>
<vbox id="PanelUI-helpItems"/>
</panelview>
<panelview id="PanelUI-developer" flex="1">
<label value="&webDeveloperMenu.label;"/>
<vbox id="PanelUI-developerItems"/>
</panelview>
<panelview id="PanelUI-characterEncodingView" flex="1">
<label value="&charsetMenu.label;"/>
<toolbarbutton label="&charsetCustomize.label;"
oncommand="PanelUI.onCharsetCustomizeCommand();"/>
<vbox id="PanelUI-characterEncodingView-customlist"
class="PanelUI-characterEncodingView-list"/>
<vbox>
<label value="&charsetMenuAutodet.label;"/>
<vbox id="PanelUI-characterEncodingView-autodetect"
class="PanelUI-characterEncodingView-list"/>
</vbox>
</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-scroller">
<vbox id="widget-overflow-list"/>
</vbox>
</panel>

View File

@ -0,0 +1,400 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ScrollbarSampler",
"resource:///modules/ScrollbarSampler.jsm");
/**
* 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-helpView",
menuButton: "PanelUI-menu-button",
panel: "PanelUI-popup",
scroller: "PanelUI-contents-scroller"
};
},
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);
});
}
this.menuButton.addEventListener("mousedown", this);
this.menuButton.addEventListener("keypress", this);
},
_eventListenersAdded: false,
_ensureEventListenersAdded: function() {
if (this._eventListenersAdded)
return;
this._addEventListeners();
},
_addEventListeners: function() {
for (let event of this.kEvents) {
this.panel.addEventListener(event, this);
}
this.helpView.addEventListener("ViewShowing", this._onHelpViewShow, false);
this.helpView.addEventListener("ViewHiding", this._onHelpViewHide, false);
this._eventListenersAdded = true;
},
uninit: function() {
if (!this._eventListenersAdded) {
return;
}
for (let event of this.kEvents) {
this.panel.removeEventListener(event, this);
}
this.helpView.removeEventListener("ViewShowing", this._onHelpViewShow);
this.helpView.removeEventListener("ViewHiding", this._onHelpViewHide);
this.menuButton.removeEventListener("mousedown", this);
this.menuButton.removeEventListener("keypress", this);
},
/**
* Customize mode extracts the mainView and puts it somewhere else while the
* user customizes. Upon completion, this function can be called to put the
* panel back to where it belongs in normal browsing mode.
*
* @param aMainView
* The mainView node to put back into place.
*/
setMainView: function(aMainView) {
this._ensureEventListenersAdded();
this.multiView.setMainView(aMainView);
},
/**
* Opens the menu panel if it's closed, or closes it if it's
* open.
*
* @param aEvent the event that triggers the toggle.
*/
toggle: function(aEvent) {
// Don't show the panel if the window is in customization mode,
// since this button doubles as an exit path for the user in this case.
if (document.documentElement.hasAttribute("customizing")) {
return;
}
this._ensureEventListenersAdded();
if (this.panel.state == "open") {
this.hide();
} else if (this.panel.state == "closed") {
this.show(aEvent);
}
},
/**
* Opens the menu panel. If the event target has a child with the
* toolbarbutton-icon attribute, the panel will be anchored on that child.
* Otherwise, the panel is anchored on the event target itself.
*
* @param aEvent the event (if any) that triggers showing the menu.
*/
show: function(aEvent) {
if (this.panel.state == "open" || this.panel.state == "showing" ||
document.documentElement.hasAttribute("customizing")) {
return;
}
this.ensureReady().then(() => {
this.panel.hidden = false;
let editControlPlacement = CustomizableUI.getPlacementOfWidget("edit-controls");
if (editControlPlacement && editControlPlacement.area == CustomizableUI.AREA_PANEL) {
updateEditUIVisibility();
}
let anchor;
if (aEvent.type == "mousedown" ||
aEvent.type == "command") {
anchor = this.menuButton;
} else {
anchor = aEvent.target;
}
let iconAnchor =
document.getAnonymousElementByAttribute(anchor, "class",
"toolbarbutton-icon");
// Only focus the panel if it's opened using the keyboard, so that
// cut/copy/paste buttons will work for mouse users.
let keyboardOpened = aEvent.sourceEvent &&
aEvent.sourceEvent.target.localName == "key";
this.panel.setAttribute("noautofocus", !keyboardOpened);
this.panel.openPopup(iconAnchor || anchor, "bottomcenter topright");
});
},
/**
* If the menu panel is being shown, hide it.
*/
hide: function() {
if (document.documentElement.hasAttribute("customizing")) {
return;
}
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;
case "mousedown":
if (aEvent.button == 0)
this.toggle(aEvent);
break;
case "keypress":
this.toggle(aEvent);
break;
}
},
/**
* Registering the menu panel is done lazily for performance reasons. This
* method is exposed so that CustomizationMode can force panel-readyness in the
* event that customization mode is started before the panel has been opened
* by the user.
*
* @param aCustomizing (optional) set to true if this was called while entering
* customization mode. If that's the case, we trust that customization
* mode will handle calling beginBatchUpdate and endBatchUpdate.
*
* @return a Promise that resolves once the panel is ready to roll.
*/
ensureReady: function(aCustomizing=false) {
if (this._readyPromise) {
return this._readyPromise;
}
this._readyPromise = Task.spawn(function() {
if (!this._scrollWidth) {
// In order to properly center the contents of the panel, while ensuring
// that we have enough space on either side to show a scrollbar, we have to
// do a bit of hackery. In particular, we calculate a new width for the
// scroller, based on the system scrollbar width.
this._scrollWidth =
(yield ScrollbarSampler.getSystemScrollbarWidth()) + "px";
let cstyle = window.getComputedStyle(this.scroller);
let widthStr = cstyle.width;
// Get the calculated padding on the left and right sides of
// the scroller too. We'll use that in our final calculation so
// that if a scrollbar appears, we don't have the contents right
// up against the edge of the scroller.
let paddingLeft = cstyle.paddingLeft;
let paddingRight = cstyle.paddingRight;
let calcStr = [widthStr, this._scrollWidth,
paddingLeft, paddingRight].join(" + ");
this.scroller.style.width = "calc(" + calcStr + ")";
}
if (aCustomizing) {
CustomizableUI.registerMenuPanel(this.contents);
} else {
this.beginBatchUpdate();
CustomizableUI.registerMenuPanel(this.contents);
this.endBatchUpdate();
}
}.bind(this)).then(null, Cu.reportError);
return this._readyPromise;
},
/**
* Switch the panel to the main view if it's not already
* in that view.
*/
showMainView: function() {
this._ensureEventListenersAdded();
this.multiView.showMainView();
},
/**
* Switch the panel to the help view if it's not already
* in that view.
*/
showHelpView: function(aAnchor) {
this._ensureEventListenersAdded();
this.multiView.showSubView("PanelUI-helpView", aAnchor);
},
/**
* Shows a subview in the panel with a given ID.
*
* @param aViewId the ID of the subview to show.
* @param aAnchor the element that spawned the subview.
* @param aPlacementArea the CustomizableUI area that aAnchor is in.
*/
showSubView: function(aViewId, aAnchor, aPlacementArea) {
this._ensureEventListenersAdded();
let viewNode = document.getElementById(aViewId);
if (!viewNode) {
Cu.reportError("Could not show panel subview with id: " + aViewId);
return;
}
if (!aAnchor) {
Cu.reportError("Expected an anchor when opening subview with id: " + aViewId);
return;
}
if (aPlacementArea == CustomizableUI.AREA_PANEL) {
this.multiView.showSubView(aViewId, aAnchor);
} else if (!aAnchor.open) {
aAnchor.open = true;
// 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");
tempPanel.setAttribute("level", "top");
document.getElementById(CustomizableUI.AREA_NAVBAR).appendChild(tempPanel);
let multiView = document.createElement("panelmultiview");
tempPanel.appendChild(multiView);
multiView.setMainView(viewNode);
CustomizableUI.addPanelCloseListeners(tempPanel);
let panelRemover = function() {
tempPanel.removeEventListener("popuphidden", panelRemover);
CustomizableUI.removePanelCloseListeners(tempPanel);
let evt = new CustomEvent("ViewHiding", {detail: viewNode});
viewNode.dispatchEvent(evt);
aAnchor.open = false;
this.multiView.appendChild(viewNode);
tempPanel.parentElement.removeChild(tempPanel);
}.bind(this);
tempPanel.addEventListener("popuphidden", panelRemover);
let iconAnchor =
document.getAnonymousElementByAttribute(aAnchor, "class",
"toolbarbutton-icon");
tempPanel.openPopup(iconAnchor || aAnchor, "bottomcenter topright");
}
},
/**
* This function can be used as a command event listener for subviews
* so that the panel knows if and when to close itself.
*/
onCommandHandler: function(aEvent) {
if (!aEvent.originalTarget.hasAttribute("noautoclose")) {
PanelUI.hide();
}
},
/**
* Open a dialog window that allow the user to customize listed character sets.
*/
onCharsetCustomizeCommand: function() {
this.hide();
window.openDialog("chrome://global/content/customizeCharset.xul",
"PrefWindow",
"chrome,modal=yes,resizable=yes",
"browser");
},
/**
* Signal that we're about to make a lot of changes to the contents of the
* panels all at once. For performance, we ignore the mutations.
*/
beginBatchUpdate: function() {
this._ensureEventListenersAdded();
this.multiView.ignoreMutations = true;
},
/**
* Signal that we're done making bulk changes to the panel. We now pay
* attention to mutations. This automatically synchronizes the multiview
* container with whichever view is displayed if the panel is open.
*/
endBatchUpdate: function(aReason) {
this._ensureEventListenersAdded();
this.multiView.ignoreMutations = false;
},
/**
* Sets the anchor node into the open or closed state, depending
* on the state of the panel.
*/
_updatePanelButton: function() {
this.menuButton.open = this.panel.state == "open" ||
this.panel.state == "showing";
},
_onHelpViewShow: function(aEvent) {
// Call global menu setup function
buildHelpMenu();
let helpMenu = document.getElementById("menu_HelpPopup");
let items = this.getElementsByTagName("vbox")[0];
let attrs = ["oncommand", "onclick", "label", "key", "disabled"];
let NSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
// Remove all buttons from the view
while (items.firstChild) {
items.removeChild(items.firstChild);
}
// Add the current set of menuitems of the Help menu to this view
let menuItems = Array.prototype.slice.call(helpMenu.getElementsByTagName("menuitem"));
let fragment = document.createDocumentFragment();
for (let node of menuItems) {
if (node.hidden)
continue;
let button = document.createElementNS(NSXUL, "toolbarbutton");
// Copy specific attributes from a menuitem of the Help menu
for (let attrName of attrs) {
if (!node.hasAttribute(attrName))
continue;
button.setAttribute(attrName, node.getAttribute(attrName));
}
fragment.appendChild(button);
}
items.appendChild(fragment);
this.addEventListener("command", PanelUI.onCommandHandler);
},
_onHelpViewHide: function(aEvent) {
this.removeEventListener("command", PanelUI.onCommandHandler);
}
};

View File

@ -0,0 +1,342 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<bindings id="browserPanelUIBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="panelmultiview">
<resources>
<stylesheet src="chrome://browser/content/customizableui/panelUI.css"/>
</resources>
<content>
<xul:box anonid="viewContainer" class="panel-viewcontainer" xbl:inherits="panelopen,viewtype,transitioning">
<xul:stack anonid="viewStack" xbl:inherits="viewtype" 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 this._mainViewId ? document.getElementById(this._mainViewId) : null;"/>
<property name="ignoreMutations">
<getter>
return this._ignoreMutations;
</getter>
<setter><![CDATA[
this._ignoreMutations = val;
if (!val && this._panel.state == "open") {
if (this.showingSubView) {
this._syncContainerWithSubView();
} else {
this._syncContainerWithMainView();
}
}
]]></setter>
</property>
<property name="_transitioning">
<getter>
return this.__transitioning;
</getter>
<setter><![CDATA[
this.__transitioning = val;
if (val) {
this.setAttribute("transitioning", "true");
} else {
this.removeAttribute("transitioning");
}
]]></setter>
</property>
<constructor><![CDATA[
this._clickCapturer.addEventListener("click", this);
this._panel.addEventListener("popupshowing", this);
this._panel.addEventListener("popupshown", this);
this._panel.addEventListener("popuphidden", this);
this._subViews.addEventListener("overflow", this);
this._mainViewContainer.addEventListener("overflow", this);
// Get a MutationObserver ready to react to subview size changes. We
// only attach this MutationObserver when a subview is being displayed.
this._subViewObserver =
new MutationObserver(this._syncContainerWithSubView.bind(this));
this._mainViewObserver =
new MutationObserver(this._syncContainerWithMainView.bind(this));
this._mainViewContainer.setAttribute("panelid",
this._panel.id);
if (this._mainView) {
this.setMainView(this._mainView);
}
this.setAttribute("viewtype", "main");
]]></constructor>
<destructor><![CDATA[
if (this._mainView) {
this._mainView.removeAttribute("mainview");
}
this._mainViewObserver.disconnect();
this._subViewObserver.disconnect();
this._panel.removeEventListener("popupshowing", this);
this._panel.removeEventListener("popupshown", this);
this._panel.removeEventListener("popuphidden", this);
this._subViews.removeEventListener("overflow", this);
this._mainViewContainer.removeEventListener("overflow", this);
this._clickCapturer.removeEventListener("click", this);
]]></destructor>
<method name="setMainView">
<parameter name="aNewMainView"/>
<body><![CDATA[
if (this._mainView) {
this._mainViewObserver.disconnect();
this._subViews.appendChild(this._mainView);
this._mainView.removeAttribute("mainview");
}
this._mainViewId = aNewMainView.id;
aNewMainView.setAttribute("mainview", "true");
this._mainViewContainer.appendChild(aNewMainView);
]]></body>
</method>
<method name="showMainView">
<body><![CDATA[
if (this.showingSubView) {
let viewNode = this._currentSubView;
let evt = document.createEvent("CustomEvent");
evt.initCustomEvent("ViewHiding", true, true, viewNode);
viewNode.dispatchEvent(evt);
viewNode.removeAttribute("current");
this._currentSubView = null;
this._subViewObserver.disconnect();
this._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 edge of the anchor, relative to the main panel.
// Then we need to add half the width of the anchor. This is the target
// that we need to transition to.
let anchorRect = aAnchor.getBoundingClientRect();
let mainViewRect = this._mainViewContainer.getBoundingClientRect();
let center = aAnchor.clientWidth / 2;
let direction = aAnchor.ownerDocument.defaultView.getComputedStyle(aAnchor, null).direction;
let edge, target;
if (direction == "ltr") {
edge = anchorRect.left - mainViewRect.left;
target = "-" + (edge + center);
} else {
edge = mainViewRect.right - anchorRect.right;
target = edge + 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 "popupshown":
this._setMaxHeight();
break;
case "popuphidden":
this.removeAttribute("panelopen");
this._mainView.style.height = "";
this.showMainView();
break;
}
]]></body>
</method>
<method name="_setMaxHeight">
<body><![CDATA[
// Ignore the mutation that'll fire when we set the height of
// the main view.
this.ignoreMutations = true;
this._mainView.style.height =
this.getBoundingClientRect().height + "px";
this.ignoreMutations = false;
]]></body>
</method>
<method name="_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>

View File

@ -0,0 +1,556 @@
<?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">
<field name="overflowedDuringConstruction">null</field>
<property name="accessibleType" readonly="true">
<getter>
return Components.interfaces.nsIAccessibleProvider.XULToolbar;
</getter>
</property>
<constructor><![CDATA[
let scope = {};
Cu.import("resource:///modules/CustomizableUI.jsm", scope);
// Add an early overflow event listener that will mark if the
// toolbar overflowed during construction.
if (scope.CustomizableUI.isAreaOverflowable(this.id)) {
this.addEventListener("overflow", this);
this.addEventListener("underflow", this);
}
if (document.readyState == "complete") {
this._init();
} else {
// Need to wait until XUL overlays are loaded. See bug 554279.
let self = this;
document.addEventListener("readystatechange", function onReadyStateChange() {
if (document.readyState != "complete")
return;
document.removeEventListener("readystatechange", onReadyStateChange, false);
self._init();
}, false);
}
]]></constructor>
<method name="_init">
<body><![CDATA[
let scope = {};
Cu.import("resource:///modules/CustomizableUI.jsm", scope);
let CustomizableUI = scope.CustomizableUI;
// Searching for the toolbox palette in the toolbar binding because
// toolbars are constructed first.
let toolbox = this.toolbox;
if (toolbox && !toolbox.palette) {
for (let node of toolbox.children) {
if (node.localName == "toolbarpalette") {
// Hold on to the palette but remove it from the document.
toolbox.palette = node;
toolbox.removeChild(node);
break;
}
}
}
// pass the current set of children for comparison with placements:
let children = [node.id for (node of this.childNodes)
if (node.getAttribute("skipintoolbarset") != "true" && node.id)];
CustomizableUI.registerToolbarNode(this, children);
]]></body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
if (aEvent.type == "overflow" && aEvent.detail > 0) {
if (this.overflowable && this.overflowable.initialized) {
this.overflowable.onOverflow(aEvent);
} else {
this.overflowedDuringConstruction = aEvent;
}
} else if (aEvent.type == "underflow" && aEvent.detail > 0) {
this.overflowedDuringConstruction = null;
}
]]></body>
</method>
<method name="insertItem">
<parameter name="aId"/>
<parameter name="aBeforeElt"/>
<parameter name="aWrapper"/>
<body><![CDATA[
if (aWrapper) {
Cu.reportError("Can't insert " + aId + ": using insertItem " +
"no longer supports wrapper elements.");
return null;
}
// Hack, the customizable UI code makes this be the last position
let pos = null;
if (aBeforeElt) {
let beforeInfo = CustomizableUI.getPlacementOfWidget(aBeforeElt.id);
if (beforeInfo.area != this.id) {
Cu.reportError("Can't insert " + aId + " before " +
aBeforeElt.id + " which isn't in this area (" +
this.id + ").");
return null;
}
pos = beforeInfo.position;
}
CustomizableUI.addWidgetToArea(aId, this.id, pos);
return this.ownerDocument.getElementById(aId);
]]></body>
</method>
<property name="toolbarName"
onget="return this.getAttribute('toolbarname');"
onset="this.setAttribute('toolbarname', val); return val;"/>
<property name="customizationTarget" readonly="true">
<getter><![CDATA[
if (this._customizationTarget)
return this._customizationTarget;
let id = this.getAttribute("customizationtarget");
if (id)
this._customizationTarget = document.getElementById(id);
if (this._customizationTarget)
this._customizationTarget.insertItem = this.insertItem.bind(this);
else
this._customizationTarget = this;
return this._customizationTarget;
]]></getter>
</property>
<property name="toolbox" readonly="true">
<getter><![CDATA[
if (this._toolbox)
return this._toolbox;
let toolboxId = this.getAttribute("toolboxid");
if (toolboxId) {
let toolbox = document.getElementById(toolboxId);
if (toolbox) {
if (toolbox.externalToolbars.indexOf(this) == -1)
toolbox.externalToolbars.push(this);
this._toolbox = toolbox;
}
}
if (!this._toolbox && this.parentNode &&
this.parentNode.localName == "toolbox") {
this._toolbox = this.parentNode;
}
return this._toolbox;
]]></getter>
</property>
<property name="currentSet">
<getter><![CDATA[
let currentWidgets = new Set();
for (let node of this.customizationTarget.children) {
let realNode = node.localName == "toolbarpaletteitem" ? node.firstChild : node;
if (realNode.getAttribute("skipintoolbarset") != "true") {
currentWidgets.add(realNode.id);
}
}
if (this.getAttribute("overflowing") == "true") {
let overflowTarget = this.getAttribute("overflowtarget");
let overflowList = this.ownerDocument.getElementById(overflowTarget);
for (let node of overflowList.children) {
let realNode = node.localName == "toolbarpaletteitem" ? node.firstChild : node;
if (realNode.getAttribute("skipintoolbarset") != "true") {
currentWidgets.add(realNode.id);
}
}
}
let orderedPlacements = CustomizableUI.getWidgetIdsInArea(this.id);
return orderedPlacements.filter((x) => currentWidgets.has(x)).join(',');
]]></getter>
<setter><![CDATA[
// Get list of new and old ids:
let newVal = (val || '').split(',').filter(x => x);
let oldIds = CustomizableUI.getWidgetIdsInArea(this.id);
// Get a list of items only in the new list
let newIds = [id for (id of newVal) if (oldIds.indexOf(id) == -1)];
CustomizableUI.beginBatchUpdate();
for (let newId of newIds) {
oldIds = CustomizableUI.getWidgetIdsInArea(this.id);
let nextId = newId;
let pos;
do {
// Get the next item
nextId = newVal[newVal.indexOf(nextId) + 1];
// Figure out where it is in the old list
pos = oldIds.indexOf(nextId);
// If it's not in the old list, repeat:
} while (pos == -1 && nextId);
if (pos == -1) {
pos = null; // We didn't find anything, insert at the end
}
CustomizableUI.addWidgetToArea(newId, this.id, pos);
}
let currentIds = this.currentSet.split(',');
let removedIds = [id for (id of currentIds) if (newIds.indexOf(id) == -1 && newVal.indexOf(id) == -1)];
for (let removedId of removedIds) {
CustomizableUI.removeWidgetFromArea(removedId);
}
CustomizableUI.endBatchUpdate();
]]></setter>
</property>
</implementation>
</binding>
<binding id="toolbar-menubar-stub">
<implementation>
<property name="toolbox" readonly="true">
<getter><![CDATA[
if (this._toolbox)
return this._toolbox;
if (this.parentNode && this.parentNode.localName == "toolbox") {
this._toolbox = this.parentNode;
}
return this._toolbox;
]]></getter>
</property>
<property name="currentSet" readonly="true">
<getter><![CDATA[
return this.getAttribute("defaultset");
]]></getter>
</property>
<method name="insertItem">
<body><![CDATA[
return null;
]]></body>
</method>
</implementation>
</binding>
<!-- The toolbar-menubar-autohide and toolbar-drag bindings are almost
verbatim copies of their toolkit counterparts - they just inherit from
the customizableui's toolbar binding instead of toolkit's. We're currently
OK with the maintainance burden of having two copies of a binding, since
the long term goal is to move the customization framework into toolkit. -->
<binding id="toolbar-menubar-autohide"
extends="chrome://browser/content/customizableui/toolbar.xml#toolbar">
<implementation>
<constructor>
this._setInactive();
</constructor>
<destructor>
this._setActive();
</destructor>
<field name="_inactiveTimeout">null</field>
<field name="_contextMenuListener"><![CDATA[({
toolbar: this,
contextMenu: null,
get active () !!this.contextMenu,
init: function (event) {
let node = event.target;
while (node != this.toolbar) {
if (node.localName == "menupopup")
return;
node = node.parentNode;
}
let contextMenuId = this.toolbar.getAttribute("context");
if (!contextMenuId)
return;
this.contextMenu = document.getElementById(contextMenuId);
if (!this.contextMenu)
return;
this.contextMenu.addEventListener("popupshown", this, false);
this.contextMenu.addEventListener("popuphiding", this, false);
this.toolbar.addEventListener("mousemove", this, false);
},
handleEvent: function (event) {
switch (event.type) {
case "popupshown":
this.toolbar.removeEventListener("mousemove", this, false);
break;
case "popuphiding":
case "mousemove":
this.toolbar._setInactiveAsync();
this.toolbar.removeEventListener("mousemove", this, false);
this.contextMenu.removeEventListener("popuphiding", this, false);
this.contextMenu.removeEventListener("popupshown", this, false);
this.contextMenu = null;
break;
}
}
})]]></field>
<method name="_setInactive">
<body><![CDATA[
this.setAttribute("inactive", "true");
]]></body>
</method>
<method name="_setInactiveAsync">
<body><![CDATA[
this._inactiveTimeout = setTimeout(function (self) {
if (self.getAttribute("autohide") == "true") {
self._inactiveTimeout = null;
self._setInactive();
}
}, 0, this);
]]></body>
</method>
<method name="_setActive">
<body><![CDATA[
if (this._inactiveTimeout) {
clearTimeout(this._inactiveTimeout);
this._inactiveTimeout = null;
}
this.removeAttribute("inactive");
]]></body>
</method>
</implementation>
<handlers>
<handler event="DOMMenuBarActive" action="this._setActive();"/>
<handler event="popupshowing" action="this._setActive();"/>
<handler event="mousedown" button="2" action="this._contextMenuListener.init(event);"/>
<handler event="DOMMenuBarInactive"><![CDATA[
if (!this._contextMenuListener.active)
this._setInactiveAsync();
]]></handler>
</handlers>
</binding>
<binding id="toolbar-drag"
extends="chrome://browser/content/customizableui/toolbar.xml#toolbar">
<implementation>
<field name="_dragBindingAlive">true</field>
<constructor><![CDATA[
if (!this._draggableStarted) {
this._draggableStarted = true;
try {
let tmp = {};
Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
let draggableThis = new tmp.WindowDraggingElement(this);
draggableThis.mouseDownCheck = function(e) {
return this._dragBindingAlive;
};
} catch (e) {}
}
]]></constructor>
</implementation>
</binding>
<!-- This is a peculiar binding. It is here to deal with overlayed/inserted add-on content,
and immediately direct such content elsewhere. -->
<binding id="addonbar-delegating">
<implementation>
<constructor><![CDATA[
// Reading these immediately so nobody messes with them anymore:
this._delegatingToolbar = this.getAttribute("toolbar-delegate");
// Leaving those in here to unbreak some code:
if (document.readyState == "complete") {
this._init();
} else {
// Need to wait until XUL overlays are loaded. See bug 554279.
let self = this;
document.addEventListener("readystatechange", function onReadyStateChange() {
if (document.readyState != "complete")
return;
document.removeEventListener("readystatechange", onReadyStateChange, false);
self._init();
}, false);
}
]]></constructor>
<method name="_init">
<body><![CDATA[
// Searching for the toolbox palette in the toolbar binding because
// toolbars are constructed first.
let toolbox = this.toolbox;
if (toolbox && !toolbox.palette) {
for (let node of toolbox.children) {
if (node.localName == "toolbarpalette") {
// Hold on to the palette but remove it from the document.
toolbox.palette = node;
toolbox.removeChild(node);
}
}
}
// pass the current set of children for comparison with placements:
let children = [node.id for (node of this.childNodes)
if (node.getAttribute("skipintoolbarset") != "true" && node.id)];
CustomizableUI.registerToolbarNode(this, children);
this.evictNodes();
// We can't easily use |this| or strong bindings for the observer fn here
// because that creates leaky circular references when the node goes away,
// and XBL destructors are unreliable.
let mutationObserver = new MutationObserver(function(mutations) {
if (!mutations.length) {
return;
}
let toolbar = mutations[0].target;
// Can't use our own attribute because we might not have one if we're set to
// collapsed
let areCustomizing = toolbar.ownerDocument.documentElement.getAttribute("customizing");
if (!toolbar._isModifying && !areCustomizing) {
toolbar.evictNodes();
}
});
mutationObserver.observe(this, {childList: true});
]]></body>
</method>
<method name="evictNodes">
<body><![CDATA[
this._isModifying = true;
let i = this.childNodes.length;
while (i--) {
let node = this.childNodes[i];
if (this.childNodes[i].id) {
this.evictNode(this.childNodes[i]);
} else {
node.remove();
}
}
this._isModifying = false;
]]></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>

View File

@ -0,0 +1,12 @@
# -*- 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',
]
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,774 @@
/* 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");
XPCOMUtils.defineLazyModuleGetter(this, "RecentlyClosedTabsAndWindowsMenuUtils",
"resource:///modules/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "CharsetManager",
"@mozilla.org/charset-converter-manager;1",
"nsICharsetConverterManager");
const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const kPrefCustomizationDebug = "browser.uiCustomization.debug";
const kWidePanelItemClass = "panel-wide-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({id: aAttrs.id}, name);
aNode.setAttribute(name, value);
}
}
}
function updateCombinedWidgetStyle(aNode, aArea, aModifyAutoclose) {
let inPanel = (aArea == CustomizableUI.AREA_PANEL);
let cls = inPanel ? "panel-combined-button" : "toolbarbutton-1";
if (!aArea)
cls = null;
let attrs = {class: cls};
if (aModifyAutoclose) {
attrs.noautoclose = inPanel ? true : null;
}
for (let i = 0, l = aNode.childNodes.length; i < l; ++i) {
setAttributes(aNode.childNodes[i], attrs);
}
}
const CustomizableWidgets = [{
id: "history-panelmenu",
type: "view",
viewId: "PanelUI-history",
shortcutId: "key_gotoHistory",
removable: true,
defaultArea: CustomizableUI.AREA_PANEL,
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 onHistoryVisit = function (aUri, aEvent, aItem) {
doc.defaultView.openUILink(aUri, aEvent);
CustomizableUI.hidePanelForNode(aItem);
};
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.setAttribute("tabindex", "0");
item.addEventListener("command", function (aEvent) {
onHistoryVisit(uri, aEvent, item);
});
item.addEventListener("click", function (aEvent) {
onHistoryVisit(uri, aEvent, 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!");
},
});
let recentlyClosedTabs = doc.getElementById("PanelUI-recentlyClosedTabs");
while (recentlyClosedTabs.firstChild) {
recentlyClosedTabs.removeChild(recentlyClosedTabs.firstChild);
}
let recentlyClosedWindows = doc.getElementById("PanelUI-recentlyClosedWindows");
while (recentlyClosedWindows.firstChild) {
recentlyClosedWindows.removeChild(recentlyClosedWindows.firstChild);
}
let tabsFragment = RecentlyClosedTabsAndWindowsMenuUtils.getTabsFragment(doc.defaultView, "toolbarbutton");
let separator = doc.getElementById("PanelUI-recentlyClosedTabs-separator");
separator.hidden = !tabsFragment.childElementCount;
recentlyClosedTabs.appendChild(tabsFragment);
let windowsFragment = RecentlyClosedTabsAndWindowsMenuUtils.getWindowsFragment(doc.defaultView, "toolbarbutton");
separator = doc.getElementById("PanelUI-recentlyClosedWindows-separator");
separator.hidden = !windowsFragment.childElementCount;
recentlyClosedWindows.appendChild(windowsFragment);
},
onViewHiding: function(aEvent) {
LOG("History view is being hidden!");
}
}, {
id: "privatebrowsing-button",
removable: true,
shortcutId: "key_privatebrowsing",
defaultArea: 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,
shortcutId: "key_savePage",
defaultArea: 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,
shortcutId: "key_find",
defaultArea: 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,
shortcutId: "openFileKb",
defaultArea: 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",
type: "view",
viewId: "PanelUI-developer",
removable: true,
shortcutId: "key_devToolboxMenuItem",
defaultArea: CustomizableUI.AREA_PANEL,
onViewShowing: function(aEvent) {
// Populate the subview with whatever menuitems are in the developer
// menu. We skip menu elements, because the menu panel has no way
// of dealing with those right now.
let doc = aEvent.target.ownerDocument;
let win = doc.defaultView;
let items = doc.getElementById("PanelUI-developerItems");
let menu = doc.getElementById("menuWebDeveloperPopup");
let attrs = ["oncommand", "onclick", "label", "key", "disabled",
"command"];
let fragment = doc.createDocumentFragment();
for (let node of menu.children) {
if (node.hidden)
continue;
let item;
if (node.localName == "menuseparator") {
item = doc.createElementNS(kNSXUL, "menuseparator");
} else if (node.localName == "menuitem") {
item = doc.createElementNS(kNSXUL, "toolbarbutton");
} else {
continue;
}
for (let attr of attrs) {
let attrVal = node.getAttribute(attr);
if (attrVal)
item.setAttribute(attr, attrVal);
}
item.setAttribute("tabindex", "0");
fragment.appendChild(item);
}
items.appendChild(fragment);
aEvent.target.addEventListener("command", win.PanelUI.onCommandHandler);
},
onViewHiding: function(aEvent) {
let doc = aEvent.target.ownerDocument;
let win = doc.defaultView;
let items = doc.getElementById("PanelUI-developerItems");
let parent = items.parentNode;
// We'll take the container out of the document before cleaning it out
// to avoid reflowing each time we remove something.
parent.removeChild(items);
while (items.firstChild) {
items.firstChild.remove();
}
parent.appendChild(items);
aEvent.target.removeEventListener("command",
win.PanelUI.onCommandHandler);
}
}, {
id: "add-ons-button",
removable: true,
shortcutId: "key_openAddons",
defaultArea: 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,
#ifdef XP_WIN
label: "preferences-button.labelWin",
tooltiptext: "preferences-button.tooltipWin",
#endif
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,
onBuild: function(aDocument) {
const kPanelId = "PanelUI-popup";
let inPanel = (this.currentArea == CustomizableUI.AREA_PANEL);
let noautoclose = inPanel ? "true" : null;
let cls = inPanel ? "panel-combined-button" : "toolbarbutton-1";
if (!this.currentArea)
cls = null;
let buttons = [{
id: "zoom-out-button",
noautoclose: noautoclose,
command: "cmd_fullZoomReduce",
class: cls,
label: true,
tooltiptext: true
}, {
id: "zoom-reset-button",
noautoclose: noautoclose,
command: "cmd_fullZoomReset",
class: cls,
tooltiptext: true
}, {
id: "zoom-in-button",
noautoclose: noautoclose,
command: "cmd_fullZoomEnlarge",
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");
node.classList.add("chromeclass-toolbar-additional");
node.classList.add("toolbaritem-combined-buttons");
node.classList.add(kWidePanelItemClass);
buttons.forEach(function(aButton) {
let btnNode = aDocument.createElementNS(kNSXUL, "toolbarbutton");
setAttributes(btnNode, aButton);
if (inPanel)
btnNode.setAttribute("tabindex", "0");
node.appendChild(btnNode);
});
// The middle node is the 'Reset Zoom' button.
let zoomResetButton = node.childNodes[1];
let window = aDocument.defaultView;
function updateZoomResetButton() {
//XXXgijs in some tests we get called very early, and there's no docShell on the
// tabbrowser. This breaks the zoom toolkit code (see bug 897410). Don't let that happen:
let zoomFactor = 100;
if (window.gBrowser.docShell) {
zoomFactor = Math.floor(window.ZoomManager.zoom * 100);
}
zoomResetButton.setAttribute("label", CustomizableUI.getLocalizedProperty(
buttons[1], "label", [zoomFactor]
));
};
// 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);
if (inPanel && this.currentArea) {
let panel = aDocument.getElementById(kPanelId);
panel.addEventListener("popupshowing", updateZoomResetButton);
} else {
updateZoomResetButton();
}
let listener = {
onWidgetAdded: function(aWidgetId, aArea, aPosition) {
if (aWidgetId != this.id)
return;
updateCombinedWidgetStyle(node, aArea, true);
updateZoomResetButton();
if (aArea == CustomizableUI.AREA_PANEL) {
let panel = aDocument.getElementById(kPanelId);
panel.addEventListener("popupshowing", updateZoomResetButton);
}
}.bind(this),
onWidgetRemoved: function(aWidgetId, aPrevArea) {
if (aWidgetId != this.id)
return;
if (aPrevArea == CustomizableUI.AREA_PANEL) {
let panel = aDocument.getElementById(kPanelId);
panel.removeEventListener("popupshowing", updateZoomResetButton);
}
// When a widget is demoted to the palette ('removed'), it's visual
// style should change.
updateCombinedWidgetStyle(node, null, true);
updateZoomResetButton();
}.bind(this),
onWidgetReset: function(aWidgetId) {
if (aWidgetId != this.id)
return;
updateCombinedWidgetStyle(node, this.currentArea, true);
updateZoomResetButton();
}.bind(this),
onWidgetMoved: function(aWidgetId, aArea) {
if (aWidgetId != this.id)
return;
updateCombinedWidgetStyle(node, aArea, true);
updateZoomResetButton();
}.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");
let panel = aDoc.getElementById(kPanelId);
panel.removeEventListener("popupshowing", updateZoomResetButton);
}.bind(this),
onWidgetDrag: function(aWidgetId, aArea) {
if (aWidgetId != this.id)
return;
aArea = aArea || this.currentArea;
updateCombinedWidgetStyle(node, aArea, true);
}.bind(this)
};
CustomizableUI.addListener(listener);
return node;
}
}, {
id: "edit-controls",
type: "custom",
removable: true,
defaultArea: CustomizableUI.AREA_PANEL,
onBuild: function(aDocument) {
let inPanel = (this.currentArea == CustomizableUI.AREA_PANEL);
let cls = inPanel ? "panel-combined-button" : "toolbarbutton-1";
if (!this.currentArea)
cls = null;
let buttons = [{
id: "cut-button",
command: "cmd_cut",
class: cls,
label: true,
tooltiptext: true
}, {
id: "copy-button",
command: "cmd_copy",
class: cls,
label: true,
tooltiptext: true
}, {
id: "paste-button",
command: "cmd_paste",
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");
node.classList.add("chromeclass-toolbar-additional");
node.classList.add("toolbaritem-combined-buttons");
node.classList.add(kWidePanelItemClass);
buttons.forEach(function(aButton) {
let btnNode = aDocument.createElementNS(kNSXUL, "toolbarbutton");
setAttributes(btnNode, aButton);
if (inPanel)
btnNode.setAttribute("tabindex", "0");
node.appendChild(btnNode);
});
let listener = {
onWidgetAdded: function(aWidgetId, aArea, aPosition) {
if (aWidgetId != this.id)
return;
updateCombinedWidgetStyle(node, aArea);
}.bind(this),
onWidgetRemoved: function(aWidgetId, aPrevArea) {
if (aWidgetId != this.id)
return;
// When a widget is demoted to the palette ('removed'), it's visual
// style should change.
updateCombinedWidgetStyle(node);
}.bind(this),
onWidgetReset: function(aWidgetId) {
if (aWidgetId != this.id)
return;
updateCombinedWidgetStyle(node, this.currentArea);
}.bind(this),
onWidgetMoved: function(aWidgetId, aArea) {
if (aWidgetId != this.id)
return;
updateCombinedWidgetStyle(node, aArea);
}.bind(this),
onWidgetInstanceRemoved: function(aWidgetId, aDoc) {
if (aWidgetId != this.id || aDoc != aDocument)
return;
CustomizableUI.removeListener(listener);
}.bind(this),
onWidgetDrag: function(aWidgetId, aArea) {
if (aWidgetId != this.id)
return;
aArea = aArea || this.currentArea;
updateCombinedWidgetStyle(node, aArea);
}.bind(this)
};
CustomizableUI.addListener(listener);
return node;
}
},
{
id: "feed-button",
type: "view",
viewId: "PanelUI-feeds",
removable: true,
defaultArea: CustomizableUI.AREA_PANEL,
onClick: function(aEvent) {
let win = aEvent.target.ownerDocument.defaultView;
let feeds = win.gBrowser.selectedBrowser.feeds;
// Here, we only care about the case where we have exactly 1 feed and the
// user clicked...
let isClick = (aEvent.button == 0 || aEvent.button == 1);
if (feeds && feeds.length == 1 && isClick) {
aEvent.preventDefault();
aEvent.stopPropagation();
win.FeedHandler.subscribeToFeed(feeds[0].href, aEvent);
CustomizableUI.hidePanelForNode(aEvent.target);
}
},
onViewShowing: function(aEvent) {
let doc = aEvent.detail.ownerDocument;
let container = doc.getElementById("PanelUI-feeds");
let gotView = doc.defaultView.FeedHandler.buildFeedList(container, true);
// For no feeds or only a single one, don't show the panel.
if (!gotView) {
aEvent.preventDefault();
aEvent.stopPropagation();
return;
}
},
onCreated: function(node) {
let win = node.ownerDocument.defaultView;
let selectedBrowser = win.gBrowser.selectedBrowser;
let feeds = selectedBrowser && selectedBrowser.feeds;
if (!feeds || !feeds.length) {
node.setAttribute("disabled", "true");
}
}
}, {
id: "characterencoding-button",
type: "view",
viewId: "PanelUI-characterEncodingView",
removable: true,
defaultArea: CustomizableUI.AREA_PANEL,
maybeDisableMenu: function(aDocument) {
let window = aDocument.defaultView;
return !(window.gBrowser &&
window.gBrowser.docShell &&
window.gBrowser.docShell.mayEnableCharacterEncodingMenu);
},
getCharsetList: function(aSection, aDocument) {
let currCharset = aDocument.defaultView.content.document.characterSet;
let list = "";
try {
let pref = "intl.charsetmenu.browser." + aSection;
list = Services.prefs.getComplexValue(pref,
Ci.nsIPrefLocalizedString).data;
} catch (e) {}
list = list.trim();
if (!list)
return [];
list = list.split(",");
let items = [];
for (let charset of list) {
charset = charset.trim();
let notForBrowser = false;
try {
notForBrowser = CharsetManager.getCharsetData(charset,
"notForBrowser");
} catch (e) {}
if (notForBrowser)
continue;
let title = charset;
try {
title = CharsetManager.getCharsetTitle(charset);
} catch (e) {}
items.push({value: charset, name: title, current: charset == currCharset});
}
return items;
},
getAutoDetectors: function(aDocument) {
let detectorEnum = CharsetManager.GetCharsetDetectorList();
let currDetector;
try {
currDetector = Services.prefs.getComplexValue(
"intl.charset.detector", Ci.nsIPrefLocalizedString).data;
} catch (e) {}
if (!currDetector)
currDetector = "off";
currDetector = "chardet." + currDetector;
let items = [];
while (detectorEnum.hasMore()) {
let detector = detectorEnum.getNext();
let title = detector;
try {
title = CharsetManager.getCharsetTitle(detector);
} catch (e) {}
items.push({value: detector, name: title, current: detector == currDetector});
}
items.sort((aItem1, aItem2) => {
return aItem1.name.localeCompare(aItem2.name);
});
return items;
},
populateList: function(aDocument, aContainerId, aSection) {
let containerElem = aDocument.getElementById(aContainerId);
while (containerElem.firstChild) {
containerElem.removeChild(containerElem.firstChild);
}
containerElem.addEventListener("command", this.onCommand, false);
let list = [];
if (aSection == "autodetect") {
list = this.getAutoDetectors(aDocument);
} else if (aSection == "browser") {
let staticList = this.getCharsetList("static", aDocument);
let cacheList = this.getCharsetList("cache", aDocument);
// Combine lists, and de-duplicate.
let checkedIn = new Set();
for (let item of staticList.concat(cacheList)) {
let itemName = item.name.toLowerCase();
if (!checkedIn.has(itemName)) {
list.push(item);
checkedIn.add(itemName);
}
}
}
// Update the appearance of the buttons when it's not possible to
// customize encoding.
let disabled = this.maybeDisableMenu(aDocument);
for (let item of list) {
let elem = aDocument.createElementNS(kNSXUL, "toolbarbutton");
elem.setAttribute("label", item.name);
elem.section = aSection;
elem.value = item.value;
if (item.current)
elem.setAttribute("current", "true");
if (disabled)
elem.setAttribute("disabled", "true");
containerElem.appendChild(elem);
}
},
onViewShowing: function(aEvent) {
let document = aEvent.target.ownerDocument;
this.populateList(document,
"PanelUI-characterEncodingView-customlist",
"browser");
this.populateList(document,
"PanelUI-characterEncodingView-autodetect",
"autodetect");
},
onCommand: function(aEvent) {
let node = aEvent.target;
if (!node.hasAttribute || !node.section) {
return;
}
CustomizableUI.hidePanelForNode(node);
let window = node.ownerDocument.defaultView;
let section = node.section;
let value = node.value;
// The behavior as implemented here is directly based off of the
// `MultiplexHandler()` method in browser.js.
if (section == "browser") {
window.BrowserSetForcedCharacterSet(value);
} else if (section == "autodetect") {
value = value.replace(/^chardet\./, "");
if (value == "off") {
value = "";
}
// Set the detector pref.
try {
let str = Cc["@mozilla.org/supports-string;1"]
.createInstance(Ci.nsISupportsString);
str.data = value;
Services.prefs.setComplexValue("intl.charset.detector", Ci.nsISupportsString, str);
} catch (e) {
Cu.reportError("Failed to set the intl.charset.detector preference.");
}
// Prepare a browser page reload with a changed charset.
window.BrowserCharsetReload();
}
},
onCreated: function(aNode) {
const kPanelId = "PanelUI-popup";
let document = aNode.ownerDocument;
let updateButton = () => {
if (this.maybeDisableMenu(document))
aNode.setAttribute("disabled", "true");
else
aNode.removeAttribute("disabled");
};
if (this.currentArea == CustomizableUI.AREA_PANEL) {
let panel = document.getElementById(kPanelId);
panel.addEventListener("popupshowing", updateButton);
}
let listener = {
onWidgetAdded: (aWidgetId, aArea) => {
if (aWidgetId != this.id)
return;
if (aArea == CustomizableUI.AREA_PANEL) {
let panel = document.getElementById(kPanelId);
panel.addEventListener("popupshowing", updateButton);
}
},
onWidgetRemoved: (aWidgetId, aPrevArea) => {
if (aWidgetId != this.id)
return;
if (aPrevArea == CustomizableUI.AREA_PANEL) {
let panel = document.getElementById(kPanelId);
panel.removeEventListener("popupshowing", updateButton);
}
},
onWidgetInstanceRemoved: (aWidgetId, aDoc) => {
if (aWidgetId != this.id || aDoc != document)
return;
CustomizableUI.removeListener(listener);
let panel = aDoc.getElementById(kPanelId);
panel.removeEventListener("popupshowing", updateButton);
}
};
CustomizableUI.addListener(listener);
}
}];

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,152 @@
/* 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 = ["PanelWideWidgetTracker"];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
let gModuleName = "[PanelWideWidgetTracker]";
#include logging.js
let gPanel = CustomizableUI.AREA_PANEL;
// We keep track of the widget placements for the panel locally:
let gPanelPlacements = [];
// All the wide widgets we know of:
let gWideWidgets = new Set();
// All the widgets we know of:
let gSeenWidgets = new Set();
// The class by which we recognize wide widgets:
const kWidePanelItemClass = "panel-wide-item";
// TODO(bug 885574): Merge this constant with the one in CustomizeMode.jsm,
// maybe just use a pref for this.
const kColumnsInMenuPanel = 3;
let PanelWideWidgetTracker = {
// Listeners used to validate panel contents whenever they change:
onWidgetAdded: function(aWidgetId, aArea, aPosition) {
if (aArea == gPanel) {
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
let moveForward = this.shouldMoveForward(aWidgetId, aPosition);
this.adjustWidgets(aWidgetId, moveForward);
}
},
onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) {
if (aArea == gPanel) {
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
let moveForward = this.shouldMoveForward(aWidgetId, aNewPosition);
this.adjustWidgets(aWidgetId, moveForward);
}
},
onWidgetRemoved: function(aWidgetId, aPrevArea) {
if (aPrevArea == gPanel) {
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
let pos = gPanelPlacements.indexOf(aWidgetId);
this.adjustWidgets(aWidgetId, false);
}
},
onWidgetReset: function(aWidgetId) {
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
},
// Listener to keep abreast of any new nodes. We use the DOM one because
// we need access to the actual node's classlist, so we can't use the ones above.
// Furthermore, onWidgetCreated only fires for API-based widgets, not for XUL ones.
onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer) {
if (!gSeenWidgets.has(aNode.id)) {
if (aNode.classList.contains(kWidePanelItemClass)) {
gWideWidgets.add(aNode.id);
}
gSeenWidgets.add(aNode.id);
}
},
// When widgets get destroyed, we remove them from our sets of stuff we care about:
onWidgetDestroyed: function(aWidgetId) {
gSeenWidgets.delete(aWidgetId);
gWideWidgets.delete(aWidgetId);
},
shouldMoveForward: function(aWidgetId, aPosition) {
let currentWidgetAtPosition = gPanelPlacements[aPosition + 1];
return gWideWidgets.has(currentWidgetAtPosition) && !gWideWidgets.has(aWidgetId);
},
adjustWidgets: function(aWidgetId, aMoveForwards) {
if (this.adjusting) {
return;
}
this.adjusting = true;
let widgetsAffected = [w for (w of gPanelPlacements) if (gWideWidgets.has(w))];
// If we're moving the wide widgets forwards (down/to the right in the panel)
// we want to start with the last widgets. Otherwise we move widgets over other wide
// widgets, which might mess up their order. Likewise, if moving backwards we should start with
// the first widget and work our way down/right from there.
let compareFn = aMoveForwards ? (function(a, b) a < b) : (function(a, b) a > b)
widgetsAffected.sort(function(a, b) compareFn(gPanelPlacements.indexOf(a),
gPanelPlacements.indexOf(b)));
for (let widget of widgetsAffected) {
this.adjustPosition(widget, aMoveForwards);
}
this.adjusting = false;
},
// This function is called whenever an item gets moved in the menu panel. It
// adjusts the position of widgets within the panel to prevent "gaps" between
// wide widgets that could be filled up with single column widgets
adjustPosition: function(aWidgetId, aMoveForwards) {
// Make sure that there are n % columns = 0 narrow buttons before the widget.
let placementIndex = gPanelPlacements.indexOf(aWidgetId);
let prevSiblingCount = 0;
let fixedPos = null;
while (placementIndex--) {
let thisWidgetId = gPanelPlacements[placementIndex];
if (gWideWidgets.has(thisWidgetId)) {
continue;
}
let widgetWrapper = CustomizableUI.getWidget(gPanelPlacements[placementIndex]);
// This widget might not actually exist:
if (!widgetWrapper) {
continue;
}
// This widget might still not actually exist:
if (widgetWrapper.provider == CustomizableUI.PROVIDER_XUL &&
widgetWrapper.instances.length == 0) {
continue;
}
// Or it might only be there some of the time:
if (widgetWrapper.provider == CustomizableUI.PROVIDER_API &&
widgetWrapper.showInPrivateBrowsing === false) {
if (!fixedPos) {
fixedPos = placementIndex;
} else {
fixedPos = Math.min(fixedPos, placementIndex);
}
// We want to position ourselves before this item:
prevSiblingCount = 0;
} else {
prevSiblingCount++;
}
}
if (fixedPos !== null || prevSiblingCount % kColumnsInMenuPanel) {
let desiredPos = (fixedPos !== null) ? fixedPos : gPanelPlacements.indexOf(aWidgetId);
let desiredChange = -(prevSiblingCount % kColumnsInMenuPanel);
if (aMoveForwards && fixedPos == null) {
// +1 because otherwise we'd count ourselves:
desiredChange = kColumnsInMenuPanel + desiredChange + 1;
}
desiredPos += desiredChange;
CustomizableUI.moveWidgetWithinArea(aWidgetId, desiredPos);
}
},
init: function() {
// Initialize our local placements copy and register the listener
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
CustomizableUI.addListener(this);
},
};

View File

@ -0,0 +1,66 @@
/* 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 = ["ScrollbarSampler"];
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
let gSystemScrollbarWidth = null;
this.ScrollbarSampler = {
getSystemScrollbarWidth: function() {
let deferred = Promise.defer();
if (gSystemScrollbarWidth !== null) {
deferred.resolve(gSystemScrollbarWidth);
return deferred.promise;
}
this._sampleSystemScrollbarWidth().then(function(systemScrollbarWidth) {
gSystemScrollbarWidth = systemScrollbarWidth;
deferred.resolve(gSystemScrollbarWidth);
});
return deferred.promise;
},
_sampleSystemScrollbarWidth: function() {
let deferred = Promise.defer();
let hwin = Services.appShell.hiddenDOMWindow;
let hdoc = hwin.document.documentElement;
let iframe = hwin.document.createElementNS("http://www.w3.org/1999/xhtml",
"html:iframe");
iframe.setAttribute("srcdoc", '<body style="overflow-y: scroll"></body>');
hdoc.appendChild(iframe);
let cwindow = iframe.contentWindow;
let utils = cwindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
cwindow.addEventListener("load", function onLoad(aEvent) {
cwindow.removeEventListener("load", onLoad);
let sbWidth = {};
try {
utils.getScrollbarSize(true, sbWidth, {});
} catch(e) {
Cu.reportError("Could not sample scrollbar size: " + e + " -- " +
e.stack);
sbWidth.value = 0;
}
// Minimum width of 10 so that we have enough padding:
sbWidth.value = Math.max(sbWidth.value, 10);
deferred.resolve(sbWidth.value);
iframe.remove();
});
return deferred.promise;
}
};
Object.freeze(this.ScrollbarSampler);

View File

@ -0,0 +1,25 @@
#if 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/. */
#endif
XPCOMUtils.defineLazyModuleGetter(this, "console",
"resource://gre/modules/devtools/Console.jsm");
let gDebug = false;
try {
gDebug = Services.prefs.getBoolPref(kPrefCustomizationDebug);
} catch (e) {}
function LOG(...args) {
if (gDebug) {
args.unshift(gModuleName);
console.log.apply(console, args);
}
}
function ERROR(...args) {
args.unshift(gModuleName);
console.error.apply(console, args);
}

View File

@ -0,0 +1,16 @@
# -*- 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/.
EXTRA_JS_MODULES += [
'ScrollbarSampler.jsm',
]
EXTRA_PP_JS_MODULES += [
'CustomizableUI.jsm',
'CustomizableWidgets.jsm',
'CustomizeMode.jsm',
'PanelWideWidgetTracker.jsm',
]

View File

@ -0,0 +1,36 @@
[DEFAULT]
support-files =
head.js
[browser_873501_handle_specials.js]
[browser_876926_customize_mode_wrapping.js]
[browser_876944_customize_mode_create_destroy.js]
[browser_877006_missing_view.js]
[browser_877178_unregisterArea.js]
[browser_877447_skip_missing_ids.js]
[browser_878452_drag_to_panel.js]
[browser_880382_drag_wide_widgets_in_panel.js]
[browser_885530_showInPrivateBrowsing.js]
[browser_886323_buildArea_removable_nodes.js]
[browser_887438_currentset_shim.js]
[browser_888817_currentset_updating.js]
[browser_890140_orphaned_placeholders.js]
[browser_890262_destroyWidget_after_add_to_panel.js]
[browser_892955_isWidgetRemovable_for_removed_widgets.js]
[browser_892956_destroyWidget_defaultPlacements.js]
[browser_909779_overflow_toolbars_new_window.js]
[browser_913972_currentset_overflow.js]
[browser_914138_widget_API_overflowable_toolbar.js]
# Because of the specific widths, this test is fragile and won't necessarily pass on other platforms
run-if = os == "mac"
[browser_914863_disabled_help_quit_buttons.js]
[browser_918049_skipintoolbarset_dnd.js]
[browser_923857_customize_mode_event_wrapping_during_reset.js]
[browser_927717_customize_drag_empty_toolbar.js]
[browser_934113_menubar_removable.js]
# Because this test is about the menubar, it can't be run on mac
skip-if = os == "mac"

View File

@ -0,0 +1,91 @@
/* 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/. */
const kToolbarName = "test-specials-toolbar";
let gTests = [
{
desc: "Add a toolbar with two springs and the downloads button.",
run: function() {
// Create the toolbar with a single spring:
createToolbarWithPlacements(kToolbarName, ["spring"]);
ok(document.getElementById(kToolbarName), "Toolbar should be created.");
// Check it's there with a generated ID:
assertAreaPlacements(kToolbarName, [/customizableui-special-spring\d+/]);
let [springId] = getAreaWidgetIds(kToolbarName);
// Add a second spring, check if that's there and doesn't share IDs
CustomizableUI.addWidgetToArea("spring", kToolbarName);
assertAreaPlacements(kToolbarName, [springId,
/customizableui-special-spring\d+/]);
let [, spring2Id] = getAreaWidgetIds(kToolbarName);
isnot(springId, spring2Id, "Springs shouldn't have identical IDs.");
// Try moving the downloads button to this new toolbar, between the two springs:
CustomizableUI.addWidgetToArea("downloads-button", kToolbarName, 1);
assertAreaPlacements(kToolbarName, [springId, "downloads-button", spring2Id]);
},
teardown: removeCustomToolbars
},
{
desc: "Add separators around the downloads button.",
run: function() {
createToolbarWithPlacements(kToolbarName, ["separator"]);
ok(document.getElementById(kToolbarName), "Toolbar should be created.");
// Check it's there with a generated ID:
assertAreaPlacements(kToolbarName, [/customizableui-special-separator\d+/]);
let [separatorId] = getAreaWidgetIds(kToolbarName);
CustomizableUI.addWidgetToArea("separator", kToolbarName);
assertAreaPlacements(kToolbarName, [separatorId,
/customizableui-special-separator\d+/]);
let [, separator2Id] = getAreaWidgetIds(kToolbarName);
isnot(separatorId, separator2Id, "Separator ids shouldn't be equal.");
CustomizableUI.addWidgetToArea("downloads-button", kToolbarName, 1);
assertAreaPlacements(kToolbarName, [separatorId, "downloads-button", separator2Id]);
},
teardown: removeCustomToolbars
},
{
desc: "Add spacers around the downloads button.",
run: function() {
createToolbarWithPlacements(kToolbarName, ["spacer"]);
ok(document.getElementById(kToolbarName), "Toolbar should be created.");
// Check it's there with a generated ID:
assertAreaPlacements(kToolbarName, [/customizableui-special-spacer\d+/]);
let [spacerId] = getAreaWidgetIds(kToolbarName);
CustomizableUI.addWidgetToArea("spacer", kToolbarName);
assertAreaPlacements(kToolbarName, [spacerId,
/customizableui-special-spacer\d+/]);
let [, spacer2Id] = getAreaWidgetIds(kToolbarName);
isnot(spacerId, spacer2Id, "Spacer ids shouldn't be equal.");
CustomizableUI.addWidgetToArea("downloads-button", kToolbarName, 1);
assertAreaPlacements(kToolbarName, [spacerId, "downloads-button", spacer2Id]);
},
teardown: removeCustomToolbars
}
];
function asyncCleanup() {
yield resetCustomization();
}
function cleanup() {
removeCustomToolbars();
}
function test() {
waitForExplicitFinish();
registerCleanupFunction(cleanup);
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,173 @@
/* 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/. */
const kXULWidgetId = "sync-button";
const kAPIWidgetId = "feed-button";
const kPanel = CustomizableUI.AREA_PANEL;
const kToolbar = CustomizableUI.AREA_NAVBAR;
const kVisiblePalette = "customization-palette";
const kPlaceholderClass = "panel-customization-placeholder";
function checkWrapper(id) {
is(document.querySelectorAll("#wrapper-" + id).length, 1, "There should be exactly 1 wrapper for " + id + " in the customizing window.");
}
let move = {
"drag": function(id, target) {
let targetNode = document.getElementById(target);
if (targetNode.customizationTarget) {
targetNode = targetNode.customizationTarget;
}
simulateItemDrag(document.getElementById(id), targetNode);
},
"dragToItem": function(id, target) {
let targetNode = document.getElementById(target);
if (targetNode.customizationTarget) {
targetNode = targetNode.customizationTarget;
}
let items = targetNode.querySelectorAll("toolbarpaletteitem:not(." + kPlaceholderClass + ")");
if (target == kPanel) {
targetNode = items[items.length - 1];
} else {
targetNode = items[0];
}
simulateItemDrag(document.getElementById(id), targetNode);
},
"API": function(id, target) {
if (target == kVisiblePalette) {
return CustomizableUI.removeWidgetFromArea(id);
}
return CustomizableUI.addWidgetToArea(id, target, null);
}
}
function isLast(containerId, defaultPlacements, id) {
assertAreaPlacements(containerId, defaultPlacements.concat([id]));
is(document.getElementById(containerId).customizationTarget.lastChild.firstChild.id, id,
"Widget " + id + " should be in " + containerId + " in customizing window.");
is(otherWin.document.getElementById(containerId).customizationTarget.lastChild.id, id,
"Widget " + id + " should be in " + containerId + " in other window.");
}
function isFirst(containerId, defaultPlacements, id) {
assertAreaPlacements(containerId, [id].concat(defaultPlacements));
is(document.getElementById(containerId).customizationTarget.firstChild.firstChild.id, id,
"Widget " + id + " should be in " + containerId + " in customizing window.");
is(otherWin.document.getElementById(containerId).customizationTarget.firstChild.id, id,
"Widget " + id + " should be in " + containerId + " in other window.");
}
function checkToolbar(id, method) {
// Place at start of the toolbar:
let toolbarPlacements = getAreaWidgetIds(kToolbar);
move[method](id, kToolbar);
if (method == "dragToItem") {
isFirst(kToolbar, toolbarPlacements, id);
} else {
isLast(kToolbar, toolbarPlacements, id);
}
checkWrapper(id);
}
function checkPanel(id, method) {
let panelPlacements = getAreaWidgetIds(kPanel);
move[method](id, kPanel);
let children = document.getElementById(kPanel).querySelectorAll("toolbarpaletteitem:not(." + kPlaceholderClass + ")");
let otherChildren = otherWin.document.getElementById(kPanel).children;
let newPlacements = panelPlacements.concat([id]);
// Relative position of the new item from the end:
let position = -1;
// For the drag to item case, we drag to the last item, making the dragged item the
// penultimate item. We can't well use the first item because the panel has complicated
// rules about rearranging wide items (which, by default, the first two items are).
if (method == "dragToItem") {
newPlacements.pop();
newPlacements.splice(panelPlacements.length - 1, 0, id);
position = -2;
}
assertAreaPlacements(kPanel, newPlacements);
is(children[children.length + position].firstChild.id, id,
"Widget " + id + " should be in " + kPanel + " in customizing window.");
is(otherChildren[otherChildren.length + position].id, id,
"Widget " + id + " should be in " + kPanel + " in other window.");
checkWrapper(id);
}
function checkPalette(id, method) {
// Move back to palette:
move[method](id, kVisiblePalette);
ok(CustomizableUI.inDefaultState, "Should end in default state");
let visibleChildren = gCustomizeMode.visiblePalette.children;
let expectedChild = method == "dragToItem" ? visibleChildren[0] : visibleChildren[visibleChildren.length - 1];
is(expectedChild.firstChild.id, id, "Widget " + id + " was moved using " + method + " and should now be wrapped in palette in customizing window.");
if (id == kXULWidgetId) {
ok(otherWin.gNavToolbox.palette.querySelector("#" + id), "Widget " + id + " should be in invisible palette in other window.");
}
checkWrapper(id);
}
let otherWin;
let gTests = [
{
desc: "Moving widgets in two windows, one with customize mode and one without, should work",
setup: startCustomizing,
run: function() {
otherWin = yield openAndLoadWindow(null, true);
// Open panel to force its construction:
yield afterPanelOpen(otherWin);
ok(CustomizableUI.inDefaultState, "Should start in default state");
for (let widgetId of [kXULWidgetId, kAPIWidgetId]) {
for (let method of ["API", "drag", "dragToItem"]) {
info("Moving widget " + widgetId + " using " + method);
checkToolbar(widgetId, method);
checkPanel(widgetId, method);
checkPalette(widgetId, method);
checkPanel(widgetId, method);
checkToolbar(widgetId, method);
checkPalette(widgetId, method);
}
}
otherWin.close();
otherWin = null;
},
teardown: function() {
if (otherWin) {
otherWin.close();
}
yield endCustomizing();
}
}
];
function afterPanelOpen(win) {
let panelEl = win.PanelUI.panel;
let deferred = Promise.defer();
function onPanelClose(e) {
panelEl.removeEventListener("popuphidden", onPanelClose);
deferred.resolve();
}
function onPanelOpen(e) {
panelEl.removeEventListener("popupshown", onPanelOpen);
panelEl.addEventListener("popuphidden", onPanelClose);
win.PanelUI.toggle({type: "command"});
};
panelEl.addEventListener("popupshown", onPanelOpen);
win.PanelUI.toggle({type: "command"});
return deferred.promise;
}
function asyncCleanup() {
Services.prefs.clearUserPref("browser.uiCustomization.skipSourceNodeCheck");
yield resetCustomization();
}
function test() {
Services.prefs.setBoolPref("browser.uiCustomization.skipSourceNodeCheck", true);
waitForExplicitFinish();
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,65 @@
/* 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/. */
const kTestWidget1 = "test-customize-mode-create-destroy1";
const kTestWidget2 = "test-customize-mode-create-destroy2";
let gTests = [
{
desc: "Creating and destroying a widget should correctly wrap/unwrap stuff",
setup: startCustomizing,
run: function() {
CustomizableUI.createWidget({id: kTestWidget1, label: 'Pretty label', tooltiptext: 'Pretty tooltip'});
let elem = document.getElementById(kTestWidget1);
let wrapper = document.getElementById("wrapper-" + kTestWidget1);
ok(elem, "There should be an item");
ok(wrapper, "There should be a wrapper");
is(wrapper.firstChild.id, kTestWidget1, "Wrapper should have test widget");
is(wrapper.parentNode.id, "customization-palette", "Wrapper should be in palette");
CustomizableUI.destroyWidget(kTestWidget1);
wrapper = document.getElementById("wrapper-" + kTestWidget1);
ok(!wrapper, "There should be a wrapper");
let item = document.getElementById(kTestWidget1);
ok(!item, "There should no longer be an item");
},
},
{
desc: "Creating and destroying a widget should correctly deal with panel placeholders",
run: function() {
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
is(panel.querySelectorAll(".panel-customization-placeholder").length, 3, "The number of placeholders should be correct.");
CustomizableUI.createWidget({id: kTestWidget2, label: 'Pretty label', tooltiptext: 'Pretty tooltip', defaultArea: CustomizableUI.AREA_PANEL});
let elem = document.getElementById(kTestWidget2);
let wrapper = document.getElementById("wrapper-" + kTestWidget2);
ok(elem, "There should be an item");
ok(wrapper, "There should be a wrapper");
is(wrapper.firstChild.id, kTestWidget2, "Wrapper should have test widget");
is(wrapper.parentNode, panel, "Wrapper should be in panel");
is(panel.querySelectorAll(".panel-customization-placeholder").length, 2, "The number of placeholders should be correct.");
CustomizableUI.destroyWidget(kTestWidget2);
wrapper = document.getElementById("wrapper-" + kTestWidget2);
ok(!wrapper, "There should be a wrapper");
let item = document.getElementById(kTestWidget2);
ok(!item, "There should no longer be an item");
},
teardown: endCustomizing
},
];
function asyncCleanup() {
yield endCustomizing();
try {
CustomizableUI.destroyWidget(kTestWidget1);
} catch (ex) {}
try {
CustomizableUI.destroyWidget(kTestWidget2);
} catch (ex) {}
yield resetCustomization();
}
function test() {
waitForExplicitFinish();
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,50 @@
/* 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/. */
let gTests = [
{
desc: "Should be able to add broken view widget",
run: function() {
const kWidgetId = 'test-877006-broken-widget';
let widgetSpec = {
id: kWidgetId,
type: 'view',
viewId: 'idontexist',
/* Empty handler so we try to attach it maybe? */
onViewShowing: function() {
},
};
let noError = true;
try {
CustomizableUI.createWidget(widgetSpec);
CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_NAVBAR);
} catch (ex) {
Cu.reportError(ex);
noError = false;
}
ok(noError, "Should not throw an exception trying to add a broken view widget.");
noError = true;
try {
CustomizableUI.destroyWidget(kWidgetId);
} catch (ex) {
Cu.reportError(ex);
noError = false;
}
ok(noError, "Should not throw an exception trying to remove the broken view widget.");
}
}
];
function asyncCleanup() {
yield resetCustomization();
}
function test() {
waitForExplicitFinish();
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,67 @@
/* 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/. */
let gTests = [
{
desc: "Sanity checks",
run: function() {
SimpleTest.doesThrow(function() CustomizableUI.registerArea("@foo"),
"Registering areas with an invalid ID should throw.");
SimpleTest.doesThrow(function() CustomizableUI.registerArea([]),
"Registering areas with an invalid ID should throw.");
SimpleTest.doesThrow(function() CustomizableUI.registerArea(CustomizableUI.AREA_NAVBAR),
"Registering an area with an ID that's already registered should throw.");
SimpleTest.doesThrow(function() CustomizableUI.unregisterArea("@foo"),
"Unregistering areas with an invalid ID should throw.");
SimpleTest.doesThrow(function() CustomizableUI.unregisterArea([]),
"Unregistering areas with an invalid ID should throw.");
SimpleTest.doesThrow(function() CustomizableUI.unregisterArea("unknown"),
"Unregistering an area that's not registered should throw.");
}
},
{
desc: "Check areas are loaded with their default placements.",
run: function() {
ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
}
},
{
desc: "Check registering and unregistering a new area.",
run: function() {
const kToolbarId = "test-registration-toolbar";
const kButtonId = "test-registration-button";
createDummyXULButton(kButtonId);
createToolbarWithPlacements(kToolbarId, ["spring", kButtonId, "spring"]);
assertAreaPlacements(kToolbarId,
[/customizableui-special-spring\d+/,
kButtonId,
/customizableui-special-spring\d+/]);
ok(CustomizableUI.inDefaultState, "With a new toolbar and default placements, " +
"everything should still be in a default state.");
removeCustomToolbars(); // Will call unregisterArea for us
ok(CustomizableUI.inDefaultState, "When the toolbar is unregistered, " +
"everything should still be in a default state.");
}
}
];
function asyncCleanup() {
yield resetCustomization();
}
function cleanup() {
removeCustomToolbars();
}
function test() {
waitForExplicitFinish();
registerCleanupFunction(cleanup);
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,35 @@
/* 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/. */
let gTests = [
{
run: function() {
const kButtonId = "look-at-me-disappear-button";
CustomizableUI.reset();
ok(CustomizableUI.inDefaultState, "Should be in the default state.");
let btn = createDummyXULButton(kButtonId, "Gone!");
CustomizableUI.addWidgetToArea(kButtonId, CustomizableUI.AREA_NAVBAR);
ok(!CustomizableUI.inDefaultState, "Should no longer be in the default state.");
is(btn.parentNode.parentNode.id, CustomizableUI.AREA_NAVBAR, "Button should be in navbar");
btn.remove();
is(btn.parentNode, null, "Button is no longer in the navbar");
ok(CustomizableUI.inDefaultState, "Should be back in the default state, " +
"despite unknown button ID in placements.");
}
}
];
function asyncCleanup() {
yield resetCustomization();
}
function cleanup() {
removeCustomToolbars();
}
function test() {
waitForExplicitFinish();
registerCleanupFunction(cleanup);
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,80 @@
/* 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/. */
let gTests = [
{
desc: "Dragging an item from the palette to another button in the panel should work.",
setup: startCustomizing,
run: function() {
let btn = document.getElementById("developer-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
let lastButtonIndex = placements.length - 1;
let lastButton = placements[lastButtonIndex];
let placementsAfterInsert = placements.slice(0, lastButtonIndex).concat(["developer-button", lastButton]);
let lastButtonNode = document.getElementById(lastButton);
simulateItemDrag(btn, lastButtonNode);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let palette = document.getElementById("customization-palette");
simulateItemDrag(btn, palette);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
},
},
{
desc: "Dragging an item from the palette to the panel itself should also work.",
setup: startCustomizing,
run: function() {
let btn = document.getElementById("developer-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
let placementsAfterAppend = placements.concat(["developer-button"]);
simulateItemDrag(btn, panel);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let palette = document.getElementById("customization-palette");
simulateItemDrag(btn, palette);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
},
},
{
desc: "Dragging an item from the palette to an empty panel should also work.",
setup: function() {
let widgetIds = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
while (widgetIds.length) {
CustomizableUI.removeWidgetFromArea(widgetIds.shift());
}
return startCustomizing()
},
run: function() {
let btn = document.getElementById("developer-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
assertAreaPlacements(panel.id, []);
let placementsAfterAppend = ["developer-button"];
simulateItemDrag(btn, panel);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let palette = document.getElementById("customization-palette");
simulateItemDrag(btn, palette);
assertAreaPlacements(panel.id, []);
},
}
];
function asyncCleanup() {
yield endCustomizing();
Services.prefs.clearUserPref("browser.uiCustomization.skipSourceNodeCheck");
yield resetCustomization();
}
function test() {
Services.prefs.setBoolPref("browser.uiCustomization.skipSourceNodeCheck", true);
waitForExplicitFinish();
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,424 @@
/* 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/. */
let gTests = [
{
desc: "Dragging the zoom controls to be before the print button " +
"should not move any controls.",
setup: startCustomizing,
run: function() {
let zoomControls = document.getElementById("zoom-controls");
let printButton = document.getElementById("print-button");
let placementsAfterMove = ["edit-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"zoom-controls",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
simulateItemDrag(zoomControls, printButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let newWindowButton = document.getElementById("new-window-button");
simulateItemDrag(zoomControls, newWindowButton);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
},
},
{
desc: "Dragging the zoom controls to be before the save button " +
"should not move any controls.",
setup: startCustomizing,
run: function() {
let zoomControls = document.getElementById("zoom-controls");
let savePageButton = document.getElementById("save-page-button");
let placementsAfterMove = ["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"];
simulateItemDrag(zoomControls, savePageButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
ok(CustomizableUI.inDefaultState, "Should be in default state.");
},
},
{
desc: "Dragging the zoom controls to be before the new-window " +
"button should not move any widgets.",
setup: startCustomizing,
run: function() {
let zoomControls = document.getElementById("zoom-controls");
let newWindowButton = document.getElementById("new-window-button");
let placementsAfterMove = ["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"];
simulateItemDrag(zoomControls, newWindowButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
},
},
{
desc: "Dragging the zoom controls to be before the history-panelmenu " +
"should move the zoom-controls in to the row higher than the " +
"history-panelmenu.",
setup: startCustomizing,
run: function() {
let zoomControls = document.getElementById("zoom-controls");
let historyPanelMenu = document.getElementById("history-panelmenu");
let placementsAfterMove = ["edit-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"zoom-controls",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
simulateItemDrag(zoomControls, historyPanelMenu);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let newWindowButton = document.getElementById("new-window-button");
simulateItemDrag(zoomControls, newWindowButton);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
},
},
{
desc: "Dragging the zoom controls to be before the preferences-button " +
"should move the zoom-controls in to the row higher than the " +
"preferences-button.",
setup: startCustomizing,
run: function() {
let zoomControls = document.getElementById("zoom-controls");
let preferencesButton = document.getElementById("preferences-button");
let placementsAfterMove = ["edit-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"zoom-controls",
"find-button",
"preferences-button",
"add-ons-button"];
simulateItemDrag(zoomControls, preferencesButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let newWindowButton = document.getElementById("new-window-button");
simulateItemDrag(zoomControls, newWindowButton);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
},
},
{
desc: "Dragging an item from the palette to before the zoom-controls " +
"should move it and two other buttons before the zoom controls.",
setup: startCustomizing,
run: function() {
let developerButton = document.getElementById("developer-button");
let zoomControls = document.getElementById("zoom-controls");
let placementsAfterInsert = ["edit-controls",
"developer-button",
"new-window-button",
"privatebrowsing-button",
"zoom-controls",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
simulateItemDrag(developerButton, zoomControls);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let palette = document.getElementById("customization-palette");
// Check that the palette items are re-wrapped correctly.
let feedWrapper = document.getElementById("wrapper-feed-button");
let feedButton = document.getElementById("feed-button");
is(feedButton.parentNode, feedWrapper,
"feed-button should be a child of wrapper-feed-button");
is(feedWrapper.getAttribute("place"), "palette",
"The feed-button wrapper should have it's place set to 'palette'");
simulateItemDrag(developerButton, palette);
is(developerButton.parentNode.tagName, "toolbarpaletteitem",
"The developer-button should be wrapped by a toolbarpaletteitem");
let newWindowButton = document.getElementById("new-window-button");
simulateItemDrag(zoomControls, newWindowButton);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
},
},
{
desc: "Dragging an item from the palette to before the edit-controls " +
"should move it and two other buttons before the edit and zoom controls.",
setup: startCustomizing,
run: function() {
let developerButton = document.getElementById("developer-button");
let editControls = document.getElementById("edit-controls");
let placementsAfterInsert = ["developer-button",
"new-window-button",
"privatebrowsing-button",
"edit-controls",
"zoom-controls",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
simulateItemDrag(developerButton, editControls);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let palette = document.getElementById("customization-palette");
// Check that the palette items are re-wrapped correctly.
let feedWrapper = document.getElementById("wrapper-feed-button");
let feedButton = document.getElementById("feed-button");
is(feedButton.parentNode, feedWrapper,
"feed-button should be a child of wrapper-feed-button");
is(feedWrapper.getAttribute("place"), "palette",
"The feed-button wrapper should have it's place set to 'palette'");
simulateItemDrag(developerButton, palette);
is(developerButton.parentNode.tagName, "toolbarpaletteitem",
"The developer-button should be wrapped by a toolbarpaletteitem");
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
},
},
{
desc: "Dragging the edit-controls to be before the zoom-controls button " +
"should not move any widgets.",
setup: startCustomizing,
run: function() {
let editControls = document.getElementById("edit-controls");
let zoomControls = document.getElementById("zoom-controls");
let placementsAfterMove = ["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"];
simulateItemDrag(editControls, zoomControls);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
},
},
{
desc: "Dragging the edit-controls to be before the new-window-button should " +
"move the zoom-controls before the edit-controls.",
setup: startCustomizing,
run: function() {
let editControls = document.getElementById("edit-controls");
let newWindowButton = document.getElementById("new-window-button");
let placementsAfterMove = ["zoom-controls",
"edit-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
simulateItemDrag(editControls, newWindowButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
let zoomControls = document.getElementById("zoom-controls");
simulateItemDrag(editControls, zoomControls);
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
},
},
{
desc: "Dragging the edit-controls to be before the privatebrowsing-button " +
"should move the edit-controls in to the row higher than the " +
"privatebrowsing-button.",
setup: startCustomizing,
run: function() {
let editControls = document.getElementById("edit-controls");
let privateBrowsingButton = document.getElementById("privatebrowsing-button");
let placementsAfterMove = ["zoom-controls",
"edit-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
simulateItemDrag(editControls, privateBrowsingButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
let zoomControls = document.getElementById("zoom-controls");
simulateItemDrag(editControls, zoomControls);
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
},
},
{
desc: "Dragging the edit-controls to be before the save-page-button " +
"should move the edit-controls in to the row higher than the " +
"save-page-button.",
setup: startCustomizing,
run: function() {
let editControls = document.getElementById("edit-controls");
let savePageButton = document.getElementById("save-page-button");
let placementsAfterMove = ["zoom-controls",
"edit-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
simulateItemDrag(editControls, savePageButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
let zoomControls = document.getElementById("zoom-controls");
simulateItemDrag(editControls, zoomControls);
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
},
},
{
desc: "Dragging the edit-controls to the panel itself should append " +
"the edit controls to the bottom of the panel.",
setup: startCustomizing,
run: function() {
let editControls = document.getElementById("edit-controls");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let placementsAfterMove = ["zoom-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button",
"edit-controls"];
simulateItemDrag(editControls, panel);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
let zoomControls = document.getElementById("zoom-controls");
simulateItemDrag(editControls, zoomControls);
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
},
},
{
desc: "Dragging the edit-controls to the customization-palette and " +
"back should work.",
setup: startCustomizing,
run: function() {
let editControls = document.getElementById("edit-controls");
let palette = document.getElementById("customization-palette");
let placementsAfterMove = ["zoom-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
let paletteChildElementCount = palette.childElementCount;
simulateItemDrag(editControls, palette);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
is(paletteChildElementCount + 1, palette.childElementCount,
"The palette should have a new child, congratulations!");
is(editControls.parentNode.id, "wrapper-edit-controls",
"The edit-controls should be properly wrapped.");
is(editControls.parentNode.getAttribute("place"), "palette",
"The edit-controls should have the place of 'palette'.");
let zoomControls = document.getElementById("zoom-controls");
simulateItemDrag(editControls, zoomControls);
is(paletteChildElementCount, palette.childElementCount,
"The palette child count should have returned to its prior value.");
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
},
},
{
desc: "Dragging the edit-controls to each of the panel placeholders " +
"should append the edit-controls to the bottom of the panel.",
setup: startCustomizing,
run: function() {
let editControls = document.getElementById("edit-controls");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
for (let i = 0; i < 3; i++) {
// NB: We can't just iterate over all of the placeholders
// because each drag-drop action recreates them.
let placeholder = panel.getElementsByClassName("panel-customization-placeholder")[i];
let placementsAfterMove = ["zoom-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button",
"edit-controls"];
simulateItemDrag(editControls, placeholder);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
let zoomControls = document.getElementById("zoom-controls");
simulateItemDrag(editControls, zoomControls);
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
}
},
},
{
desc: "Dragging the developer-button back on to itself should work.",
setup: startCustomizing,
run: function() {
let developerButton = document.getElementById("developer-button");
is(developerButton.parentNode.tagName, "toolbarpaletteitem",
"developer-button should be wrapped by a toolbarpaletteitem");
simulateItemDrag(developerButton, developerButton);
is(developerButton.parentNode.tagName, "toolbarpaletteitem",
"developer-button should be wrapped by a toolbarpaletteitem");
let editControls = document.getElementById("edit-controls");
is(editControls.parentNode.tagName, "toolbarpaletteitem",
"edit-controls should be wrapped by a toolbarpaletteitem");
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
},
},
];
function asyncCleanup() {
yield endCustomizing();
Services.prefs.clearUserPref("browser.uiCustomization.skipSourceNodeCheck");
yield resetCustomization();
}
function test() {
Services.prefs.setBoolPref("browser.uiCustomization.skipSourceNodeCheck", true);
waitForExplicitFinish();
requestLongerTimeout(5);
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,151 @@
/* 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/. */
const kWidgetId = "some-widget";
function assertWidgetExists(aWindow, aExists) {
if (aExists) {
ok(aWindow.document.getElementById(kWidgetId),
"Should have found test widget in the window");
} else {
is(aWindow.document.getElementById(kWidgetId), null,
"Should not have found test widget in the window");
}
}
let gTests = [
{
desc: "A widget that is created with showInPrivateBrowsing undefined should " +
"have that value default to false.",
run: function() {
let wrapper = CustomizableUI.createWidget({
id: kWidgetId
});
ok(wrapper.showInPrivateBrowsing,
"showInPrivateBrowsing should have defaulted to true.");
CustomizableUI.destroyWidget(kWidgetId);
},
},
{
desc: "Add a widget via the API with showInPrivateBrowsing set to false " +
"and ensure it does not appear in pre-existing or newly created " +
"private windows.",
run: function() {
let plain = yield openAndLoadWindow();
let private = yield openAndLoadWindow({private: true});
CustomizableUI.createWidget({
id: kWidgetId,
removable: true,
showInPrivateBrowsing: false
});
CustomizableUI.addWidgetToArea(kWidgetId,
CustomizableUI.AREA_NAVBAR);
assertWidgetExists(plain, true);
assertWidgetExists(private, false);
// Now open up some new windows. The widget should exist in the new
// plain window, but not the new private window.
let plain2 = yield openAndLoadWindow();
let private2 = yield openAndLoadWindow({private: true});
assertWidgetExists(plain2, true);
assertWidgetExists(private2, false);
// Try moving the widget around and make sure it doesn't get added
// to the private windows. We'll start by appending it to the tabstrip.
CustomizableUI.addWidgetToArea(kWidgetId,
CustomizableUI.AREA_TABSTRIP);
assertWidgetExists(plain, true);
assertWidgetExists(plain2, true);
assertWidgetExists(private, false);
assertWidgetExists(private2, false);
// And then move it to the beginning of the tabstrip.
CustomizableUI.moveWidgetWithinArea(kWidgetId, 0);
assertWidgetExists(plain, true);
assertWidgetExists(plain2, true);
assertWidgetExists(private, false);
assertWidgetExists(private2, false);
CustomizableUI.removeWidgetFromArea("some-widget");
assertWidgetExists(plain, false);
assertWidgetExists(plain2, false);
assertWidgetExists(private, false);
assertWidgetExists(private2, false);
plain.close();
plain2.close();
private.close();
private2.close();
CustomizableUI.destroyWidget("some-widget");
},
},
{
desc: "Add a widget via the API with showInPrivateBrowsing set to true, " +
"and ensure that it appears in pre-existing or newly created " +
"private browsing windows.",
run: function() {
let plain = yield openAndLoadWindow();
let private = yield openAndLoadWindow({private: true});
CustomizableUI.createWidget({
id: kWidgetId,
removable: true,
showInPrivateBrowsing: true
});
CustomizableUI.addWidgetToArea(kWidgetId,
CustomizableUI.AREA_NAVBAR);
assertWidgetExists(plain, true);
assertWidgetExists(private, true);
// Now open up some new windows. The widget should exist in the new
// plain window, but not the new private window.
let plain2 = yield openAndLoadWindow();
let private2 = yield openAndLoadWindow({private: true});
assertWidgetExists(plain2, true);
assertWidgetExists(private2, true);
// Try moving the widget around and make sure it doesn't get added
// to the private windows. We'll start by appending it to the tabstrip.
CustomizableUI.addWidgetToArea(kWidgetId,
CustomizableUI.AREA_TABSTRIP);
assertWidgetExists(plain, true);
assertWidgetExists(plain2, true);
assertWidgetExists(private, true);
assertWidgetExists(private2, true);
// And then move it to the beginning of the tabstrip.
CustomizableUI.moveWidgetWithinArea(kWidgetId, 0);
assertWidgetExists(plain, true);
assertWidgetExists(plain2, true);
assertWidgetExists(private, true);
assertWidgetExists(private2, true);
CustomizableUI.removeWidgetFromArea("some-widget");
assertWidgetExists(plain, false);
assertWidgetExists(plain2, false);
assertWidgetExists(private, false);
assertWidgetExists(private2, false);
plain.close();
plain2.close();
private.close();
private2.close();
CustomizableUI.destroyWidget("some-widget");
},
}
];
function asyncCleanup() {
yield resetCustomization();
}
function test() {
waitForExplicitFinish();
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,56 @@
/* 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/. */
const kButtonId = "test-886323-removable-moved-node";
const kLazyAreaId = "test-886323-lazy-area-for-removability-testing";
let gNavBar = document.getElementById(CustomizableUI.AREA_NAVBAR);
let gLazyArea;
let gTests = [
{
desc: "Removable nodes shouldn't be moved by buildArea",
setup: function() {
let dummyBtn = createDummyXULButton(kButtonId, "Dummy");
dummyBtn.setAttribute("removable", "true");
gNavBar.customizationTarget.appendChild(dummyBtn);
let popupSet = document.getElementById("mainPopupSet");
gLazyArea = document.createElementNS(kNSXUL, "panel");
gLazyArea.id = kLazyAreaId;
gLazyArea.setAttribute("hidden", "true");
popupSet.appendChild(gLazyArea);
CustomizableUI.registerArea(kLazyAreaId, {
type: CustomizableUI.TYPE_MENU_PANEL,
defaultPlacements: []
});
},
run: function() {
CustomizableUI.addWidgetToArea(kButtonId, kLazyAreaId);
assertAreaPlacements(kLazyAreaId, [kButtonId],
"Placements should have changed because widget is removable.");
let btn = document.getElementById(kButtonId);
btn.setAttribute("removable", "false");
gLazyArea.customizationTarget = gLazyArea;
CustomizableUI.registerToolbarNode(gLazyArea, []);
assertAreaPlacements(kLazyAreaId, [], "Placements should no longer include widget.");
is(btn.parentNode.id, gNavBar.customizationTarget.id,
"Button shouldn't actually have moved as it's not removable");
},
teardown: function() {
let btn = document.getElementById(kButtonId);
if (btn) btn.remove();
CustomizableUI.removeWidgetFromArea(kButtonId);
CustomizableUI.unregisterArea(kLazyAreaId);
gLazyArea.remove();
}
}
];
function asyncCleanup() {
yield resetCustomization();
}
function test() {
waitForExplicitFinish();
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,84 @@
/* 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/. */
let navbar = document.getElementById("nav-bar")
let navbarCT = navbar.customizationTarget;
let overflowPanelList = document.getElementById("widget-overflow-list");
let gTests = [
{
desc: "Reading currentset",
run: function() {
let nodeIds = [];
for (let node of navbarCT.childNodes) {
if (node.getAttribute("skipintoolbarset") != "true") {
nodeIds.push(node.id);
}
}
for (let node of overflowPanelList.childNodes) {
if (node.getAttribute("skipintoolbarset") != "true") {
nodeIds.push(node.id);
}
}
let currentSet = navbar.currentSet;
is(currentSet.split(',').length, nodeIds.length, "Should be just as many nodes as there are.");
is(currentSet, nodeIds.join(','), "Current set and node IDs should match.");
}
},
{
desc: "Insert, then remove items",
run: function() {
let currentSet = navbar.currentSet;
let newCurrentSet = currentSet.replace('home-button', 'feed-button,sync-button,home-button');
navbar.currentSet = newCurrentSet;
is(newCurrentSet, navbar.currentSet, "Current set should match expected current set.");
let feedBtn = document.getElementById("feed-button");
let syncBtn = document.getElementById("sync-button");
ok(feedBtn, "Feed button should have been added.");
ok(syncBtn, "Sync button should have been added.");
if (feedBtn && syncBtn) {
let feedParent = feedBtn.parentNode;
let syncParent = syncBtn.parentNode;
ok(feedParent == navbarCT || feedParent == overflowPanelList,
"Feed button should be in navbar or overflow");
ok(syncParent == navbarCT || syncParent == overflowPanelList,
"Feed button should be in navbar or overflow");
is(feedBtn.nextElementSibling, syncBtn, "Feed button should be next to sync button.");
let homeBtn = document.getElementById("home-button");
is(syncBtn.nextElementSibling, homeBtn, "Sync button should be next to home button.");
}
navbar.currentSet = currentSet;
is(currentSet, navbar.currentSet, "Should be able to remove the added items.");
}
},
{
desc: "Simultaneous insert/remove:",
run: function() {
let currentSet = navbar.currentSet;
let newCurrentSet = currentSet.replace('home-button', 'feed-button');
navbar.currentSet = newCurrentSet;
is(newCurrentSet, navbar.currentSet, "Current set should match expected current set.");
let feedBtn = document.getElementById("feed-button");
ok(feedBtn, "Feed button should have been added.");
let homeBtn = document.getElementById("home-button");
ok(!homeBtn, "Home button should have been removed.");
if (feedBtn) {
let feedParent = feedBtn.parentNode;
ok(feedParent == navbarCT || feedParent == overflowPanelList,
"Feed button should be in navbar or overflow");
}
navbar.currentSet = currentSet;
is(currentSet, navbar.currentSet, "Should be able to return to original state.");
}
}
];
function asyncCleanup() {
yield resetCustomization();
}
function test() {
waitForExplicitFinish();
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,65 @@
/* 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/. */
let gTests = [
{
desc: "Adding, moving and removing items should update the relevant currentset attributes",
setup: function() {
let personalbar = document.getElementById(CustomizableUI.AREA_BOOKMARKS);
setToolbarVisibility(personalbar, true);
},
run: function() {
ok(CustomizableUI.inDefaultState, "Should be in the default state when we start");
let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
let personalbar = document.getElementById(CustomizableUI.AREA_BOOKMARKS);
let navbarCurrentset = navbar.getAttribute("currentset") || navbar.currentSet;
let personalbarCurrentset = personalbar.getAttribute("currentset") || personalbar.currentSet;
let otherWin = yield openAndLoadWindow();
let otherNavbar = otherWin.document.getElementById(CustomizableUI.AREA_NAVBAR);
let otherPersonalbar = otherWin.document.getElementById(CustomizableUI.AREA_BOOKMARKS);
CustomizableUI.moveWidgetWithinArea("home-button", 0);
navbarCurrentset = "home-button," + navbarCurrentset.replace(",home-button", "");
is(navbar.getAttribute("currentset"), navbarCurrentset,
"Should have updated currentSet after move.");
is(otherNavbar.getAttribute("currentset"), navbarCurrentset,
"Should have updated other window's currentSet after move.");
CustomizableUI.addWidgetToArea("home-button", CustomizableUI.AREA_BOOKMARKS);
navbarCurrentset = navbarCurrentset.replace("home-button,", "");
personalbarCurrentset = personalbarCurrentset + ",home-button";
is(navbar.getAttribute("currentset"), navbarCurrentset,
"Should have updated navbar currentSet after implied remove.");
is(otherNavbar.getAttribute("currentset"), navbarCurrentset,
"Should have updated other window's navbar currentSet after implied remove.");
is(personalbar.getAttribute("currentset"), personalbarCurrentset,
"Should have updated personalbar currentSet after add.");
is(otherPersonalbar.getAttribute("currentset"), personalbarCurrentset,
"Should have updated other window's personalbar currentSet after add.");
CustomizableUI.removeWidgetFromArea("home-button");
personalbarCurrentset = personalbarCurrentset.replace(",home-button", "");
is(personalbar.getAttribute("currentset"), personalbarCurrentset,
"Should have updated currentSet after remove.");
is(otherPersonalbar.getAttribute("currentset"), personalbarCurrentset,
"Should have updated other window's currentSet after remove.");
otherWin.close();
// Reset in asyncCleanup will put our button back for us.
}
}
];
function asyncCleanup() {
let personalbar = document.getElementById(CustomizableUI.AREA_BOOKMARKS);
setToolbarVisibility(personalbar, false);
yield resetCustomization();
}
function test() {
waitForExplicitFinish();
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,135 @@
/* 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/. */
let gTests = [
{
desc: "One orphaned item should have two placeholders next to it.",
setup: startCustomizing,
run: function() {
let btn = document.getElementById("developer-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
let placementsAfterAppend = placements.concat(["developer-button"]);
simulateItemDrag(btn, panel);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
is(getVisiblePlaceholderCount(panel), 2, "Should only have 2 visible placeholders before exiting");
yield endCustomizing();
yield startCustomizing();
is(getVisiblePlaceholderCount(panel), 2, "Should only have 2 visible placeholders after re-entering");
let palette = document.getElementById("customization-palette");
simulateItemDrag(btn, palette);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
},
},
{
desc: "Two orphaned items should have one placeholder next to them (case 1).",
setup: startCustomizing,
run: function() {
let btn = document.getElementById("developer-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
let placementsAfterAppend = placements.concat(["developer-button", "sync-button"]);
simulateItemDrag(btn, panel);
btn = document.getElementById("sync-button");
simulateItemDrag(btn, panel);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholders before exiting");
yield endCustomizing();
yield startCustomizing();
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholders after re-entering");
let palette = document.getElementById("customization-palette");
simulateItemDrag(btn, palette);
btn = document.getElementById("developer-button");
simulateItemDrag(btn, palette);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
},
},
{
desc: "Two orphaned items should have one placeholder next to them (case 2).",
setup: startCustomizing,
run: function() {
let btn = document.getElementById("add-ons-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let palette = document.getElementById("customization-palette");
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
let placementsAfterAppend = placements.filter(p => p != btn.id);
simulateItemDrag(btn, palette);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholders before exiting");
yield endCustomizing();
yield startCustomizing();
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholders after re-entering");
simulateItemDrag(btn, panel);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placements);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
},
},
{
desc: "A wide widget at the bottom of the panel should have three placeholders after it.",
setup: startCustomizing,
run: function() {
let btn = document.getElementById("edit-controls");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
let placementsAfterAppend = placements.concat([placements.shift()]);
simulateItemDrag(btn, panel);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
is(getVisiblePlaceholderCount(panel), 3, "Should have 3 visible placeholders before exiting");
yield endCustomizing();
yield startCustomizing();
is(getVisiblePlaceholderCount(panel), 3, "Should have 3 visible placeholders after re-entering");
let zoomControls = document.getElementById("zoom-controls");
simulateItemDrag(btn, zoomControls);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
},
},
{
desc: "The default placements should have three placeholders at the bottom.",
setup: startCustomizing,
run: function() {
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
ok(CustomizableUI.inDefaultState, "Should be in default state.");
is(getVisiblePlaceholderCount(panel), 3, "Should have 3 visible placeholders before exiting");
yield endCustomizing();
yield startCustomizing();
is(getVisiblePlaceholderCount(panel), 3, "Should have 3 visible placeholders after re-entering");
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
},
},
];
function asyncCleanup() {
yield endCustomizing();
Services.prefs.clearUserPref("browser.uiCustomization.skipSourceNodeCheck");
yield resetCustomization();
}
function test() {
Services.prefs.setBoolPref("browser.uiCustomization.skipSourceNodeCheck", true);
waitForExplicitFinish();
runTests(gTests, asyncCleanup);
}
function getVisiblePlaceholderCount(aPanel) {
let visiblePlaceholders = aPanel.querySelectorAll(".panel-customization-placeholder:not([hidden=true])");
return visiblePlaceholders.length;
}

View File

@ -0,0 +1,75 @@
/* 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/. */
const kLazyAreaId = "test-890262-lazy-area";
const kWidget1Id = "test-890262-widget1";
const kWidget2Id = "test-890262-widget2";
let gTests = [
{
desc: "Destroying a widget after defaulting it to a non-legacy area should work.",
run: function() {
CustomizableUI.createWidget({
id: kWidget1Id,
removable: true,
defaultArea: kLazyAreaId
});
let noError = true;
try {
CustomizableUI.destroyWidget(kWidget1Id);
} catch (ex) {
Cu.reportError(ex);
noError = false;
}
ok(noError, "Shouldn't throw an exception for a widget that was created in a not-yet-constructed area");
}
},
{
desc: "Destroying a widget after moving it to a non-legacy area should work.",
run: function() {
CustomizableUI.createWidget({
id: kWidget2Id,
removable: true,
defaultArea: CustomizableUI.AREA_NAVBAR
});
CustomizableUI.addWidgetToArea(kWidget2Id, kLazyAreaId);
let noError = true;
try {
CustomizableUI.destroyWidget(kWidget2Id);
} catch (ex) {
Cu.reportError(ex);
noError = false;
}
ok(noError, "Shouldn't throw an exception for a widget that was added to a not-yet-constructed area");
}
}
];
function asyncCleanup() {
let lazyArea = document.getElementById(kLazyAreaId);
if (lazyArea) {
lazyArea.remove();
}
try {
CustomizableUI.unregisterArea(kLazyAreaId);
} catch (ex) {} // If we didn't register successfully for some reason
yield resetCustomization();
}
function setupArea() {
let lazyArea = document.createElementNS(kNSXUL, "hbox");
lazyArea.id = kLazyAreaId;
document.getElementById("nav-bar").appendChild(lazyArea);
CustomizableUI.registerArea(kLazyAreaId, {
type: CustomizableUI.TYPE_TOOLBAR,
defaultPlacements: []
});
}
function test() {
waitForExplicitFinish();
setupArea();
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,37 @@
/* 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/. */
const kWidgetId = "test-892955-remove-widget";
let gTests = [
{
desc: "Removing a destroyed widget should work.",
run: function() {
let widgetSpec = {
id: kWidgetId,
defaultArea: CustomizableUI.AREA_NAVBAR
};
CustomizableUI.createWidget(widgetSpec);
CustomizableUI.destroyWidget(kWidgetId);
let noError = true;
try {
CustomizableUI.removeWidgetFromArea(kWidgetId);
} catch (ex) {
noError = false;
Cu.reportError(ex);
}
ok(noError, "Shouldn't throw an error removing a destroyed widget.");
}
}
];
function asyncCleanup() {
yield resetCustomization();
}
function test() {
waitForExplicitFinish();
runTests(gTests, asyncCleanup);
}

View File

@ -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/. */
const kWidgetId = "test-892956-destroyWidget-defaultPlacement";
let gTests = [
{
desc: "destroyWidget should clean up defaultPlacements if the widget had a defaultArea",
run: function() {
ok(CustomizableUI.inDefaultState, "Should be in the default state when we start");
let widgetSpec = {
id: kWidgetId,
defaultArea: CustomizableUI.AREA_NAVBAR
};
CustomizableUI.createWidget(widgetSpec);
CustomizableUI.destroyWidget(kWidgetId);
ok(CustomizableUI.inDefaultState, "Should be in the default state when we finish");
}
}
];
function asyncCleanup() {
yield resetCustomization();
}
function test() {
waitForExplicitFinish();
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,38 @@
/* 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/. */
let gTests = [
{
desc: "Resize to a small window, open a new window, check that new window handles overflow properly",
run: function() {
let originalWindowWidth = window.outerWidth;
let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
let oldChildCount = navbar.customizationTarget.childElementCount;
window.resizeTo(400, window.outerHeight);
yield waitForCondition(() => navbar.hasAttribute("overflowing"));
ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
ok(navbar.customizationTarget.childElementCount < oldChildCount, "Should have fewer children.");
let newWindow = yield openAndLoadWindow();
let otherNavBar = newWindow.document.getElementById(CustomizableUI.AREA_NAVBAR);
yield waitForCondition(() => otherNavBar.hasAttribute("overflowing"));
ok(otherNavBar.hasAttribute("overflowing"), "Other window should have an overflowing toolbar.");
newWindow.close();
window.resizeTo(originalWindowWidth, window.outerHeight);
yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
ok(!navbar.hasAttribute("overflowing"), "Should no longer have an overflowing toolbar.");
}
}
];
function asyncCleanup() {
yield resetCustomization();
}
function test() {
waitForExplicitFinish();
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,62 @@
/* 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/. */
let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
let gTests = [
{
desc: "Resize to a small window, resize back, shouldn't affect currentSet",
run: function() {
let originalWindowWidth = window.outerWidth;
let oldCurrentSet = navbar.currentSet;
ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
ok(CustomizableUI.inDefaultState, "Should start in default state.");
let oldChildCount = navbar.customizationTarget.childElementCount;
window.resizeTo(400, window.outerHeight);
yield waitForCondition(() => navbar.hasAttribute("overflowing"));
ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
is(navbar.currentSet, oldCurrentSet, "Currentset should be the same when overflowing.");
ok(CustomizableUI.inDefaultState, "Should still be in default state when overflowing.");
ok(navbar.customizationTarget.childElementCount < oldChildCount, "Should have fewer children.");
window.resizeTo(originalWindowWidth, window.outerHeight);
yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
ok(!navbar.hasAttribute("overflowing"), "Should no longer have an overflowing toolbar.");
is(navbar.currentSet, oldCurrentSet, "Currentset should still be the same now we're no longer overflowing.");
ok(CustomizableUI.inDefaultState, "Should still be in default state now we're no longer overflowing.");
// Verify actual physical placements match those of the placement array:
let placementCounter = 0;
let placements = CustomizableUI.getWidgetIdsInArea(CustomizableUI.AREA_NAVBAR);
for (let node of navbar.customizationTarget.childNodes) {
if (node.getAttribute("skipintoolbarset") == "true") {
continue;
}
is(placements[placementCounter++], node.id, "Nodes should match after overflow");
}
is(placements.length, placementCounter, "Should have as many nodes as expected");
is(navbar.customizationTarget.childElementCount, oldChildCount, "Number of nodes should match");
}
},
{
desc: "Enter and exit customization mode, check that currentSet works",
run: function() {
let oldCurrentSet = navbar.currentSet;
ok(CustomizableUI.inDefaultState, "Should start in default state.");
yield startCustomizing();
ok(CustomizableUI.inDefaultState, "Should be in default state in customization mode.");
is(navbar.currentSet, oldCurrentSet, "Currentset should be the same in customization mode.");
yield endCustomizing();
ok(CustomizableUI.inDefaultState, "Should be in default state after customization mode.");
is(navbar.currentSet, oldCurrentSet, "Currentset should be the same after customization mode.");
}
}
];
function asyncCleanup() {
yield resetCustomization();
}
function test() {
waitForExplicitFinish();
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,171 @@
/* 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/. */
let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
let overflowList = document.getElementById(navbar.getAttribute("overflowtarget"));
const kTestBtn1 = "test-addWidgetToArea-overflow";
const kTestBtn2 = "test-removeWidgetFromArea-overflow";
const kHomeBtn = "home-button";
const kDownloadsBtn = "downloads-button";
const kSearchBox = "search-container";
const kStarBtn = "bookmarks-menu-button";
let originalWindowWidth;
let gTests = [
{
desc: "Adding a widget should add it next to the widget it's being inserted next to.",
setup: function() {
originalWindowWidth = window.outerWidth;
createDummyXULButton(kTestBtn1, "Test");
},
run: function() {
ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
ok(CustomizableUI.inDefaultState, "Should start in default state.");
window.resizeTo(400, window.outerHeight);
yield waitForCondition(() => navbar.hasAttribute("overflowing"));
ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
ok(!navbar.querySelector("#" + kHomeBtn), "Home button should no longer be in the navbar");
ok(overflowList.querySelector("#" + kHomeBtn), "Home button should be overflowing");
let placementOfHomeButton = CustomizableUI.getWidgetIdsInArea(navbar.id).indexOf(kHomeBtn);
CustomizableUI.addWidgetToArea(kTestBtn1, navbar.id, placementOfHomeButton);
ok(!navbar.querySelector("#" + kTestBtn1), "New button should not be in the navbar");
ok(overflowList.querySelector("#" + kTestBtn1), "New button should be overflowing");
let nextEl = document.getElementById(kTestBtn1).nextSibling;
is(nextEl && nextEl.id, kHomeBtn, "Test button should be next to home button.");
window.resizeTo(originalWindowWidth, window.outerHeight);
yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
ok(!navbar.hasAttribute("overflowing"), "Should not have an overflowing toolbar.");
ok(navbar.querySelector("#" + kHomeBtn), "Home button should be in the navbar");
ok(!overflowList.querySelector("#" + kHomeBtn), "Home button should no longer be overflowing");
ok(navbar.querySelector("#" + kTestBtn1), "Test button should be in the navbar");
ok(!overflowList.querySelector("#" + kTestBtn1), "Test button should no longer be overflowing");
},
teardown: function() {
let el = document.getElementById(kTestBtn1);
if (el) {
CustomizableUI.removeWidgetFromArea(kTestBtn1);
el.remove();
}
window.resizeTo(originalWindowWidth, window.outerHeight);
}
},
{
desc: "Removing a widget from the toolbar should try to move items back.",
setup: function() {
// This is pretty weird. We're going to try to move only the home button into the overlay:
let downloadsBtn = document.getElementById(kDownloadsBtn);
// Guarantee overflow of too much stuff:
window.resizeTo(700, window.outerHeight);
let inc = 15;
while (window.outerWidth < originalWindowWidth &&
downloadsBtn.parentNode != navbar.customizationTarget) {
window.resizeTo(window.outerWidth + inc, window.outerHeight);
yield waitFor(500);
}
},
run: function() {
ok(overflowList.querySelector("#home-button"), "Home button should be overflowing");
CustomizableUI.removeWidgetFromArea("downloads-button");
is(document.getElementById("home-button").parentNode, navbar.customizationTarget, "Home button should move back.");
ok(!navbar.hasAttribute("overflowing"), "Navbar is no longer overflowing");
},
teardown: function() {
window.resizeTo(originalWindowWidth, window.outerHeight);
yield waitForCondition(function() !navbar.hasAttribute("overflowing"));
CustomizableUI.reset();
}
},
{
desc: "Removing a widget should remove it from the overflow list if that is where it is, and update it accordingly.",
setup: function() {
createDummyXULButton(kTestBtn2, "Test");
},
run: function() {
ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
ok(CustomizableUI.inDefaultState, "Should start in default state.");
CustomizableUI.addWidgetToArea(kTestBtn2, navbar.id);
ok(!navbar.hasAttribute("overflowing"), "Should still have a non-overflowing toolbar.");
window.resizeTo(400, window.outerHeight);
yield waitForCondition(() => navbar.hasAttribute("overflowing"));
ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
ok(!navbar.querySelector("#" + kTestBtn2), "Test button should not be in the navbar");
ok(overflowList.querySelector("#" + kTestBtn2), "Test button should be overflowing");
CustomizableUI.removeWidgetFromArea(kTestBtn2);
ok(!overflowList.querySelector("#" + kTestBtn2), "Test button should not be overflowing.");
ok(!navbar.querySelector("#" + kTestBtn2), "Test button should not be in the navbar");
ok(gNavToolbox.palette.querySelector("#" + kTestBtn2), "Test button should be in the palette");
window.resizeTo(originalWindowWidth, window.outerHeight);
yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
ok(!navbar.hasAttribute("overflowing"), "Should not have an overflowing toolbar.");
},
teardown: function() {
let el = document.getElementById(kTestBtn2);
if (el) {
CustomizableUI.removeWidgetFromArea(kTestBtn2);
el.remove();
}
window.resizeTo(originalWindowWidth, window.outerHeight);
}
},
{
desc: "Overflow everything that can, then reorganize that list",
setup: function() {
ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
ok(CustomizableUI.inDefaultState, "Should start in default state.");
window.resizeTo(480, window.outerHeight);
yield waitForCondition(() => navbar.hasAttribute("overflowing"));
ok(!navbar.querySelector("#" + kSearchBox), "Search container should be overflowing");
},
run: function() {
let placements = CustomizableUI.getWidgetIdsInArea(navbar.id);
let searchboxPlacement = placements.indexOf(kSearchBox);
CustomizableUI.moveWidgetWithinArea(kHomeBtn, searchboxPlacement);
yield waitForCondition(() => navbar.querySelector("#" + kHomeBtn));
ok(navbar.querySelector("#" + kHomeBtn), "Home button should have moved back");
let inc = 15;
window.resizeTo(640, window.outerHeight);
while (window.outerWidth < originalWindowWidth &&
!navbar.querySelector("#" + kSearchBox)) {
window.resizeTo(window.outerWidth + inc, window.outerHeight);
yield waitFor(500);
}
ok(!navbar.querySelector("#" + kStarBtn), "Star button should still be overflowed");
CustomizableUI.moveWidgetWithinArea(kStarBtn);
let starButtonOverflowed = overflowList.querySelector("#" + kStarBtn);
ok(starButtonOverflowed && !starButtonOverflowed.nextSibling, "Star button should be last item");
window.resizeTo(window.outerWidth + 15, window.outerHeight);
yield waitForCondition(() => navbar.querySelector("#" + kDownloadsBtn) && navbar.hasAttribute("overflowing"));
ok(navbar.hasAttribute("overflowing"), "navbar should still be overflowing");
CustomizableUI.moveWidgetWithinArea(kHomeBtn);
let homeButtonOverflowed = overflowList.querySelector("#" + kHomeBtn);
ok(homeButtonOverflowed, "Home button should be in overflow list");
ok(navbar.hasAttribute("overflowing"), "navbar should still be overflowing");
ok(homeButtonOverflowed && !homeButtonOverflowed.nextSibling, "Home button should be last item");
},
teardown: function() {
window.resizeTo(originalWindowWidth, window.outerHeight);
yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
ok(!navbar.hasAttribute("overflowing"), "Should not have an overflowing toolbar.");
}
}
];
function asyncCleanup() {
yield resetCustomization();
}
function test() {
waitForExplicitFinish();
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,26 @@
/* 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/. */
let gTests = [
{
desc: "Entering then exiting customization mode should reenable the Help and Exit buttons.",
run: function() {
yield startCustomizing();
let helpButton = document.getElementById("PanelUI-help");
let quitButton = document.getElementById("PanelUI-quit");
ok(helpButton.getAttribute("disabled") == "true", "Help button should be disabled while in customization mode.");
ok(quitButton.getAttribute("disabled") == "true", "Quit button should be disabled while in customization mode.");
yield endCustomizing();
ok(!helpButton.hasAttribute("disabled"), "Help button should not be disabled.");
ok(!quitButton.hasAttribute("disabled"), "Quit button should not be disabled.");
}
},
];
function test() {
waitForExplicitFinish();
runTests(gTests);
}

View File

@ -0,0 +1,46 @@
/* 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/. */
let navbar;
let skippedItem;
let gTests = [
{
desc: "Attempting to drag a skipintoolbarset item should work.",
setup: function() {
navbar = document.getElementById("nav-bar");
skippedItem = document.createElement("toolbarbutton");
skippedItem.id = "test-skipintoolbarset-item";
skippedItem.setAttribute("label", "Test");
skippedItem.setAttribute("skipintoolbarset", "true");
navbar.customizationTarget.appendChild(skippedItem);
},
run: function() {
let downloadsButton = document.getElementById("downloads-button");
yield startCustomizing();
ok(CustomizableUI.inDefaultState, "Should still be in default state");
simulateItemDrag(skippedItem, downloadsButton);
ok(CustomizableUI.inDefaultState, "Should still be in default state");
let skippedItemWrapper = skippedItem.parentNode;
is(skippedItemWrapper.nextSibling && skippedItemWrapper.nextSibling.id,
downloadsButton.parentNode.id, "Should be next to downloads button");
simulateItemDrag(downloadsButton, skippedItem);
let downloadWrapper = downloadsButton.parentNode;
is(downloadWrapper.nextSibling && downloadWrapper.nextSibling.id,
skippedItem.parentNode.id, "Should be next to skipintoolbarset item");
ok(CustomizableUI.inDefaultState, "Should still be in default state");
}
},
];
function asyncCleanup() {
yield endCustomizing();
skippedItem.remove();
Services.prefs.clearUserPref("browser.uiCustomization.skipSourceNodeCheck");
yield resetCustomization();
}
function test() {
Services.prefs.setBoolPref("browser.uiCustomization.skipSourceNodeCheck", true);
waitForExplicitFinish();
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,38 @@
/* 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/. */
let gTests = [
{
desc: "Customize mode reset button should revert correctly",
setup: function() {
yield startCustomizing();
},
run: function() {
let devButton = document.getElementById("developer-button");
let downloadsButton = document.getElementById("downloads-button");
let searchBox = document.getElementById("search-container");
let palette = document.getElementById("customization-palette");
ok(devButton && downloadsButton && searchBox && palette, "Stuff should exist");
simulateItemDrag(devButton, downloadsButton);
simulateItemDrag(searchBox, palette);
gCustomizeMode.reset();
yield waitForCondition(function() !gCustomizeMode.resetting);
ok(CustomizableUI.inDefaultState, "Should be back in default state");
},
teardown: function() {
yield endCustomizing();
}
}
];
function asyncCleanup() {
Services.prefs.clearUserPref("browser.uiCustomization.skipSourceNodeCheck");
yield resetCustomization();
}
function test() {
Services.prefs.setBoolPref("browser.uiCustomization.skipSourceNodeCheck", true);
waitForExplicitFinish();
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,37 @@
/* 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/. */
const kTestToolbarId = "test-empty-drag";
let gTests = [
{
desc: "Attempting to drag an item to an empty container should work.",
setup: function() {
createToolbarWithPlacements(kTestToolbarId, "");
},
run: function() {
yield startCustomizing();
let downloadButton = document.getElementById("downloads-button");
let customToolbar = document.getElementById(kTestToolbarId);
simulateItemDrag(downloadButton, customToolbar);
assertAreaPlacements(kTestToolbarId, ["downloads-button"]);
ok(downloadButton.parentNode && downloadButton.parentNode.parentNode == customToolbar,
"Button should really be in toolbar");
},
teardown: function() {
yield endCustomizing();
removeCustomToolbars();
}
},
];
function asyncCleanup() {
yield endCustomizing();
Services.prefs.clearUserPref("browser.uiCustomization.skipSourceNodeCheck");
yield resetCustomization();
}
function test() {
Services.prefs.setBoolPref("browser.uiCustomization.skipSourceNodeCheck", true);
waitForExplicitFinish();
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,32 @@
/* 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/. */
let gTests = [
{
desc: "Attempting to drag the menubar to the navbar shouldn't work.",
setup: startCustomizing,
run: function() {
let menuItems = document.getElementById("menubar-items");
let navbar = document.getElementById("nav-bar");
let menubar = document.getElementById("toolbar-menubar");
simulateItemDrag(menuItems, navbar.customizationTarget);
is(getAreaWidgetIds("nav-bar").indexOf("menubar-items"), -1, "Menu bar shouldn't be in the navbar.");
ok(!navbar.querySelector("#menubar-items"), "Shouldn't find menubar items in the navbar.");
ok(menubar.querySelector("#menubar-items"), "Should find menubar items in the menubar.");
isnot(getAreaWidgetIds("toolbar-menubar").indexOf("menubar-items"), -1, "Menubar items shouldn't be missing from the navbar.");
},
teardown: endCustomizing
},
];
function asyncCleanup() {
yield endCustomizing();
Services.prefs.clearUserPref("browser.uiCustomization.skipSourceNodeCheck");
yield resetCustomization();
}
function test() {
Services.prefs.setBoolPref("browser.uiCustomization.skipSourceNodeCheck", true);
waitForExplicitFinish();
runTests(gTests, asyncCleanup);
}

View File

@ -0,0 +1,232 @@
/* 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/. */
// Avoid leaks by using tmp for imports...
let tmp = {};
Cu.import("resource://gre/modules/Promise.jsm", tmp);
Cu.import("resource://gre/modules/Task.jsm", tmp);
Cu.import("resource:///modules/CustomizableUI.jsm", tmp);
let {Promise, Task, CustomizableUI} = tmp;
let ChromeUtils = {};
let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js", ChromeUtils);
let {synthesizeDragStart, synthesizeDrop} = ChromeUtils;
const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
function createDummyXULButton(id, label) {
let btn = document.createElementNS(kNSXUL, "toolbarbutton");
btn.id = id;
btn.setAttribute("label", label || id);
btn.className = "toolbarbutton-1 chromeclass-toolbar-additional";
window.gNavToolbox.palette.appendChild(btn);
return btn;
}
let gAddedToolbars = new Set();
function createToolbarWithPlacements(id, placements) {
gAddedToolbars.add(id);
let tb = document.createElementNS(kNSXUL, "toolbar");
tb.id = id;
tb.setAttribute("customizable", "true");
CustomizableUI.registerArea(id, {
type: CustomizableUI.TYPE_TOOLBAR,
defaultPlacements: placements
});
gNavToolbox.appendChild(tb);
}
function removeCustomToolbars() {
CustomizableUI.reset();
for (let toolbarId of gAddedToolbars) {
CustomizableUI.unregisterArea(toolbarId);
document.getElementById(toolbarId).remove();
}
gAddedToolbars.clear();
}
function resetCustomization() {
return CustomizableUI.reset();
}
function assertAreaPlacements(areaId, expectedPlacements) {
let actualPlacements = getAreaWidgetIds(areaId);
is(actualPlacements.length, expectedPlacements.length,
"Area " + areaId + " should have " + expectedPlacements.length + " items.");
let minItems = Math.min(expectedPlacements.length, actualPlacements.length);
for (let i = 0; i < minItems; i++) {
if (typeof expectedPlacements[i] == "string") {
is(actualPlacements[i], expectedPlacements[i],
"Item " + i + " in " + areaId + " should match expectations.");
} else if (expectedPlacements[i] instanceof RegExp) {
ok(expectedPlacements[i].test(actualPlacements[i]),
"Item " + i + " (" + actualPlacements[i] + ") in " +
areaId + " should match " + expectedPlacements[i]);
} else {
ok(false, "Unknown type of expected placement passed to " +
" assertAreaPlacements. Is your test broken?");
}
}
}
function todoAssertAreaPlacements(areaId, expectedPlacements) {
let actualPlacements = getAreaWidgetIds(areaId);
let isPassing = actualPlacements.length == expectedPlacements.length;
let minItems = Math.min(expectedPlacements.length, actualPlacements.length);
for (let i = 0; i < minItems; i++) {
if (typeof expectedPlacements[i] == "string") {
isPassing = isPassing && actualPlacements[i] == expectedPlacements[i];
} else if (expectedPlacements[i] instanceof RegExp) {
isPassing = isPassing && expectedPlacements[i].test(actualPlacements[i]);
} else {
ok(false, "Unknown type of expected placement passed to " +
" assertAreaPlacements. Is your test broken?");
}
}
todo(isPassing, "The area placements for " + areaId +
" should equal the expected placements.")
}
function getAreaWidgetIds(areaId) {
return CustomizableUI.getWidgetIdsInArea(areaId);
}
function simulateItemDrag(toDrag, target) {
let docId = toDrag.ownerDocument.documentElement.id;
let dragData = [[{type: 'text/toolbarwrapper-id/' + docId,
data: toDrag.id}]];
synthesizeDragStart(toDrag.parentNode, dragData);
synthesizeDrop(target, target, dragData);
}
function endCustomizing() {
if (document.documentElement.getAttribute("customizing") != "true") {
return true;
}
let deferredEndCustomizing = Promise.defer();
function onCustomizationEnds() {
window.gNavToolbox.removeEventListener("aftercustomization", onCustomizationEnds);
deferredEndCustomizing.resolve();
}
window.gNavToolbox.addEventListener("aftercustomization", onCustomizationEnds);
window.gCustomizeMode.exit();
return deferredEndCustomizing.promise.then(function() {
let deferredLoadNewTab = Promise.defer();
//XXXgijs so some tests depend on this tab being about:blank. Make it so.
let newTabBrowser = window.gBrowser.selectedBrowser;
newTabBrowser.stop();
// If we stop early enough, this might actually be about:blank.
if (newTabBrowser.contentDocument.location.href == "about:blank") {
return;
}
// Otherwise, make it be about:blank, and wait for that to be done.
function onNewTabLoaded(e) {
newTabBrowser.removeEventListener("load", onNewTabLoaded, true);
deferredLoadNewTab.resolve();
}
newTabBrowser.addEventListener("load", onNewTabLoaded, true);
newTabBrowser.contentDocument.location.replace("about:blank");
return deferredLoadNewTab.promise;
});
}
function startCustomizing() {
if (document.documentElement.getAttribute("customizing") == "true") {
return;
}
let deferred = Promise.defer();
function onCustomizing() {
window.gNavToolbox.removeEventListener("customizationready", onCustomizing);
deferred.resolve();
}
window.gNavToolbox.addEventListener("customizationready", onCustomizing);
window.gCustomizeMode.enter();
return deferred.promise;
}
function openAndLoadWindow(aOptions, aWaitForDelayedStartup=false) {
let deferred = Promise.defer();
let win = OpenBrowserWindow(aOptions);
if (aWaitForDelayedStartup) {
Services.obs.addObserver(function onDS(aSubject, aTopic, aData) {
if (aSubject != win) {
return;
}
Services.obs.removeObserver(onDS, "browser-delayed-startup-finished");
deferred.resolve(win);
}, "browser-delayed-startup-finished", false);
} else {
win.addEventListener("load", function onLoad() {
win.removeEventListener("load", onLoad);
deferred.resolve(win);
});
}
return deferred.promise;
}
function waitForCondition(aConditionFn, aMaxTries=50, aCheckInterval=100) {
function tryNow() {
tries++;
if (aConditionFn()) {
deferred.resolve();
} else if (tries < aMaxTries) {
tryAgain();
} else {
deferred.reject("Condition timed out: " + aConditionFn.toSource());
}
}
function tryAgain() {
setTimeout(tryNow, aCheckInterval);
}
let deferred = Promise.defer();
let tries = 0;
tryAgain();
return deferred.promise;
}
function waitFor(aTimeout=100) {
let deferred = Promise.defer();
setTimeout(function() deferred.resolve(), aTimeout);
return deferred.promise;
}
function testRunner(testAry, asyncCleanup) {
Services.prefs.setBoolPref("browser.uiCustomization.disableAnimation", true);
for (let test of testAry) {
info(test.desc);
if (test.setup)
yield test.setup();
info("Running test");
yield test.run();
info("Cleanup");
if (test.teardown)
yield test.teardown();
ok(!document.getElementById(CustomizableUI.AREA_NAVBAR).hasAttribute("overflowing"), "Shouldn't overflow");
}
if (asyncCleanup) {
yield asyncCleanup();
}
ok(CustomizableUI.inDefaultState, "Should remain in default state");
Services.prefs.clearUserPref("browser.uiCustomization.disableAnimation");
}
function runTests(testAry, asyncCleanup) {
Task.spawn(testRunner(gTests, asyncCleanup)).then(finish, ex => {
// The stack of ok() here is misleading due to Promises. The stack of the
// actual exception is likely much more valuable, hence concatentating it.
ok(false, "Unexpected exception: " + ex + " With stack: " + ex.stack);
finish();
}).then(null, Cu.reportError);
}

View File

@ -641,11 +641,6 @@ const DownloadsOverlayLoader = {
this._overlayLoading = false;
this._loadedOverlays[aOverlay] = true;
// Loading the overlay causes all the persisted XUL attributes to be
// reapplied, including "iconsize" on the toolbars. Until bug 640158 is
// fixed, we must recalculate the correct "iconsize" attributes manually.
retrieveToolbarIconsizesFromTheme();
this.processPendingRequests();
}

View File

@ -110,10 +110,12 @@ const DownloadsButton = {
indicator.open = this._anchorRequested;
// Determine if we're located on an invisible toolbar.
if (!isElementVisible(indicator.parentNode)) {
return null;
}
let widget = CustomizableUI.getWidget("downloads-button")
.forWindow(window);
// Determine if the indicator is located on an invisible toolbar.
if (!isElementVisible(indicator.parentNode) && !widget.overflowed) {
return null;
}
return DownloadsIndicatorView.indicatorAnchor;
},
@ -317,10 +319,20 @@ const DownloadsIndicatorView = {
return;
}
// If the anchor is not there or its container is hidden, don't show
// a notification
let anchor = DownloadsButton._placeholder;
let widgetGroup = CustomizableUI.getWidget("downloads-button");
let widgetInWindow = widgetGroup.forWindow(window);
if (widgetInWindow.overflowed || widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
if (anchor && isElementVisible(anchor.parentNode)) {
// If the panel is open, don't do anything:
return;
}
// Otherwise, try to use the anchor of the panel:
anchor = widgetInWindow.anchor;
}
if (!anchor || !isElementVisible(anchor.parentNode)) {
// Our container isn't visible, so can't show the animation:
return;
}
@ -481,7 +493,14 @@ const DownloadsIndicatorView = {
onCommand: function DIV_onCommand(aEvent)
{
DownloadsPanel.showPanel();
// If the downloads button is in the menu panel, open the Library
let widgetGroup = CustomizableUI.getWidget("downloads-button");
if (widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
DownloadsPanel.showDownloadsHistory();
} else {
DownloadsPanel.showPanel();
}
aEvent.stopPropagation();
},
@ -512,7 +531,6 @@ const DownloadsIndicatorView = {
},
_indicator: null,
_indicatorAnchor: null,
__indicatorCounter: null,
__indicatorProgress: null,
@ -536,8 +554,12 @@ const DownloadsIndicatorView = {
get indicatorAnchor()
{
return this._indicatorAnchor ||
(this._indicatorAnchor = document.getElementById("downloads-indicator-anchor"));
let widget = CustomizableUI.getWidget("downloads-button")
.forWindow(window);
if (widget.overflowed) {
return widget.anchor;
}
return document.getElementById("downloads-indicator-anchor");
},
get _indicatorCounter()
@ -560,7 +582,6 @@ const DownloadsIndicatorView = {
_onCustomizedAway: function() {
this._indicator = null;
this._indicatorAnchor = null;
this.__indicatorCounter = null;
this.__indicatorProgress = null;
},

View File

@ -3,3 +3,4 @@ support-files = head.js
[browser_basic_functionality.js]
[browser_first_download_panel.js]
[browser_overflow_anchor.js]

View File

@ -0,0 +1,115 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure the downloads button and indicator overflows into the nav-bar
* chevron properly, and then when those buttons are clicked in the overflow
* panel that the downloads panel anchors to the chevron.
*/
function test_task() {
try {
// Ensure that state is reset in case previous tests didn't finish.
yield task_resetState();
// Record the original width of the window so we can put it back when
// this test finishes.
let oldWidth = window.outerWidth;
// The downloads button should not be overflowed to begin with.
let button = CustomizableUI.getWidget("downloads-button")
.forWindow(window);
ok(!button.overflowed, "Downloads button should not be overflowed.");
// Hack - we lock the size of the default flex-y items in the nav-bar,
// namely, the URL and search inputs. That way we can resize the
// window without worrying about them flexing.
const kFlexyItems = ["urlbar-container", "search-container"];
registerCleanupFunction(() => unlockWidth(kFlexyItems));
lockWidth(kFlexyItems);
// Resize the window to half of its original size. That should
// be enough to overflow the downloads button.
window.resizeTo(oldWidth / 2, window.outerHeight);
yield waitForOverflowed(button, true);
let promise = promisePanelOpened();
button.node.doCommand();
yield promise;
let panel = DownloadsPanel.panel;
let chevron = document.getElementById("nav-bar-overflow-button");
is(panel.anchorNode, chevron, "Panel should be anchored to the chevron.");
DownloadsPanel.hidePanel();
// Unlock the widths on the flex-y items.
unlockWidth(kFlexyItems);
// Put the window back to its original dimensions.
window.resizeTo(oldWidth, window.outerHeight);
// The downloads button should eventually be un-overflowed.
yield waitForOverflowed(button, false);
// Now try opening the panel again.
promise = promisePanelOpened();
button.node.doCommand();
yield promise;
is(panel.anchorNode.id, "downloads-indicator-anchor");
DownloadsPanel.hidePanel();
} finally {
// Clean up when the test finishes.
yield task_resetState();
}
}
/**
* For some node IDs, finds the nodes and sets their min-width's to their
* current width, preventing them from flex-shrinking.
*
* @param aItemIDs an array of item IDs to set min-width on.
*/
function lockWidth(aItemIDs) {
for (let itemID of aItemIDs) {
let item = document.getElementById(itemID);
let curWidth = item.getBoundingClientRect().width + "px";
item.style.minWidth = curWidth;
}
}
/**
* Clears the min-width's set on a set of IDs by lockWidth.
*
* @param aItemIDs an array of ItemIDs to remove min-width on.
*/
function unlockWidth(aItemIDs) {
for (let itemID of aItemIDs) {
let item = document.getElementById(itemID);
item.style.minWidth = "";
}
}
/**
* Waits for a node to enter or exit the overflowed state.
*
* @param aItem the node to wait for.
* @param aIsOverflowed if we're waiting for the item to be overflowed.
*/
function waitForOverflowed(aItem, aIsOverflowed) {
let deferOverflow = Promise.defer();
if (aItem.overflowed == aIsOverflowed) {
return deferOverflow.resolve();
}
let observer = new MutationObserver(function(aMutations) {
if (aItem.overflowed == aIsOverflowed) {
observer.disconnect();
deferOverflow.resolve();
}
});
observer.observe(aItem.node, {attributes: true});
return deferOverflow.promise;
}

View File

@ -7,6 +7,7 @@
PARALLEL_DIRS += [
'about',
'certerror',
'customizableui',
'dirprovider',
'downloads',
'feeds',

View File

@ -1285,49 +1285,17 @@ BrowserGlue.prototype = {
},
_migrateUI: function BG__migrateUI() {
const UI_VERSION = 14;
const UI_VERSION = 17;
const BROWSER_DOCURL = "chrome://browser/content/browser.xul#";
let wasCustomizedAndOnAustralis = Services.prefs.prefHasUserValue("browser.uiCustomization.state");
let currentUIVersion = 0;
try {
currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
} catch(ex) {}
if (!wasCustomizedAndOnAustralis && currentUIVersion >= UI_VERSION)
if (currentUIVersion >= UI_VERSION)
return;
this._rdf = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
this._dataSource = this._rdf.GetDataSource("rdf:local-store");
// No version check for this as this code should run until we have Australis everywhere:
if (wasCustomizedAndOnAustralis) {
// This profile's been on australis! If it's missing the back/fwd button
// or go/stop/reload button, then put them back:
let currentsetResource = this._rdf.GetResource("currentset");
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
let currentset = this._getPersist(toolbarResource, currentsetResource);
if (currentset.indexOf("unified-back-forward-button") == -1) {
currentset = currentset.replace("urlbar-container",
"unified-back-forward-button,urlbar-container");
}
if (currentset.indexOf("reload-button") == -1) {
currentset = currentset.replace("urlbar-container", "urlbar-container,reload-button");
}
if (currentset.indexOf("stop-button") == -1) {
currentset = currentset.replace("reload-button", "reload-button,stop-button");
}
this._setPersist(toolbarResource, currentsetResource, currentset);
Services.prefs.clearUserPref("browser.uiCustomization.state");
// If we don't have anything else to do, we can bail here:
if (currentUIVersion >= UI_VERSION) {
delete this._rdf;
delete this._dataSource;
return;
}
}
this._dirty = false;
if (currentUIVersion < 2) {
@ -1511,6 +1479,60 @@ BrowserGlue.prototype = {
OS.File.remove(path);
}
if (currentUIVersion < 15) {
// Migrate users from text or text&icons mode to icons mode.
let updateToolbars = function (aToolbarIds, aResourceName, aResourceValue) {
let resource = this._rdf.GetResource(aResourceName);
for (toolbarId of aToolbarIds) {
let toolbar = this._rdf.GetResource(BROWSER_DOCURL + toolbarId);
let oldValue = this._getPersist(toolbar, resource);
if (oldValue && oldValue != aResourceValue) {
this._setPersist(toolbar, resource, aResourceValue);
}
}
}.bind(this);
updateToolbars(["navigator-toolbox", "nav-bar", "PersonalToolbar", "addon-bar"], "mode", "icons");
// Exclude PersonalToolbar and addon-bar since they have lockiconsize="true".
updateToolbars(["navigator-toolbox", "nav-bar"], "iconsize", "large");
}
if (currentUIVersion < 16) {
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
let collapsedResource = this._rdf.GetResource("collapsed");
let isCollapsed = this._getPersist(toolbarResource, collapsedResource);
if (isCollapsed == "true") {
this._setPersist(toolbarResource, collapsedResource, "false");
}
}
// Insert the bookmarks-menu-button into the nav-bar if it isn't already
// there.
if (currentUIVersion < 17) {
let currentsetResource = this._rdf.GetResource("currentset");
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
let currentset = this._getPersist(toolbarResource, currentsetResource);
// Need to migrate only if toolbar is customized.
if (currentset) {
if (!currentset.contains("bookmarks-menu-button")) {
// The button isn't in the nav-bar, so let's look for an appropriate
// place to put it.
if (currentset.contains("downloads-button")) {
currentset = currentset.replace(/(^|,)downloads-button($|,)/,
"$1bookmarks-menu-button,downloads-button$2");
} else if (currentset.contains("home-button")) {
currentset = currentset.replace(/(^|,)home-button($|,)/,
"$1bookmarks-menu-button,home-button$2");
} else {
// Just append.
currentset = currentset.replace(/(^|,)window-controls($|,)/,
"$1bookmarks-menu-button,window-controls$2")
}
this._setPersist(toolbarResource, currentsetResource, currentset);
}
}
}
if (this._dirty)
this._dataSource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();

View File

@ -1745,3 +1745,157 @@ PlacesMenu.prototype = {
}
};
function PlacesPanelMenuView(aPlace, aViewId, aRootId) {
this._viewElt = document.getElementById(aViewId);
this._rootElt = document.getElementById(aRootId);
this._viewElt._placesView = this;
PlacesViewBase.call(this, aPlace);
}
PlacesPanelMenuView.prototype = {
__proto__: PlacesViewBase.prototype,
QueryInterface: function PAMV_QueryInterface(aIID) {
return PlacesViewBase.prototype.QueryInterface.apply(this, arguments);
},
uninit: function PAMV_uninit() {
PlacesViewBase.prototype.uninit.apply(this, arguments);
},
_insertNewItem:
function PAMV__insertNewItem(aChild, aBefore) {
this._domNodes.delete(aChild);
let type = aChild.type;
let button;
if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
button = document.createElement("toolbarseparator");
}
else {
button = document.createElement("toolbarbutton");
button.className = "bookmark-item";
button.setAttribute("label", aChild.title);
let icon = aChild.icon;
if (icon)
button.setAttribute("image", icon);
if (PlacesUtils.containerTypes.indexOf(type) != -1) {
button.setAttribute("container", "true");
if (PlacesUtils.nodeIsQuery(aChild)) {
button.setAttribute("query", "true");
if (PlacesUtils.nodeIsTagQuery(aChild))
button.setAttribute("tagContainer", "true");
}
else if (PlacesUtils.nodeIsFolder(aChild)) {
PlacesUtils.livemarks.getLivemark(
{ id: aChild.itemId },
function (aStatus, aLivemark) {
if (Components.isSuccessCode(aStatus)) {
button.setAttribute("livemark", "true");
this.controller.cacheLivemarkInfo(aChild, aLivemark);
}
}.bind(this)
);
}
}
else if (PlacesUtils.nodeIsURI(aChild)) {
button.setAttribute("scheme",
PlacesUIUtils.guessUrlSchemeForUI(aChild.uri));
}
}
button._placesNode = aChild;
if (!this._domNodes.has(aChild))
this._domNodes.set(aChild, button);
this._rootElt.insertBefore(button, aBefore);
},
nodeInserted:
function PAMV_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) {
let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
if (parentElt != this._rootElt)
return;
let children = this._rootElt.childNodes;
this._insertNewItem(aPlacesNode,
aIndex < children.length ? children[aIndex] : null);
},
nodeRemoved:
function PAMV_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) {
let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
if (parentElt != this._rootElt)
return;
let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
this._removeChild(elt);
},
nodeMoved:
function PAMV_nodeMoved(aPlacesNode,
aOldParentPlacesNode, aOldIndex,
aNewParentPlacesNode, aNewIndex) {
let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode);
if (parentElt != this._rootElt)
return;
let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
this._removeChild(elt);
this._rootElt.insertBefore(elt, this._rootElt.childNodes[aNewIndex]);
},
nodeAnnotationChanged:
function PAMV_nodeAnnotationChanged(aPlacesNode, aAnno) {
let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
// There's no UI representation for the root node.
if (elt == this._rootElt)
return;
if (elt.parentNode != this._rootElt)
return;
// All livemarks have a feedURI, so use it as our indicator.
if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
elt.setAttribute("livemark", true);
PlacesUtils.livemarks.getLivemark(
{ id: aPlacesNode.itemId },
function (aStatus, aLivemark) {
if (Components.isSuccessCode(aStatus)) {
this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
this.invalidateContainer(aPlacesNode);
}
}.bind(this)
);
}
},
nodeTitleChanged: function PAMV_nodeTitleChanged(aPlacesNode, aNewTitle) {
let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
// There's no UI representation for the root node.
if (elt == this._rootElt)
return;
PlacesViewBase.prototype.nodeTitleChanged.apply(this, arguments);
},
invalidateContainer: function PAMV_invalidateContainer(aPlacesNode) {
let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
if (elt != this._rootElt)
return;
// Container is the toolbar itself.
while (this._rootElt.hasChildNodes()) {
this._rootElt.removeChild(this._rootElt.firstChild);
}
for (let i = 0; i < this._resultNode.childCount; ++i) {
this._insertNewItem(this._resultNode.getChild(i), null);
}
}
};

View File

@ -80,8 +80,8 @@
elt = elt.parentNode;
// Calculate positions taking care of arrowscrollbox
let eventY = aEvent.layerY;
let scrollbox = this._scrollBox;
let eventY = aEvent.layerY + (scrollbox.boxObject.y - this.boxObject.y);
let scrollboxOffset = scrollbox.scrollBoxObject.y -
(scrollbox.boxObject.y - this.boxObject.y);
let eltY = elt.boxObject.y - scrollboxOffset;
@ -485,4 +485,121 @@
</handlers>
</binding>
<!-- Most of this is copied from the arrowpanel binding in popup.xml -->
<binding id="places-popup-arrow"
extends="chrome://browser/content/places/menu.xml#places-popup-base">
<content flip="both" side="top" position="bottomcenter topleft">
<xul:box anonid="container" class="panel-arrowcontainer" flex="1"
xbl:inherits="side,panelopen">
<xul:box anonid="arrowbox" class="panel-arrowbox">
<xul:image anonid="arrow" class="panel-arrow" xbl:inherits="side"/>
</xul:box>
<xul:box class="panel-arrowcontent" xbl:inherits="side,align,dir,orient,pack" flex="1">
<xul:vbox class="menupopup-drop-indicator-bar" hidden="true">
<xul:image class="menupopup-drop-indicator" mousethrough="always"/>
</xul:vbox>
<xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
smoothscroll="false">
<children/>
</xul:arrowscrollbox>
</xul:box>
</xul:box>
</content>
<implementation>
<method name="adjustArrowPosition">
<body><![CDATA[
var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");
var anchor = this.anchorNode;
if (!anchor) {
arrow.hidden = true;
return;
}
var container = document.getAnonymousElementByAttribute(this, "anonid", "container");
var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");
// if this panel has a "sliding" arrow, we may have previously set margins...
arrowbox.style.removeProperty("margin");
var position = this.alignmentPosition;
if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) {
container.orient = "";
arrowbox.orient = "vertical";
if (position.indexOf("_after") > 0) {
arrowbox.pack = "end";
arrowbox.style.marginBottom = this.alignmentOffset + "px";
} else {
arrowbox.pack = "start";
arrowbox.style.marginTop = this.alignmentOffset + "px";
}
// The assigned side stays the same regardless of direction.
var isRTL = (window.getComputedStyle(this).direction == "rtl");
if (position.indexOf("start_") == 0) {
container.dir = "reverse";
this.setAttribute("side", isRTL ? "left" : "right");
}
else {
container.dir = "";
this.setAttribute("side", isRTL ? "right" : "left");
}
}
else if (position.indexOf("before_") == 0 || position.indexOf("after_") == 0) {
container.orient = "vertical";
arrowbox.orient = "";
if (position.indexOf("_end") > 0) {
arrowbox.pack = "end";
arrowbox.style.marginRight = this.alignmentOffset + "px";
} else {
arrowbox.pack = "start";
arrowbox.style.marginLeft = this.alignmentOffset + "px";
}
if (position.indexOf("before_") == 0) {
container.dir = "reverse";
this.setAttribute("side", "bottom");
}
else {
container.dir = "";
this.setAttribute("side", "top");
}
}
arrow.hidden = false;
]]></body>
</method>
</implementation>
<handlers>
<handler event="popupshowing" phase="target"><![CDATA[
this.adjustArrowPosition();
]]></handler>
<handler event="popupshown" phase="target"><![CDATA[
this.setAttribute("panelopen", "true");
// Allow anchoring to a specified element inside the anchor.
var anchorClass = this.getAttribute("anonanchorclass");
if (anchorClass && this.anchorNode) {
let anchor =
document.getAnonymousElementByAttribute(this.anchorNode, "class",
anchorClass);
if (anchor) {
let offsetX = anchor.boxObject.width / 2;
if (this.alignmentPosition.endsWith("_end"))
offsetX *= -1;
this.popupBoxObject.moveToAnchor(anchor, this.alignmentPosition,
offsetX, 0,
false);
this.adjustArrowPosition();
}
}
]]></handler>
<handler event="popuphidden" phase="target"><![CDATA[
this.removeAttribute("panelopen");
]]></handler>
</handlers>
</binding>
</bindings>

View File

@ -1,6 +1,9 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource:///modules/CustomizableUI.jsm");
function test() {
let cw;
let win;
@ -31,10 +34,7 @@ function test() {
let pos = currentSet.indexOf(buttonId);
if (-1 < pos) {
currentSet.splice(pos, 1);
toolbar.setAttribute("currentset", currentSet.join(","));
toolbar.currentSet = currentSet.join(",");
win.document.persist(toolbar.id, "currentset");
CustomizableUI.removeWidgetFromArea("tabview-button");
}
}

Some files were not shown because too many files have changed in this diff Show More