Merge mozilla-central and fx-team

This commit is contained in:
Ed Morley 2014-04-02 18:25:23 +01:00
commit 757c230f65
28 changed files with 765 additions and 242 deletions

View File

@ -73,10 +73,12 @@
accesskey="&emailPageCmd.accesskey;" accesskey="&emailPageCmd.accesskey;"
command="Browser:SendLink"/> command="Browser:SendLink"/>
<menuseparator/> <menuseparator/>
#ifndef XP_LINUX
<menuitem id="menu_printSetup" <menuitem id="menu_printSetup"
label="&printSetupCmd.label;" label="&printSetupCmd.label;"
accesskey="&printSetupCmd.accesskey;" accesskey="&printSetupCmd.accesskey;"
command="cmd_pageSetup"/> command="cmd_pageSetup"/>
#endif
#ifndef XP_MACOSX #ifndef XP_MACOSX
<menuitem id="menu_printPreview" <menuitem id="menu_printPreview"
label="&printPreviewCmd.label;" label="&printPreviewCmd.label;"

View File

@ -884,6 +884,10 @@ let PlacesToolbarHelper = {
if (!viewElt || viewElt._placesView) if (!viewElt || viewElt._placesView)
return; return;
// CustomizableUI.addListener is idempotent, so we can safely
// call this multiple times.
CustomizableUI.addListener(this);
// If the bookmarks toolbar item is: // If the bookmarks toolbar item is:
// - not in a toolbar, or; // - not in a toolbar, or;
// - the toolbar is collapsed, or; // - the toolbar is collapsed, or;
@ -899,7 +903,11 @@ let PlacesToolbarHelper = {
if (forceToolbarOverflowCheck) { if (forceToolbarOverflowCheck) {
viewElt._placesView.updateOverflowStatus(); viewElt._placesView.updateOverflowStatus();
} }
this.customizeChange(); this._setupPlaceholder();
},
uninit: function PTH_uninit() {
CustomizableUI.removeListener(this);
}, },
customizeStart: function PTH_customizeStart() { customizeStart: function PTH_customizeStart() {
@ -914,10 +922,15 @@ let PlacesToolbarHelper = {
}, },
customizeChange: function PTH_customizeChange() { customizeChange: function PTH_customizeChange() {
this._setupPlaceholder();
},
_setupPlaceholder: function PTH_setupPlaceholder() {
let placeholder = this._placeholder; let placeholder = this._placeholder;
if (!placeholder) { if (!placeholder) {
return; return;
} }
let shouldWrapNow = this._getShouldWrap(); let shouldWrapNow = this._getShouldWrap();
if (this._shouldWrap != shouldWrapNow) { if (this._shouldWrap != shouldWrapNow) {
if (shouldWrapNow) { if (shouldWrapNow) {
@ -958,7 +971,40 @@ let PlacesToolbarHelper = {
element = element.parentNode; element = element.parentNode;
} }
return null; return null;
} },
onWidgetUnderflow: function(aNode, aContainer) {
// The view gets broken by being removed and reinserted by the overflowable
// toolbar, so we have to force an uninit and reinit.
let win = aNode.ownerDocument.defaultView;
if (aNode.id == "personal-bookmarks" && win == window) {
this._resetView();
}
},
onWidgetAdded: function(aWidgetId, aArea, aPosition) {
if (aWidgetId == "personal-bookmarks" && !this._isCustomizing) {
// It's possible (with the "Add to Menu", "Add to Toolbar" context
// options) that the Places Toolbar Items have been moved without
// letting us prepare and handle it with with customizeStart and
// customizeDone. If that's the case, we need to reset the views
// since they're probably broken from the DOM reparenting.
this._resetView();
}
},
_resetView: function() {
if (this._viewElt) {
// It's possible that the placesView might not exist, and we need to
// do a full init. This could happen if the Bookmarks Toolbar Items are
// moved to the Menu Panel, and then to the toolbar with the "Add to Toolbar"
// context menu option, outside of customize mode.
if (this._viewElt._placesView) {
this._viewElt._placesView.uninit();
}
this.init(true);
}
},
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -1164,6 +1210,16 @@ let BookmarkingUI = {
// so kill current view and let popupshowing generate a new one. // so kill current view and let popupshowing generate a new one.
if (this.button._placesView) if (this.button._placesView)
this.button._placesView.uninit(); this.button._placesView.uninit();
// We have to do the same thing for the "special" views underneath the
// the bookmarks menu.
const kSpecialViewNodeIDs = ["BMB_bookmarksToolbar", "BMB_unsortedBookmarks"];
for (let viewNodeID of kSpecialViewNodeIDs) {
let elem = document.getElementById(viewNodeID);
if (elem && elem._placesView) {
elem._placesView.uninit();
}
}
}, },
onCustomizeStart: function BUI_customizeStart(aWindow) { onCustomizeStart: function BUI_customizeStart(aWindow) {

View File

@ -1289,6 +1289,8 @@ var gBrowserInit = {
} catch (ex) { } catch (ex) {
} }
PlacesToolbarHelper.uninit();
BookmarkingUI.uninit(); BookmarkingUI.uninit();
TabsInTitlebar.uninit(); TabsInTitlebar.uninit();

View File

@ -93,6 +93,10 @@ skip-if = os == "linux"
[browser_978084_dragEnd_after_move.js] [browser_978084_dragEnd_after_move.js]
[browser_980155_add_overflow_toolbar.js] [browser_980155_add_overflow_toolbar.js]
[browser_981418-widget-onbeforecreated-handler.js] [browser_981418-widget-onbeforecreated-handler.js]
[browser_984455_bookmarks_items_reparenting.js]
skip-if = os == "linux"
[browser_985815_propagate_setToolbarVisibility.js] [browser_985815_propagate_setToolbarVisibility.js]
[browser_981305_separator_insertion.js] [browser_981305_separator_insertion.js]
[browser_987177_destroyWidget_xul.js] [browser_987177_destroyWidget_xul.js]

View File

@ -10,7 +10,7 @@ const isOSX = (Services.appinfo.OS === "Darwin");
// show a context menu with options to move it. // show a context menu with options to move it.
add_task(function() { add_task(function() {
let contextMenu = document.getElementById("toolbar-context-menu"); let contextMenu = document.getElementById("toolbar-context-menu");
let shownPromise = contextMenuShown(contextMenu); let shownPromise = popupShown(contextMenu);
let homeButton = document.getElementById("home-button"); let homeButton = document.getElementById("home-button");
EventUtils.synthesizeMouse(homeButton, 2, 2, {type: "contextmenu", button: 2 }); EventUtils.synthesizeMouse(homeButton, 2, 2, {type: "contextmenu", button: 2 });
yield shownPromise; yield shownPromise;
@ -30,7 +30,7 @@ add_task(function() {
); );
checkContextMenu(contextMenu, expectedEntries); checkContextMenu(contextMenu, expectedEntries);
let hiddenPromise = contextMenuHidden(contextMenu); let hiddenPromise = popupHidden(contextMenu);
contextMenu.hidePopup(); contextMenu.hidePopup();
yield hiddenPromise; yield hiddenPromise;
}); });
@ -40,7 +40,7 @@ add_task(function() {
// and a toggle option for the extra toolbar // and a toggle option for the extra toolbar
add_task(function() { add_task(function() {
let contextMenu = document.getElementById("toolbar-context-menu"); let contextMenu = document.getElementById("toolbar-context-menu");
let shownPromise = contextMenuShown(contextMenu); let shownPromise = popupShown(contextMenu);
let toolbar = createToolbarWithPlacements("880164_empty_toolbar", []); let toolbar = createToolbarWithPlacements("880164_empty_toolbar", []);
toolbar.setAttribute("context", "toolbar-context-menu"); toolbar.setAttribute("context", "toolbar-context-menu");
toolbar.setAttribute("toolbarname", "Fancy Toolbar for Context Menu"); toolbar.setAttribute("toolbarname", "Fancy Toolbar for Context Menu");
@ -63,7 +63,7 @@ add_task(function() {
); );
checkContextMenu(contextMenu, expectedEntries); checkContextMenu(contextMenu, expectedEntries);
let hiddenPromise = contextMenuHidden(contextMenu); let hiddenPromise = popupHidden(contextMenu);
contextMenu.hidePopup(); contextMenu.hidePopup();
yield hiddenPromise; yield hiddenPromise;
removeCustomToolbars(); removeCustomToolbars();
@ -74,7 +74,7 @@ add_task(function() {
// show a context menu with disabled options to move it. // show a context menu with disabled options to move it.
add_task(function() { add_task(function() {
let contextMenu = document.getElementById("toolbar-context-menu"); let contextMenu = document.getElementById("toolbar-context-menu");
let shownPromise = contextMenuShown(contextMenu); let shownPromise = popupShown(contextMenu);
let urlBarContainer = document.getElementById("urlbar-container"); let urlBarContainer = document.getElementById("urlbar-container");
// Need to make sure not to click within an edit field. // Need to make sure not to click within an edit field.
let urlbarRect = urlBarContainer.getBoundingClientRect(); let urlbarRect = urlBarContainer.getBoundingClientRect();
@ -96,7 +96,7 @@ add_task(function() {
); );
checkContextMenu(contextMenu, expectedEntries); checkContextMenu(contextMenu, expectedEntries);
let hiddenPromise = contextMenuHidden(contextMenu); let hiddenPromise = popupHidden(contextMenu);
contextMenu.hidePopup(); contextMenu.hidePopup();
yield hiddenPromise; yield hiddenPromise;
}); });
@ -135,7 +135,7 @@ add_task(function() {
yield shownPanelPromise; yield shownPanelPromise;
let contextMenu = document.getElementById("customizationPanelItemContextMenu"); let contextMenu = document.getElementById("customizationPanelItemContextMenu");
let shownContextPromise = contextMenuShown(contextMenu); let shownContextPromise = popupShown(contextMenu);
let newWindowButton = document.getElementById("new-window-button"); let newWindowButton = document.getElementById("new-window-button");
ok(newWindowButton, "new-window-button was found"); ok(newWindowButton, "new-window-button was found");
EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2}); EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2});
@ -151,7 +151,7 @@ add_task(function() {
]; ];
checkContextMenu(contextMenu, expectedEntries); checkContextMenu(contextMenu, expectedEntries);
let hiddenContextPromise = contextMenuHidden(contextMenu); let hiddenContextPromise = popupHidden(contextMenu);
contextMenu.hidePopup(); contextMenu.hidePopup();
yield hiddenContextPromise; yield hiddenContextPromise;
@ -165,7 +165,7 @@ add_task(function() {
add_task(function() { add_task(function() {
yield startCustomizing(); yield startCustomizing();
let contextMenu = document.getElementById("toolbar-context-menu"); let contextMenu = document.getElementById("toolbar-context-menu");
let shownPromise = contextMenuShown(contextMenu); let shownPromise = popupShown(contextMenu);
let homeButton = document.getElementById("wrapper-home-button"); let homeButton = document.getElementById("wrapper-home-button");
EventUtils.synthesizeMouse(homeButton, 2, 2, {type: "contextmenu", button: 2}); EventUtils.synthesizeMouse(homeButton, 2, 2, {type: "contextmenu", button: 2});
yield shownPromise; yield shownPromise;
@ -185,7 +185,7 @@ add_task(function() {
); );
checkContextMenu(contextMenu, expectedEntries); checkContextMenu(contextMenu, expectedEntries);
let hiddenContextPromise = contextMenuHidden(contextMenu); let hiddenContextPromise = popupHidden(contextMenu);
contextMenu.hidePopup(); contextMenu.hidePopup();
yield hiddenContextPromise; yield hiddenContextPromise;
}); });
@ -194,7 +194,7 @@ add_task(function() {
// show a context menu with options to move it. // show a context menu with options to move it.
add_task(function() { add_task(function() {
let contextMenu = document.getElementById("customizationPaletteItemContextMenu"); let contextMenu = document.getElementById("customizationPaletteItemContextMenu");
let shownPromise = contextMenuShown(contextMenu); let shownPromise = popupShown(contextMenu);
let openFileButton = document.getElementById("wrapper-open-file-button"); let openFileButton = document.getElementById("wrapper-open-file-button");
EventUtils.synthesizeMouse(openFileButton, 2, 2, {type: "contextmenu", button: 2}); EventUtils.synthesizeMouse(openFileButton, 2, 2, {type: "contextmenu", button: 2});
yield shownPromise; yield shownPromise;
@ -205,7 +205,7 @@ add_task(function() {
]; ];
checkContextMenu(contextMenu, expectedEntries); checkContextMenu(contextMenu, expectedEntries);
let hiddenContextPromise = contextMenuHidden(contextMenu); let hiddenContextPromise = popupHidden(contextMenu);
contextMenu.hidePopup(); contextMenu.hidePopup();
yield hiddenContextPromise; yield hiddenContextPromise;
}); });
@ -214,7 +214,7 @@ add_task(function() {
// should show a context menu with options to move it. // should show a context menu with options to move it.
add_task(function() { add_task(function() {
let contextMenu = document.getElementById("customizationPanelItemContextMenu"); let contextMenu = document.getElementById("customizationPanelItemContextMenu");
let shownPromise = contextMenuShown(contextMenu); let shownPromise = popupShown(contextMenu);
let newWindowButton = document.getElementById("wrapper-new-window-button"); let newWindowButton = document.getElementById("wrapper-new-window-button");
EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2}); EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2});
yield shownPromise; yield shownPromise;
@ -227,7 +227,7 @@ add_task(function() {
]; ];
checkContextMenu(contextMenu, expectedEntries); checkContextMenu(contextMenu, expectedEntries);
let hiddenContextPromise = contextMenuHidden(contextMenu); let hiddenContextPromise = popupHidden(contextMenu);
contextMenu.hidePopup(); contextMenu.hidePopup();
yield hiddenContextPromise; yield hiddenContextPromise;
yield endCustomizing(); yield endCustomizing();
@ -241,7 +241,7 @@ add_task(function() {
yield startCustomizing(this.otherWin); yield startCustomizing(this.otherWin);
let contextMenu = this.otherWin.document.getElementById("customizationPanelItemContextMenu"); let contextMenu = this.otherWin.document.getElementById("customizationPanelItemContextMenu");
let shownPromise = contextMenuShown(contextMenu); let shownPromise = popupShown(contextMenu);
let newWindowButton = this.otherWin.document.getElementById("wrapper-new-window-button"); let newWindowButton = this.otherWin.document.getElementById("wrapper-new-window-button");
EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2}, this.otherWin); EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2}, this.otherWin);
yield shownPromise; yield shownPromise;
@ -254,7 +254,7 @@ add_task(function() {
]; ];
checkContextMenu(contextMenu, expectedEntries, this.otherWin); checkContextMenu(contextMenu, expectedEntries, this.otherWin);
let hiddenContextPromise = contextMenuHidden(contextMenu); let hiddenContextPromise = popupHidden(contextMenu);
contextMenu.hidePopup(); contextMenu.hidePopup();
yield hiddenContextPromise; yield hiddenContextPromise;
yield endCustomizing(this.otherWin); yield endCustomizing(this.otherWin);
@ -267,13 +267,13 @@ add_task(function() {
add_task(function() { add_task(function() {
yield startCustomizing(); yield startCustomizing();
let contextMenu = document.getElementById("customizationPanelItemContextMenu"); let contextMenu = document.getElementById("customizationPanelItemContextMenu");
let shownPromise = contextMenuShown(contextMenu); let shownPromise = popupShown(contextMenu);
let zoomControls = document.getElementById("wrapper-zoom-controls"); let zoomControls = document.getElementById("wrapper-zoom-controls");
EventUtils.synthesizeMouse(zoomControls, 2, 2, {type: "contextmenu", button: 2}); EventUtils.synthesizeMouse(zoomControls, 2, 2, {type: "contextmenu", button: 2});
yield shownPromise; yield shownPromise;
// Execute the command to move the item from the panel to the toolbar. // Execute the command to move the item from the panel to the toolbar.
contextMenu.childNodes[0].doCommand(); contextMenu.childNodes[0].doCommand();
let hiddenPromise = contextMenuHidden(contextMenu); let hiddenPromise = popupHidden(contextMenu);
contextMenu.hidePopup(); contextMenu.hidePopup();
yield hiddenPromise; yield hiddenPromise;
yield endCustomizing(); yield endCustomizing();
@ -282,7 +282,7 @@ add_task(function() {
is(zoomControls.parentNode.id, "nav-bar-customization-target", "Zoom-controls should be on the nav-bar"); is(zoomControls.parentNode.id, "nav-bar-customization-target", "Zoom-controls should be on the nav-bar");
contextMenu = document.getElementById("toolbar-context-menu"); contextMenu = document.getElementById("toolbar-context-menu");
shownPromise = contextMenuShown(contextMenu); shownPromise = popupShown(contextMenu);
EventUtils.synthesizeMouse(zoomControls, 2, 2, {type: "contextmenu", button: 2}); EventUtils.synthesizeMouse(zoomControls, 2, 2, {type: "contextmenu", button: 2});
yield shownPromise; yield shownPromise;
@ -301,7 +301,7 @@ add_task(function() {
); );
checkContextMenu(contextMenu, expectedEntries); checkContextMenu(contextMenu, expectedEntries);
hiddenPromise = contextMenuHidden(contextMenu); hiddenPromise = popupHidden(contextMenu);
contextMenu.hidePopup(); contextMenu.hidePopup();
yield hiddenPromise; yield hiddenPromise;
yield resetCustomization(); yield resetCustomization();
@ -315,7 +315,7 @@ add_task(function() {
yield PanelUI.show(); yield PanelUI.show();
let contextMenu = document.getElementById("customizationPanelItemContextMenu"); let contextMenu = document.getElementById("customizationPanelItemContextMenu");
let shownContextPromise = contextMenuShown(contextMenu); let shownContextPromise = popupShown(contextMenu);
let newWindowButton = document.getElementById("new-window-button"); let newWindowButton = document.getElementById("new-window-button");
ok(newWindowButton, "new-window-button was found"); ok(newWindowButton, "new-window-button was found");
EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2}); EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2});
@ -331,7 +331,7 @@ add_task(function() {
]; ];
checkContextMenu(contextMenu, expectedEntries); checkContextMenu(contextMenu, expectedEntries);
let hiddenContextPromise = contextMenuHidden(contextMenu); let hiddenContextPromise = popupHidden(contextMenu);
contextMenu.hidePopup(); contextMenu.hidePopup();
yield hiddenContextPromise; yield hiddenContextPromise;

View File

@ -27,7 +27,7 @@ add_task(function() {
yield shownPanelPromise; yield shownPanelPromise;
let contextMenu = document.getElementById("toolbar-context-menu"); let contextMenu = document.getElementById("toolbar-context-menu");
let shownContextPromise = contextMenuShown(contextMenu); let shownContextPromise = popupShown(contextMenu);
let homeButton = document.getElementById("home-button"); let homeButton = document.getElementById("home-button");
ok(homeButton, "home-button was found"); ok(homeButton, "home-button was found");
is(homeButton.getAttribute("overflowedItem"), "true", "Home button is overflowing"); is(homeButton.getAttribute("overflowedItem"), "true", "Home button is overflowing");
@ -51,7 +51,7 @@ add_task(function() {
); );
checkContextMenu(contextMenu, expectedEntries); checkContextMenu(contextMenu, expectedEntries);
let hiddenContextPromise = contextMenuHidden(contextMenu); let hiddenContextPromise = popupHidden(contextMenu);
let hiddenPromise = promisePanelElementHidden(window, overflowPanel); let hiddenPromise = promisePanelElementHidden(window, overflowPanel);
let moveToPanel = contextMenu.querySelector(".customize-context-moveToPanel"); let moveToPanel = contextMenu.querySelector(".customize-context-moveToPanel");
if (moveToPanel) { if (moveToPanel) {

View File

@ -0,0 +1,251 @@
/* 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";
let gNavBar = document.getElementById(CustomizableUI.AREA_NAVBAR);
let gOverflowList = document.getElementById(gNavBar.getAttribute("overflowtarget"));
const kBookmarksButton = "bookmarks-menu-button";
const kBookmarksItems = "personal-bookmarks";
const kOriginalWindowWidth = window.outerWidth;
const kSmallWidth = 400;
/**
* Helper function that opens the bookmarks menu, and returns a Promise that
* resolves as soon as the menu is ready for interaction.
*/
function bookmarksMenuPanelShown() {
let deferred = Promise.defer();
let bookmarksMenuPopup = document.getElementById("BMB_bookmarksPopup");
let onTransitionEnd = (e) => {
if (e.target == bookmarksMenuPopup) {
bookmarksMenuPopup.removeEventListener("transitionend", onTransitionEnd);
deferred.resolve();
}
}
bookmarksMenuPopup.addEventListener("transitionend", onTransitionEnd);
return deferred.promise;
}
/**
* Checks that the placesContext menu is correctly attached to the
* controller of some view. Returns a Promise that resolves as soon
* as the context menu is closed.
*
* @param aItemWithContextMenu the item that we need to synthesize hte
* right click on in order to open the context menu.
*/
function checkContextMenu(aItemWithContextMenu) {
return Task.spawn(function* () {
let contextMenu = document.getElementById("placesContext");
let newBookmarkItem = document.getElementById("placesContext_new:bookmark");
let shownPromise = popupShown(contextMenu);
EventUtils.synthesizeMouseAtCenter(aItemWithContextMenu,
{type: "contextmenu", button: 2});
yield shownPromise;
ok(!newBookmarkItem.hasAttribute("disabled"),
"New bookmark item shouldn't be disabled");
yield closePopup(contextMenu);
});
}
/**
* Opens the bookmarks menu panel, and then opens each of the "special"
* submenus in that list. Then it checks that those submenu's context menus
* are properly hooked up to a controller.
*/
function checkSpecialContextMenus() {
return Task.spawn(function* () {
let contextMenu = document.getElementById("placesContext");
let bookmarksMenuButton = document.getElementById(kBookmarksButton);
let bookmarksMenuPopup = document.getElementById("BMB_bookmarksPopup");
const kSpecialItemIDs = {
"BMB_bookmarksToolbar": "BMB_bookmarksToolbarPopup",
"BMB_unsortedBookmarks": "BMB_unsortedBookmarksPopup",
};
// Open the bookmarks menu button context menus and ensure that
// they have the proper views attached.
let shownPromise = bookmarksMenuPanelShown();
let dropmarker = document.getAnonymousElementByAttribute(bookmarksMenuButton,
"anonid", "dropmarker");
EventUtils.synthesizeMouseAtCenter(dropmarker, {});
info("Waiting for bookmarks menu popup to show after clicking dropmarker.")
yield shownPromise;
for (let menuID in kSpecialItemIDs) {
let menuItem = document.getElementById(menuID);
let menuPopup = document.getElementById(kSpecialItemIDs[menuID]);
let shownPromise = popupShown(menuPopup);
EventUtils.synthesizeMouseAtCenter(menuItem, {});
yield shownPromise;
yield checkContextMenu(menuPopup);
yield closePopup(menuPopup);
}
yield closePopup(bookmarksMenuPopup);
});
}
/**
* Closes a focused popup by simulating pressing the Escape key,
* and returns a Promise that resolves as soon as the popup is closed.
*
* @param aPopup the popup node to close.
*/
function closePopup(aPopup) {
let hiddenPromise = popupHidden(aPopup);
EventUtils.synthesizeKey("VK_ESCAPE", {});
return hiddenPromise;
}
/**
* Helper function that checks that the context menu of the
* bookmark toolbar items chevron popup is correctly hooked up
* to the controller of a view.
*/
function checkBookmarksItemsChevronContextMenu() {
return Task.spawn(function*() {
let chevronPopup = document.getElementById("PlacesChevronPopup");
let shownPromise = popupShown(chevronPopup);
let chevron = document.getElementById("PlacesChevron");
EventUtils.synthesizeMouseAtCenter(chevron, {});
yield shownPromise;
yield checkContextMenu(chevronPopup);
yield closePopup(chevronPopup);
});
}
/**
* Forces the window to a width that causes the nav-bar to overflow
* its contents. Returns a Promise that resolves as soon as the
* overflowable nav-bar is showing its chevron.
*/
function overflowEverything() {
window.resizeTo(kSmallWidth, window.outerHeight);
return waitForCondition(() => gNavBar.hasAttribute("overflowing"));
}
/**
* Returns the window to its original size from the start of the test,
* and returns a Promise that resolves when the nav-bar is no longer
* overflowing.
*/
function stopOverflowing() {
window.resizeTo(kOriginalWindowWidth, window.outerHeight);
return waitForCondition(() => !gNavBar.hasAttribute("overflowing"));
}
/**
* Checks that an item with ID aID is overflowing in the nav-bar.
*
* @param aID the ID of the node to check for overflowingness.
*/
function checkOverflowing(aID) {
ok(!gNavBar.querySelector("#" + aID),
"Item with ID " + aID + " should no longer be in the gNavBar");
let item = gOverflowList.querySelector("#" + aID);
ok(item, "Item with ID " + aID + " should be overflowing");
is(item.getAttribute("overflowedItem"), "true",
"Item with ID " + aID + " should have overflowedItem attribute");
}
/**
* Checks that an item with ID aID is not overflowing in the nav-bar.
*
* @param aID the ID of hte node to check for non-overflowingness.
*/
function checkNotOverflowing(aID) {
ok(!gOverflowList.querySelector("#" + aID),
"Item with ID " + aID + " should no longer be overflowing");
let item = gNavBar.querySelector("#" + aID);
ok(item, "Item with ID " + aID + " should be in the nav bar");
ok(!item.hasAttribute("overflowedItem"),
"Item with ID " + aID + " should not have overflowedItem attribute");
}
/**
* Test that overflowing the bookmarks menu button doesn't break the
* context menus for the Unsorted and Bookmarks Toolbar menu items.
*/
add_task(function* testOverflowingBookmarksButtonContextMenu() {
ok(!gNavBar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
ok(CustomizableUI.inDefaultState, "Should start in default state.");
// Open the Unsorted and Bookmarks Toolbar context menus and ensure
// that they have views attached.
yield checkSpecialContextMenus();
yield overflowEverything();
checkOverflowing(kBookmarksButton);
yield stopOverflowing();
checkNotOverflowing(kBookmarksButton);
yield checkSpecialContextMenus();
});
/**
* Test that the bookmarks toolbar items context menu still works if moved
* to the menu from the overflow panel, and then back to the toolbar.
*/
add_task(function* testOverflowingBookmarksItemsContextMenu() {
yield PanelUI.ensureReady();
let bookmarksToolbarItems = document.getElementById(kBookmarksItems);
gCustomizeMode.addToToolbar(bookmarksToolbarItems);
yield checkContextMenu(bookmarksToolbarItems);
yield overflowEverything();
checkOverflowing(kBookmarksItems)
gCustomizeMode.addToPanel(bookmarksToolbarItems);
yield stopOverflowing();
gCustomizeMode.addToToolbar(bookmarksToolbarItems);
yield checkContextMenu(bookmarksToolbarItems);
});
/**
* Test that overflowing the bookmarks toolbar items doesn't cause the
* context menu in the bookmarks toolbar items chevron to stop working.
*/
add_task(function* testOverflowingBookmarksItemsChevronContextMenu() {
// If it's not already there, let's move the bookmarks toolbar items to
// the nav-bar.
let bookmarksToolbarItems = document.getElementById(kBookmarksItems);
gCustomizeMode.addToToolbar(bookmarksToolbarItems);
// We make the PlacesToolbarItems element be super tiny in order to force
// the bookmarks toolbar items into overflowing and making the chevron
// show itself.
let placesToolbarItems = document.getElementById("PlacesToolbarItems");
let placesChevron = document.getElementById("PlacesChevron");
placesToolbarItems.style.maxWidth = "10px";
yield waitForCondition(() => !placesChevron.collapsed);
yield checkBookmarksItemsChevronContextMenu();
yield overflowEverything();
checkOverflowing(kBookmarksItems);
yield stopOverflowing();
checkNotOverflowing(kBookmarksItems);
yield checkBookmarksItemsChevronContextMenu();
placesToolbarItems.style.removeProperty("max-width");
});
add_task(function* asyncCleanup() {
window.resizeTo(kOriginalWindowWidth, window.outerHeight);
yield resetCustomization();
});

View File

@ -428,36 +428,49 @@ function promiseTabHistoryNavigation(aDirection = -1, aConditionFn) {
return deferred.promise; return deferred.promise;
} }
function contextMenuShown(aContextMenu) { function popupShown(aPopup) {
let deferred = Promise.defer(); return promisePopupEvent(aPopup, "shown");
let win = aContextMenu.ownerDocument.defaultView;
let timeoutId = win.setTimeout(() => {
deferred.reject("Context menu (" + aContextMenu.id + ") did not show within 20 seconds.");
}, 20000);
function onPopupShown(e) {
aContextMenu.removeEventListener("popupshown", onPopupShown);
win.clearTimeout(timeoutId);
deferred.resolve();
};
aContextMenu.addEventListener("popupshown", onPopupShown);
return deferred.promise;
} }
function contextMenuHidden(aContextMenu) { function popupHidden(aPopup) {
let deferred = Promise.defer(); return promisePopupEvent(aPopup, "hidden");
let win = aContextMenu.ownerDocument.defaultView;
let timeoutId = win.setTimeout(() => {
deferred.reject("Context menu (" + aContextMenu.id + ") did not hide within 20 seconds.");
}, 20000);
function onPopupHidden(e) {
win.clearTimeout(timeoutId);
aContextMenu.removeEventListener("popuphidden", onPopupHidden);
deferred.resolve();
};
aContextMenu.addEventListener("popuphidden", onPopupHidden);
return deferred.promise;
} }
/**
* Returns a Promise that resolves when aPopup fires an event of type
* aEventType. Times out and rejects after 20 seconds.
*
* @param aPopup the popup to monitor for events.
* @param aEventSuffix the _suffix_ for the popup event type to watch for.
*
* Example usage:
* let popupShownPromise = promisePopupEvent(somePopup, "shown");
* // ... something that opens a popup
* yield popupShownPromise;
*
* let popupHiddenPromise = promisePopupEvent(somePopup, "hidden");
* // ... something that hides a popup
* yield popupHiddenPromise;
*/
function promisePopupEvent(aPopup, aEventSuffix) {
let deferred = Promise.defer();
let win = aPopup.ownerDocument.defaultView;
let eventType = "popup" + aEventSuffix;
let timeoutId = win.setTimeout(() => {
deferred.reject("Context menu (" + aPopup.id + ") did not fire "
+ eventType + " within 20 seconds.");
}, 20000);
function onPopupEvent(e) {
win.clearTimeout(timeoutId);
aPopup.removeEventListener(eventType, onPopupEvent);
deferred.resolve();
};
aPopup.addEventListener(eventType, onPopupEvent);
return deferred.promise;
}
// This is a simpler version of the context menu check that // This is a simpler version of the context menu check that
// exists in contextmenu_common.js. // exists in contextmenu_common.js.

View File

@ -960,6 +960,10 @@ PlacesToolbar.prototype = {
this._removeEventListeners(window, ["resize", "unload"], false); this._removeEventListeners(window, ["resize", "unload"], false);
this._removeEventListeners(gBrowser.tabContainer, ["TabOpen", "TabClose"], false); this._removeEventListeners(gBrowser.tabContainer, ["TabOpen", "TabClose"], false);
if (this._chevron._placesView) {
this._chevron._placesView.uninit();
}
PlacesViewBase.prototype.uninit.apply(this, arguments); PlacesViewBase.prototype.uninit.apply(this, arguments);
}, },

View File

@ -26,6 +26,10 @@ const BUTTON_POSITION_DONT_SAVE = 2;
const BUTTON_POSITION_REVERT = 0; const BUTTON_POSITION_REVERT = 0;
const EVAL_FUNCTION_TIMEOUT = 1000; // milliseconds const EVAL_FUNCTION_TIMEOUT = 1000; // milliseconds
const MAXIMUM_FONT_SIZE = 96;
const MINIMUM_FONT_SIZE = 6;
const NORMAL_FONT_SIZE = 12;
const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties"; const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties";
const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled"; const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
const PREF_RECENT_FILES_MAX = "devtools.scratchpad.recentFilesMax"; const PREF_RECENT_FILES_MAX = "devtools.scratchpad.recentFilesMax";
@ -212,7 +216,25 @@ var Scratchpad = {
}, },
"sp-cmd-hideSidebar": () => { "sp-cmd-hideSidebar": () => {
Scratchpad.sidebar.hide(); Scratchpad.sidebar.hide();
} },
"sp-cmd-line-numbers": () => {
Scratchpad.toggleEditorOption('lineNumbers');
},
"sp-cmd-wrap-text": () => {
Scratchpad.toggleEditorOption('lineWrapping');
},
"sp-cmd-highlight-trailing-space": () => {
Scratchpad.toggleEditorOption('showTrailingSpace');
},
"sp-cmd-larger-font": () => {
Scratchpad.increaseFontSize();
},
"sp-cmd-smaller-font": () => {
Scratchpad.decreaseFontSize();
},
"sp-cmd-normal-font": () => {
Scratchpad.normalFontSize();
},
} }
for (let command in commands) { for (let command in commands) {
@ -1779,6 +1801,47 @@ var Scratchpad = {
return shouldClose; return shouldClose;
}, },
/**
* Toggle a editor's boolean option.
*/
toggleEditorOption: function SP_toggleEditorOption(optionName)
{
let newOptionValue = !this.editor.getOption(optionName);
this.editor.setOption(optionName, newOptionValue);
},
/**
* Increase the editor's font size by 1 px.
*/
increaseFontSize: function SP_increaseFontSize()
{
let size = this.editor.getFontSize();
if (size < MAXIMUM_FONT_SIZE) {
this.editor.setFontSize(size + 1);
}
},
/**
* Decrease the editor's font size by 1 px.
*/
decreaseFontSize: function SP_decreaseFontSize()
{
let size = this.editor.getFontSize();
if (size > MINIMUM_FONT_SIZE) {
this.editor.setFontSize(size - 1);
}
},
/**
* Restore the editor's original font size.
*/
normalFontSize: function SP_normalFontSize()
{
this.editor.setFontSize(NORMAL_FONT_SIZE);
},
_observers: [], _observers: [],
/** /**

View File

@ -47,6 +47,12 @@
<command id="sp-cmd-saveas" oncommand=";"/> <command id="sp-cmd-saveas" oncommand=";"/>
<command id="sp-cmd-revert" oncommand=";" disabled="true"/> <command id="sp-cmd-revert" oncommand=";" disabled="true"/>
<command id="sp-cmd-close" oncommand=";"/> <command id="sp-cmd-close" oncommand=";"/>
<command id="sp-cmd-line-numbers" oncommand=";"/>
<command id="sp-cmd-wrap-text" oncommand=";"/>
<command id="sp-cmd-highlight-trailing-space" oncommand=";"/>
<command id="sp-cmd-larger-font" oncommand=";"/>
<command id="sp-cmd-smaller-font" oncommand=";"/>
<command id="sp-cmd-normal-font" oncommand=";"/>
<command id="sp-cmd-run" oncommand=";"/> <command id="sp-cmd-run" oncommand=";"/>
<command id="sp-cmd-inspect" oncommand=";"/> <command id="sp-cmd-inspect" oncommand=";"/>
<command id="sp-cmd-display" oncommand=";"/> <command id="sp-cmd-display" oncommand=";"/>
@ -80,6 +86,18 @@
key="&closeCmd.key;" key="&closeCmd.key;"
command="sp-cmd-close" command="sp-cmd-close"
modifiers="accel"/> modifiers="accel"/>
<key id="sp-key-larger-font"
key="&largerFont.commandkey;"
command="sp-cmd-larger-font"
modifiers="accel"/>
<key id="sp-key-smaller-font"
key="&smallerFont.commandkey;"
command="sp-cmd-smaller-font"
modifiers="accel"/>
<key id="sp-key-normal-size-font"
key="&normalSize.commandkey;"
command="sp-cmd-normal-font"
modifiers="accel"/>
<key id="sp-key-run" <key id="sp-key-run"
key="&run.key;" key="&run.key;"
command="sp-cmd-run" command="sp-cmd-run"
@ -166,6 +184,43 @@
</menupopup> </menupopup>
</menu> </menu>
<menu id="sp-view-menu" label="&viewMenu.label;" accesskey="&viewMenu.accesskey;">
<menupopup id="sp-menu-viewpopup">
<menuitem id="sp-menu-line-numbers"
label="&lineNumbers.label;"
accesskey="&lineNumbers.accesskey;"
type="checkbox"
checked="true"
command="sp-cmd-line-numbers"/>
<menuitem id="sp-menu-word-wrap"
label="&wordWrap.label;"
accesskey="&wordWrap.accesskey;"
type="checkbox"
command="sp-cmd-wrap-text"/>
<menuitem id="sp-menu-highlight-trailing-space"
label="&highlightTrailingSpace.label;"
accesskey="&highlightTrailingSpace.accesskey;"
type="checkbox"
command="sp-cmd-highlight-trailing-space"/>
<menuseparator/>
<menuitem id="sp-menu-larger-font"
label="&largerFont.label;"
key="sp-key-larger-font"
accesskey="&largerFont.accesskey;"
command="sp-cmd-larger-font"/>
<menuitem id="sp-menu-smaller-font"
label="&smallerFont.label;"
key="sp-key-smaller-font"
accesskey="&smallerFont.accesskey;"
command="sp-cmd-smaller-font"/>
<menuitem id="sp-menu-normal-size-font"
label="&normalSize.label;"
key="sp-menu-normal-font"
accesskey="&normalSize.accesskey;"
command="sp-cmd-normal-font"/>
</menupopup>
</menu>
<menu id="sp-edit-menu" label="&editMenu.label;" <menu id="sp-edit-menu" label="&editMenu.label;"
accesskey="&editMenu.accesskey;"> accesskey="&editMenu.accesskey;">
<menupopup id="sp-menu_editpopup"> <menupopup id="sp-menu_editpopup">

View File

@ -29,10 +29,16 @@ function runTests()
"sp-text-run": "run", "sp-text-run": "run",
"sp-text-inspect": "inspect", "sp-text-inspect": "inspect",
"sp-text-display": "display", "sp-text-display": "display",
"sp-text-reloadAndRun" : "reloadAndRun", "sp-text-reloadAndRun": "reloadAndRun",
"sp-menu-content": "setContentContext", "sp-menu-content": "setContentContext",
"sp-menu-browser": "setBrowserContext", "sp-menu-browser": "setBrowserContext",
"sp-menu-pprint":"prettyPrint", "sp-menu-pprint": "prettyPrint",
"sp-menu-line-numbers": "toggleEditorOption",
"sp-menu-word-wrap": "toggleEditorOption",
"sp-menu-highlight-trailing-space": "toggleEditorOption",
"sp-menu-larger-font": "increaseFontSize",
"sp-menu-smaller-font": "decreaseFontSize",
"sp-menu-normal-size-font": "normalFontSize",
}; };
let lastMethodCalled = null; let lastMethodCalled = null;

View File

@ -50,6 +50,30 @@
<!ENTITY closeCmd.key "W"> <!ENTITY closeCmd.key "W">
<!ENTITY closeCmd.accesskey "C"> <!ENTITY closeCmd.accesskey "C">
<!ENTITY viewMenu.label "View">
<!ENTITY viewMenu.accesskey "V">
<!ENTITY lineNumbers.label "Show Line Numbers">
<!ENTITY lineNumbers.accesskey "L">
<!ENTITY wordWrap.label "Wrap Text">
<!ENTITY wordWrap.accesskey "W">
<!ENTITY highlightTrailingSpace.label "Highlight Trailing Space">
<!ENTITY highlightTrailingSpace.accesskey "H">
<!ENTITY largerFont.label "Larger Font">
<!ENTITY largerFont.accesskey "a">
<!ENTITY largerFont.commandkey "+">
<!ENTITY smallerFont.label "Smaller Font">
<!ENTITY smallerFont.accesskey "M">
<!ENTITY smallerFont.commandkey "-">
<!ENTITY normalSize.label "Normal Size">
<!ENTITY normalSize.accesskey "N">
<!ENTITY normalSize.commandkey "0">
<!ENTITY editMenu.label "Edit"> <!ENTITY editMenu.label "Edit">
<!ENTITY editMenu.accesskey "E"> <!ENTITY editMenu.accesskey "E">

View File

@ -54,7 +54,7 @@
#nav-bar { #nav-bar {
background-image: linear-gradient(@toolbarHighlight@, rgba(255,255,255,0)); background-image: linear-gradient(@toolbarHighlight@, rgba(255,255,255,0));
box-shadow: 0 1px 0 @toolbarHighlight@ inset; box-shadow: 0 1px 0 @toolbarHighlight@ inset;
margin-top: -1px; /* Move up 1px into the TabsToolbar */ margin-top: -@tabToolbarNavbarOverlap@; /* Move up into the TabsToolbar */
padding-top: 2px; padding-top: 2px;
padding-bottom: 2px; padding-bottom: 2px;
/* Position the toolbar above the bottom of background tabs */ /* Position the toolbar above the bottom of background tabs */
@ -1730,7 +1730,7 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
#TabsToolbar::after { #TabsToolbar::after {
content: ""; content: "";
position: absolute; position: absolute;
bottom: 1px; bottom: @tabToolbarNavbarOverlap@;
left: 0; left: 0;
right: 0; right: 0;
z-index: 0; z-index: 0;

View File

@ -98,7 +98,7 @@ toolbarseparator {
background-position: 0 1px, 0 0; background-position: 0 1px, 0 0;
box-shadow: inset 0 1px 0 hsla(0,0%,100%,.4); box-shadow: inset 0 1px 0 hsla(0,0%,100%,.4);
margin-top: -1px; margin-top: -@tabToolbarNavbarOverlap@;
/* Position the toolbar above the bottom of background tabs */ /* Position the toolbar above the bottom of background tabs */
position: relative; position: relative;
z-index: 1; z-index: 1;
@ -1617,11 +1617,12 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
#zoom-controls[cui-areatype="toolbar"]:not([overflowedItem=true]) > #zoom-reset-button { #zoom-controls[cui-areatype="toolbar"]:not([overflowedItem=true]) > #zoom-reset-button {
min-width: 0; min-width: 0;
margin: 0; margin: 0;
-moz-box-orient: horizontal;
-moz-box-align: center;
} }
#zoom-controls[cui-areatype="toolbar"]:not([overflowedItem=true]) > #zoom-reset-button > .toolbarbutton-text { #zoom-controls[cui-areatype="toolbar"]:not([overflowedItem=true]) > #zoom-reset-button > .toolbarbutton-text {
padding-top: 4px; margin-top: 0;
margin: 2px;
} }
/* ----- FULLSCREEN WINDOW CONTROLS ----- */ /* ----- FULLSCREEN WINDOW CONTROLS ----- */
@ -2800,7 +2801,7 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
* ordinal group value (see bug 853415). */ * ordinal group value (see bug 853415). */
-moz-box-ordinal-group: 1001; -moz-box-ordinal-group: 1001;
position: absolute; position: absolute;
bottom: 1px; bottom: @tabToolbarNavbarOverlap@;
left: 0; left: 0;
right: 0; right: 0;
z-index: 0; z-index: 0;

View File

@ -11,3 +11,4 @@
%endif %endif
%define inAnyPanel :-moz-any(:not([cui-areatype="toolbar"]), [overflowedItem=true]) %define inAnyPanel :-moz-any(:not([cui-areatype="toolbar"]), [overflowedItem=true])
%define tabToolbarNavbarOverlap 1px

View File

@ -273,7 +273,7 @@
.tabbrowser-tab[pinned][titlechanged]:not([selected="true"]) > .tab-stack > .tab-content { .tabbrowser-tab[pinned][titlechanged]:not([selected="true"]) > .tab-stack > .tab-content {
background-image: radial-gradient(farthest-corner at center bottom, rgb(255,255,255) 3%, rgba(186,221,251,0.75) 20%, rgba(127,179,255,0.25) 40%, rgba(127,179,255,0) 70%); background-image: radial-gradient(farthest-corner at center bottom, rgb(255,255,255) 3%, rgba(186,221,251,0.75) 20%, rgba(127,179,255,0.25) 40%, rgba(127,179,255,0) 70%);
background-position: center bottom 1px; background-position: center bottom @tabToolbarNavbarOverlap@;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 85% 100%; background-size: 85% 100%;
} }
@ -286,12 +286,11 @@
-moz-margin-start: -1.5px; -moz-margin-start: -1.5px;
-moz-margin-end: -1.5px; -moz-margin-end: -1.5px;
background-image: url(chrome://browser/skin/tabbrowser/tab-separator.png); background-image: url(chrome://browser/skin/tabbrowser/tab-separator.png);
background-position: left bottom; background-position: left bottom @tabToolbarNavbarOverlap@;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 3px 100%; background-size: 3px 100%;
content: ""; content: "";
display: -moz-box; display: -moz-box;
margin-bottom: 1px;
width: 3px; width: 3px;
} }

View File

@ -267,7 +267,7 @@
#nav-bar { #nav-bar {
background-image: linear-gradient(@toolbarHighlight@, rgba(255,255,255,0)); background-image: linear-gradient(@toolbarHighlight@, rgba(255,255,255,0));
box-shadow: 0 1px 0 @toolbarHighlight@ inset; box-shadow: 0 1px 0 @toolbarHighlight@ inset;
margin-top: -1px; /* Move up 1px into the TabsToolbar */ margin-top: -@tabToolbarNavbarOverlap@; /* Move up into the TabsToolbar */
/* Position the toolbar above the bottom of background tabs */ /* Position the toolbar above the bottom of background tabs */
position: relative; position: relative;
z-index: 1; z-index: 1;

View File

@ -11,6 +11,7 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor; import android.content.SharedPreferences.Editor;
import android.os.Build; import android.os.Build;
import android.os.StrictMode;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
@ -151,30 +152,20 @@ public final class GeckoSharedPrefs {
return; return;
} }
if (ThreadUtils.isOnBackgroundThread()) { // We deliberatly perform the migration in the current thread (which
Log.d(LOGTAG, "Already in background thread, migrating directly"); // is likely the UI thread) as this is actually cheaper than enforcing a
// context switch to another thread (see bug 940575).
if (Build.VERSION.SDK_INT < 9) {
performMigration(context); performMigration(context);
} else { } else {
Log.d(LOGTAG, "Not in background thread, migrating with lock"); // Avoid strict mode warnings.
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
final Object migrationLock = new Object(); StrictMode.allowThreadDiskWrites();
ThreadUtils.getBackgroundHandler().post(new Runnable() {
@Override
public void run() {
synchronized(migrationLock) {
performMigration(context);
migrationLock.notifyAll();
}
}
});
try { try {
synchronized(migrationLock) { performMigration(context);
migrationLock.wait(MIGRATION_COMMIT_TIMEOUT_MSEC); } finally {
} StrictMode.setThreadPolicy(savedPolicy);
} catch (InterruptedException e) {
throw new IllegalStateException("Failed to commit migration before timeout");
} }
} }

View File

@ -83,7 +83,7 @@ public class BrowserSearch extends HomeFragment
private static final int ANIMATION_DURATION = 250; private static final int ANIMATION_DURATION = 250;
// Holds the current search term to use in the query // Holds the current search term to use in the query
private String mSearchTerm; private volatile String mSearchTerm;
// Adapter for the list of search results // Adapter for the list of search results
private SearchAdapter mAdapter; private SearchAdapter mAdapter;
@ -689,26 +689,39 @@ public class BrowserSearch extends HomeFragment
GeckoAppShell.unregisterEventListener(eventName, this); GeckoAppShell.unregisterEventListener(eventName, this);
} }
private void restartSearchLoader() {
SearchLoader.restart(getLoaderManager(), LOADER_ID_SEARCH, mCursorLoaderCallbacks, mSearchTerm);
}
private void initSearchLoader() {
SearchLoader.init(getLoaderManager(), LOADER_ID_SEARCH, mCursorLoaderCallbacks, mSearchTerm);
}
public void filter(String searchTerm, AutocompleteHandler handler) { public void filter(String searchTerm, AutocompleteHandler handler) {
if (TextUtils.isEmpty(searchTerm)) { if (TextUtils.isEmpty(searchTerm)) {
return; return;
} }
if (TextUtils.equals(mSearchTerm, searchTerm)) { final boolean isNewFilter = !TextUtils.equals(mSearchTerm, searchTerm);
return;
}
mSearchTerm = searchTerm; mSearchTerm = searchTerm;
mAutocompleteHandler = handler; mAutocompleteHandler = handler;
if (isVisible()) { if (isVisible()) {
// The adapter depends on the search term to determine its number if (isNewFilter) {
// of items. Make it we notify the view about it. // The adapter depends on the search term to determine its number
mAdapter.notifyDataSetChanged(); // of items. Make it we notify the view about it.
mAdapter.notifyDataSetChanged();
// Restart loaders with the new search term // Restart loaders with the new search term
SearchLoader.restart(getLoaderManager(), LOADER_ID_SEARCH, mCursorLoaderCallbacks, mSearchTerm); restartSearchLoader();
filterSuggestions(); filterSuggestions();
} else {
// The search term hasn't changed, simply reuse any existing
// loader for the current search term. This will ensure autocompletion
// is consistently triggered (see bug 933739).
initSearchLoader();
}
} }
} }

View File

@ -237,6 +237,9 @@ let Scheduler = {
*/ */
resetTimer: null, resetTimer: null,
/**
* Prepare to kill the OS.File worker after a few seconds.
*/
restartTimer: function(arg) { restartTimer: function(arg) {
let delay; let delay;
try { try {
@ -249,7 +252,98 @@ let Scheduler = {
if (this.resetTimer) { if (this.resetTimer) {
clearTimeout(this.resetTimer); clearTimeout(this.resetTimer);
} }
this.resetTimer = setTimeout(File.resetWorker, delay); this.resetTimer = setTimeout(
() => Scheduler.kill({reset: true, shutdown: false}),
delay
);
},
/**
* Shutdown OS.File.
*
* @param {*} options
* - {boolean} shutdown If |true|, reject any further request. Otherwise,
* further requests will resurrect the worker.
* - {boolean} reset If |true|, instruct the worker to shutdown if this
* would not cause leaks. Otherwise, assume that the worker will be shutdown
* through some other mean.
*/
kill: function({shutdown, reset}) {
return Task.spawn(function*() {
yield this.queue;
if (!this.launched || this.shutdown || !worker) {
// Nothing to kill
this.shutdown = this.shutdown || shutdown;
worker = null;
return null;
}
// Deactivate the queue, to ensure that no message is sent
// to an obsolete worker (we reactivate it in the |finally|).
let deferred = Promise.defer();
this.queue = deferred.promise;
let message = ["Meta_shutdown", [reset]];
try {
Scheduler.latestReceived = [];
Scheduler.latestSent = [Date.now(), ...message];
let promise = worker.post(...message);
// Wait for result
let resources;
try {
resources = (yield promise).ok;
Scheduler.latestReceived = [Date.now(), message];
} catch (ex) {
LOG("Could not dispatch Meta_reset", ex);
// It's most likely a programmer error, but we'll assume that
// the worker has been shutdown, as it's less risky than the
// opposite stance.
resources = {openedFiles: [], openedDirectoryIterators: [], killed: true};
Scheduler.latestReceived = [Date.now(), message, ex];
}
let {openedFiles, openedDirectoryIterators, killed} = resources;
if (!reset
&& (openedFiles && openedFiles.length
|| ( openedDirectoryIterators && openedDirectoryIterators.length))) {
// The worker still holds resources. Report them.
let msg = "";
if (openedFiles.length > 0) {
msg += "The following files are still open:\n" +
openedFiles.join("\n");
}
if (openedDirectoryIterators.length > 0) {
msg += "The following directory iterators are still open:\n" +
openedDirectoryIterators.join("\n");
}
LOG("WARNING: File descriptors leaks detected.\n" + msg);
}
// Make sure that we do not leave an invalid |worker| around.
if (killed || shutdown) {
worker = null;
}
this.shutdown = shutdown;
return resources;
} finally {
// Resume accepting messages. If we have set |shutdown| to |true|,
// any pending/future request will be rejected. Otherwise, any
// pending/future request will spawn a new worker if necessary.
deferred.resolve();
}
}.bind(this));
}, },
/** /**
@ -310,6 +404,11 @@ let Scheduler = {
// expensive, we only keep a shortened version of it. // expensive, we only keep a shortened version of it.
Scheduler.Debugging.latestReceived = null; Scheduler.Debugging.latestReceived = null;
Scheduler.Debugging.latestSent = [Date.now(), method, summarizeObject(methodArgs)]; Scheduler.Debugging.latestSent = [Date.now(), method, summarizeObject(methodArgs)];
// Don't kill the worker just yet
Scheduler.restartTimer();
let data; let data;
let reply; let reply;
let isError = false; let isError = false;
@ -346,11 +445,7 @@ let Scheduler = {
Scheduler._updateTelemetry(); Scheduler._updateTelemetry();
} }
// Don't restart the timer when reseting the worker, since that will Scheduler.restartTimer();
// lead to an endless "resetWorker()" loop.
if (method != "Meta_reset") {
Scheduler.restartTimer();
}
} }
// Check for duration and return result. // Check for duration and return result.
@ -470,54 +565,9 @@ const WEB_WORKERS_SHUTDOWN_TOPIC = "web-workers-shutdown";
const PREF_OSFILE_TEST_SHUTDOWN_OBSERVER = const PREF_OSFILE_TEST_SHUTDOWN_OBSERVER =
"toolkit.osfile.test.shutdown.observer"; "toolkit.osfile.test.shutdown.observer";
/**
* A condition function meant to be used during phase
* webWorkersShutdown, to warn about unclosed files and directories
* and reconfigure the Scheduler to reject further requests.
*
* @param {bool=} shutdown If true or unspecified, reconfigure
* the scheduler to reject further requests. Can be set to |false|
* for testing purposes.
* @return {promise} A promise satisfied once all pending messages
* (including the shutdown warning message) have been answered.
*/
function warnAboutUnclosedFiles(shutdown = true) {
if (!Scheduler.launched || !worker) {
// Don't launch the scheduler on our behalf. If no message has been
// sent to the worker, we can't have any leaking file/directory
// descriptor.
return null;
}
let promise = Scheduler.post("Meta_getUnclosedResources");
// Configure the worker to reject any further message.
if (shutdown) {
Scheduler.shutdown = true;
}
return promise.then(function onSuccess(opened) {
let msg = "";
if (opened.openedFiles.length > 0) {
msg += "The following files are still open:\n" +
opened.openedFiles.join("\n");
}
if (msg) {
msg += "\n";
}
if (opened.openedDirectoryIterators.length > 0) {
msg += "The following directory iterators are still open:\n" +
opened.openedDirectoryIterators.join("\n");
}
// Only log if file descriptors leaks detected.
if (msg) {
LOG("WARNING: File descriptors leaks detected.\n" + msg);
}
});
};
AsyncShutdown.webWorkersShutdown.addBlocker( AsyncShutdown.webWorkersShutdown.addBlocker(
"OS.File: flush pending requests, warn about unclosed files, shut down service.", "OS.File: flush pending requests, warn about unclosed files, shut down service.",
() => warnAboutUnclosedFiles(true) () => Scheduler.kill({reset: false, shutdown: true})
); );
@ -542,7 +592,7 @@ Services.prefs.addObserver(PREF_OSFILE_TEST_SHUTDOWN_OBSERVER,
let phase = AsyncShutdown._getPhase(TOPIC); let phase = AsyncShutdown._getPhase(TOPIC);
phase.addBlocker( phase.addBlocker(
"(for testing purposes) OS.File: warn about unclosed files", "(for testing purposes) OS.File: warn about unclosed files",
() => warnAboutUnclosedFiles(false) () => Scheduler.kill({shutdown: false, reset: false})
); );
} }
}, false); }, false);
@ -1366,44 +1416,15 @@ DirectoryIterator.Entry.fromMsg = function fromMsg(value) {
return new DirectoryIterator.Entry(value); return new DirectoryIterator.Entry(value);
}; };
/**
* Flush all operations currently queued, then kill the underlying
* worker to save memory.
*
* @return {Promise}
* @reject {Error} If at least one file or directory iterator instance
* is still open and the worker cannot be killed safely.
*/
File.resetWorker = function() { File.resetWorker = function() {
if (!Scheduler.launched || Scheduler.shutdown) { return Task.spawn(function*() {
// No need to reset let resources = yield Scheduler.kill({shutdown: false, reset: true});
return Promise.resolve(); if (resources && !resources.killed) {
} throw new Error("Could not reset worker, this would leak file descriptors: " + JSON.stringify(resources));
return Scheduler.post("Meta_reset").then(
function(wouldLeak) {
if (!wouldLeak) {
// No resource would leak, the worker was stopped.
worker = null;
return;
}
// Otherwise, resetting would be unsafe and has been canceled.
// Turn this into an error
let msg = "Cannot reset worker: ";
let {openedFiles, openedDirectoryIterators} = wouldLeak;
if (openedFiles.length > 0) {
msg += "The following files are still open:\n" +
openedFiles.join("\n");
}
if (openedDirectoryIterators.length > 0) {
msg += "The following directory iterators are still open:\n" +
openedDirectoryIterators.join("\n");
}
throw new Error(msg);
} }
); });
}; };
// Constants // Constants
File.POS_START = SysAll.POS_START; File.POS_START = SysAll.POS_START;
File.POS_CURRENT = SysAll.POS_CURRENT; File.POS_CURRENT = SysAll.POS_CURRENT;

View File

@ -272,34 +272,25 @@ const EXCEPTION_NAMES = {
GET_DEBUG: function() { GET_DEBUG: function() {
return SharedAll.Config.DEBUG; return SharedAll.Config.DEBUG;
}, },
Meta_getUnclosedResources: function() { /**
// Return information about both opened files and opened * Execute shutdown sequence, returning data on leaked file descriptors.
// directory iterators. *
return { * @param {bool} If |true|, kill the worker if this would not cause
* leaks.
*/
Meta_shutdown: function(kill) {
let result = {
openedFiles: OpenedFiles.listOpenedResources(), openedFiles: OpenedFiles.listOpenedResources(),
openedDirectoryIterators: OpenedDirectoryIterators.listOpenedResources() openedDirectoryIterators: OpenedDirectoryIterators.listOpenedResources(),
killed: false // Placeholder
}; };
},
Meta_reset: function() { // Is it safe to kill the worker?
// Attempt to stop the worker. This fails if at least one let safe = result.openedFiles.length == 0
// resource is still open. Returns the list of files and && result.openedDirectoryIterators.length == 0;
// directory iterators that cannot be closed safely (or undefined result.killed = safe && kill;
// if there are no such files/directory iterators).
let openedFiles = OpenedFiles.listOpenedResources(); return new Meta(result, {shutdown: result.killed});
let openedDirectoryIterators =
OpenedDirectoryIterators.listOpenedResources();
let canShutdown = openedFiles.length == 0
&& openedDirectoryIterators.length == 0;
if (canShutdown) {
// Succeed. Shutdown the thread, nothing to return
return new Meta(null, {shutdown: true});
} else {
// Fail. Don't shutdown the thread, return info on resources
return {
openedFiles: openedFiles,
openedDirectoryIterators: openedDirectoryIterators
};
}
}, },
// Functions of OS.File // Functions of OS.File
stat: function stat(path, options) { stat: function stat(path, options) {

View File

@ -37,7 +37,7 @@ add_task(function file_open_cannot_reset() {
let thrown = false; let thrown = false;
try { try {
yield OS.File.resetWorker(); yield OS.File.resetWorker();
} catch (ex if ex.message.indexOf(TEST_FILE) != -1 ) { } catch (ex if ex.message.indexOf(OS.Path.basename(TEST_FILE)) != -1 ) {
thrown = true; thrown = true;
} }
do_check_true(thrown); do_check_true(thrown);
@ -47,14 +47,14 @@ add_task(function file_open_cannot_reset() {
yield OS.File.resetWorker(); yield OS.File.resetWorker();
}); });
add_task(function file_open_cannot_reset() { add_task(function dir_open_cannot_reset() {
let TEST_DIR = yield OS.File.getCurrentDirectory(); let TEST_DIR = yield OS.File.getCurrentDirectory();
do_print("Leaking directory " + TEST_DIR + ", we shouldn't be able to reset"); do_print("Leaking directory " + TEST_DIR + ", we shouldn't be able to reset");
let iterator = new OS.File.DirectoryIterator(TEST_DIR); let iterator = new OS.File.DirectoryIterator(TEST_DIR);
let thrown = false; let thrown = false;
try { try {
yield OS.File.resetWorker(); yield OS.File.resetWorker();
} catch (ex if ex.message.indexOf(TEST_DIR) != -1 ) { } catch (ex if ex.message.indexOf(OS.Path.basename(TEST_DIR)) != -1 ) {
thrown = true; thrown = true;
} }
do_check_true(thrown); do_check_true(thrown);

View File

@ -89,7 +89,7 @@ add_task(function system_shutdown() {
do_check_true((yield testLeaksOf(TEST_FILE, "test.shutdown.file.leak"))); do_check_true((yield testLeaksOf(TEST_FILE, "test.shutdown.file.leak")));
yield openedFile.close(); yield openedFile.close();
do_print("At this stage, we don't leak the file anymore"); do_print("At this stage, we don't leak the file anymore");
do_check_false((yield testLeaksOf(TEST_FILE, "test.shutdown.file.leak"))); do_check_false((yield testLeaksOf(TEST_FILE, "test.shutdown.file.leak.2")));
}); });

View File

@ -605,18 +605,29 @@ var AddonManagerInternal = {
try { try {
defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED); defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED);
} catch (e) {} } catch (e) {}
AddonManagerPrivate.recordSimpleMeasure("default_providers", defaultProvidersEnabled);
// Ensure all default providers have had a chance to register themselves // Ensure all default providers have had a chance to register themselves
if (defaultProvidersEnabled) { if (defaultProvidersEnabled) {
DEFAULT_PROVIDERS.forEach(function(url) { for (let url of DEFAULT_PROVIDERS) {
try { try {
Components.utils.import(url, {}); let scope = {};
Components.utils.import(url, scope);
// Sanity check - make sure the provider exports a symbol that
// has a 'startup' method
let syms = Object.keys(scope);
if ((syms.length < 1) ||
(typeof scope[syms[0]].startup != "function")) {
logger.warn("Provider " + url + " has no startup()");
AddonManagerPrivate.recordException("AMI", "provider " + url, "no startup()");
}
logger.debug("Loaded provider scope for " + url + ": " + Object.keys(scope).toSource());
} }
catch (e) { catch (e) {
AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e); AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e);
logger.error("Exception loading default provider \"" + url + "\"", e); logger.error("Exception loading default provider \"" + url + "\"", e);
} }
}); };
} }
// Load any providers registered in the category manager // Load any providers registered in the category manager
@ -772,6 +783,7 @@ var AddonManagerInternal = {
provider[aMethod].apply(provider, aArgs); provider[aMethod].apply(provider, aArgs);
} }
catch (e) { catch (e) {
AddonManagerPrivate.recordException("AMI", "provider " + aMethod, e);
logger.error("Exception calling provider " + aMethod, e); logger.error("Exception calling provider " + aMethod, e);
} }
} }

View File

@ -9,7 +9,7 @@ const Ci = Components.interfaces;
const Cr = Components.results; const Cr = Components.results;
const Cu = Components.utils; const Cu = Components.utils;
this.EXPORTED_SYMBOLS = []; this.EXPORTED_SYMBOLS = ["XPIProvider"];
Components.utils.import("resource://gre/modules/Services.jsm"); Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
@ -1545,7 +1545,7 @@ function makeSafe(aFunction) {
} }
} }
var XPIProvider = { this.XPIProvider = {
// An array of known install locations // An array of known install locations
installLocations: null, installLocations: null,
// A dictionary of known install locations by name // A dictionary of known install locations by name
@ -1758,23 +1758,6 @@ var XPIProvider = {
* if it is a new profile or the version is unknown * if it is a new profile or the version is unknown
*/ */
startup: function XPI_startup(aAppChanged, aOldAppVersion, aOldPlatformVersion) { startup: function XPI_startup(aAppChanged, aOldAppVersion, aOldPlatformVersion) {
logger.debug("startup");
this.runPhase = XPI_STARTING;
this.installs = [];
this.installLocations = [];
this.installLocationsByName = {};
// Hook for tests to detect when saving database at shutdown time fails
this._shutdownError = null;
// Clear this at startup for xpcshell test restarts
this._telemetryDetails = {};
// Clear the set of enabled experiments (experiments disabled by default).
this._enabledExperiments = new Set();
// Register our details structure with AddonManager
AddonManagerPrivate.setTelemetryDetails("XPI", this._telemetryDetails);
AddonManagerPrivate.recordTimestamp("XPI_startup_begin");
function addDirectoryInstallLocation(aName, aKey, aPaths, aScope, aLocked) { function addDirectoryInstallLocation(aName, aKey, aPaths, aScope, aLocked) {
try { try {
var dir = FileUtils.getDir(aKey, aPaths); var dir = FileUtils.getDir(aKey, aPaths);
@ -1811,6 +1794,22 @@ var XPIProvider = {
} }
try { try {
AddonManagerPrivate.recordTimestamp("XPI_startup_begin");
logger.debug("startup");
this.runPhase = XPI_STARTING;
this.installs = [];
this.installLocations = [];
this.installLocationsByName = {};
// Hook for tests to detect when saving database at shutdown time fails
this._shutdownError = null;
// Clear this at startup for xpcshell test restarts
this._telemetryDetails = {};
// Clear the set of enabled experiments (experiments disabled by default).
this._enabledExperiments = new Set();
// Register our details structure with AddonManager
AddonManagerPrivate.setTelemetryDetails("XPI", this._telemetryDetails);
let hasRegistry = ("nsIWindowsRegKey" in Ci); let hasRegistry = ("nsIWindowsRegKey" in Ci);
let enabledScopes = Prefs.getIntPref(PREF_EM_ENABLED_SCOPES, let enabledScopes = Prefs.getIntPref(PREF_EM_ENABLED_SCOPES,

View File

@ -68,10 +68,18 @@ html|html {
background-image: linear-gradient(rgba(255, 255, 255, 0), background-image: linear-gradient(rgba(255, 255, 255, 0),
rgba(255, 255, 255, 0.75)); rgba(255, 255, 255, 0.75));
border: 1px solid #C3CEDF; border: 1px solid #C3CEDF;
border-radius: 5px;
} }
%ifdef WINDOWS_AERO %ifdef WINDOWS_AERO
@media (-moz-os-version: windows-vista),
(-moz-os-version: windows-win7) {
%endif
*|*.main-content {
border-radius: 5px;
}
%ifdef WINDOWS_AERO
}
@media (-moz-windows-glass) { @media (-moz-windows-glass) {
/* Buttons */ /* Buttons */
*|button, *|button,

View File

@ -181,15 +181,22 @@
overflow: hidden; overflow: hidden;
} }
.category:-moz-locale-dir(ltr) { %ifdef WINDOWS_AERO
border-top-left-radius: 5px; @media (-moz-os-version: windows-vista),
border-bottom-left-radius: 5px; (-moz-os-version: windows-win7) {
} %endif
.category:-moz-locale-dir(ltr) {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.category:-moz-locale-dir(rtl) { .category:-moz-locale-dir(rtl) {
border-top-right-radius: 5px; border-top-right-radius: 5px;
border-bottom-right-radius: 5px; border-bottom-right-radius: 5px;
}
%ifdef WINDOWS_AERO
} }
%endif
.category[disabled] { .category[disabled] {
border-top: 0; border-top: 0;