mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge mozilla-central and fx-team
This commit is contained in:
commit
757c230f65
@ -73,10 +73,12 @@
|
||||
accesskey="&emailPageCmd.accesskey;"
|
||||
command="Browser:SendLink"/>
|
||||
<menuseparator/>
|
||||
#ifndef XP_LINUX
|
||||
<menuitem id="menu_printSetup"
|
||||
label="&printSetupCmd.label;"
|
||||
accesskey="&printSetupCmd.accesskey;"
|
||||
command="cmd_pageSetup"/>
|
||||
#endif
|
||||
#ifndef XP_MACOSX
|
||||
<menuitem id="menu_printPreview"
|
||||
label="&printPreviewCmd.label;"
|
||||
|
@ -884,6 +884,10 @@ let PlacesToolbarHelper = {
|
||||
if (!viewElt || viewElt._placesView)
|
||||
return;
|
||||
|
||||
// CustomizableUI.addListener is idempotent, so we can safely
|
||||
// call this multiple times.
|
||||
CustomizableUI.addListener(this);
|
||||
|
||||
// If the bookmarks toolbar item is:
|
||||
// - not in a toolbar, or;
|
||||
// - the toolbar is collapsed, or;
|
||||
@ -899,7 +903,11 @@ let PlacesToolbarHelper = {
|
||||
if (forceToolbarOverflowCheck) {
|
||||
viewElt._placesView.updateOverflowStatus();
|
||||
}
|
||||
this.customizeChange();
|
||||
this._setupPlaceholder();
|
||||
},
|
||||
|
||||
uninit: function PTH_uninit() {
|
||||
CustomizableUI.removeListener(this);
|
||||
},
|
||||
|
||||
customizeStart: function PTH_customizeStart() {
|
||||
@ -914,10 +922,15 @@ let PlacesToolbarHelper = {
|
||||
},
|
||||
|
||||
customizeChange: function PTH_customizeChange() {
|
||||
this._setupPlaceholder();
|
||||
},
|
||||
|
||||
_setupPlaceholder: function PTH_setupPlaceholder() {
|
||||
let placeholder = this._placeholder;
|
||||
if (!placeholder) {
|
||||
return;
|
||||
}
|
||||
|
||||
let shouldWrapNow = this._getShouldWrap();
|
||||
if (this._shouldWrap != shouldWrapNow) {
|
||||
if (shouldWrapNow) {
|
||||
@ -958,7 +971,40 @@ let PlacesToolbarHelper = {
|
||||
element = element.parentNode;
|
||||
}
|
||||
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.
|
||||
if (this.button._placesView)
|
||||
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) {
|
||||
|
@ -1289,6 +1289,8 @@ var gBrowserInit = {
|
||||
} catch (ex) {
|
||||
}
|
||||
|
||||
PlacesToolbarHelper.uninit();
|
||||
|
||||
BookmarkingUI.uninit();
|
||||
|
||||
TabsInTitlebar.uninit();
|
||||
|
@ -93,6 +93,10 @@ skip-if = os == "linux"
|
||||
[browser_978084_dragEnd_after_move.js]
|
||||
[browser_980155_add_overflow_toolbar.js]
|
||||
[browser_981418-widget-onbeforecreated-handler.js]
|
||||
|
||||
[browser_984455_bookmarks_items_reparenting.js]
|
||||
skip-if = os == "linux"
|
||||
|
||||
[browser_985815_propagate_setToolbarVisibility.js]
|
||||
[browser_981305_separator_insertion.js]
|
||||
[browser_987177_destroyWidget_xul.js]
|
||||
|
@ -10,7 +10,7 @@ const isOSX = (Services.appinfo.OS === "Darwin");
|
||||
// show a context menu with options to move it.
|
||||
add_task(function() {
|
||||
let contextMenu = document.getElementById("toolbar-context-menu");
|
||||
let shownPromise = contextMenuShown(contextMenu);
|
||||
let shownPromise = popupShown(contextMenu);
|
||||
let homeButton = document.getElementById("home-button");
|
||||
EventUtils.synthesizeMouse(homeButton, 2, 2, {type: "contextmenu", button: 2 });
|
||||
yield shownPromise;
|
||||
@ -30,7 +30,7 @@ add_task(function() {
|
||||
);
|
||||
checkContextMenu(contextMenu, expectedEntries);
|
||||
|
||||
let hiddenPromise = contextMenuHidden(contextMenu);
|
||||
let hiddenPromise = popupHidden(contextMenu);
|
||||
contextMenu.hidePopup();
|
||||
yield hiddenPromise;
|
||||
});
|
||||
@ -40,7 +40,7 @@ add_task(function() {
|
||||
// and a toggle option for the extra toolbar
|
||||
add_task(function() {
|
||||
let contextMenu = document.getElementById("toolbar-context-menu");
|
||||
let shownPromise = contextMenuShown(contextMenu);
|
||||
let shownPromise = popupShown(contextMenu);
|
||||
let toolbar = createToolbarWithPlacements("880164_empty_toolbar", []);
|
||||
toolbar.setAttribute("context", "toolbar-context-menu");
|
||||
toolbar.setAttribute("toolbarname", "Fancy Toolbar for Context Menu");
|
||||
@ -63,7 +63,7 @@ add_task(function() {
|
||||
);
|
||||
checkContextMenu(contextMenu, expectedEntries);
|
||||
|
||||
let hiddenPromise = contextMenuHidden(contextMenu);
|
||||
let hiddenPromise = popupHidden(contextMenu);
|
||||
contextMenu.hidePopup();
|
||||
yield hiddenPromise;
|
||||
removeCustomToolbars();
|
||||
@ -74,7 +74,7 @@ add_task(function() {
|
||||
// show a context menu with disabled options to move it.
|
||||
add_task(function() {
|
||||
let contextMenu = document.getElementById("toolbar-context-menu");
|
||||
let shownPromise = contextMenuShown(contextMenu);
|
||||
let shownPromise = popupShown(contextMenu);
|
||||
let urlBarContainer = document.getElementById("urlbar-container");
|
||||
// Need to make sure not to click within an edit field.
|
||||
let urlbarRect = urlBarContainer.getBoundingClientRect();
|
||||
@ -96,7 +96,7 @@ add_task(function() {
|
||||
);
|
||||
checkContextMenu(contextMenu, expectedEntries);
|
||||
|
||||
let hiddenPromise = contextMenuHidden(contextMenu);
|
||||
let hiddenPromise = popupHidden(contextMenu);
|
||||
contextMenu.hidePopup();
|
||||
yield hiddenPromise;
|
||||
});
|
||||
@ -135,7 +135,7 @@ add_task(function() {
|
||||
yield shownPanelPromise;
|
||||
|
||||
let contextMenu = document.getElementById("customizationPanelItemContextMenu");
|
||||
let shownContextPromise = contextMenuShown(contextMenu);
|
||||
let shownContextPromise = popupShown(contextMenu);
|
||||
let newWindowButton = document.getElementById("new-window-button");
|
||||
ok(newWindowButton, "new-window-button was found");
|
||||
EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2});
|
||||
@ -151,7 +151,7 @@ add_task(function() {
|
||||
];
|
||||
checkContextMenu(contextMenu, expectedEntries);
|
||||
|
||||
let hiddenContextPromise = contextMenuHidden(contextMenu);
|
||||
let hiddenContextPromise = popupHidden(contextMenu);
|
||||
contextMenu.hidePopup();
|
||||
yield hiddenContextPromise;
|
||||
|
||||
@ -165,7 +165,7 @@ add_task(function() {
|
||||
add_task(function() {
|
||||
yield startCustomizing();
|
||||
let contextMenu = document.getElementById("toolbar-context-menu");
|
||||
let shownPromise = contextMenuShown(contextMenu);
|
||||
let shownPromise = popupShown(contextMenu);
|
||||
let homeButton = document.getElementById("wrapper-home-button");
|
||||
EventUtils.synthesizeMouse(homeButton, 2, 2, {type: "contextmenu", button: 2});
|
||||
yield shownPromise;
|
||||
@ -185,7 +185,7 @@ add_task(function() {
|
||||
);
|
||||
checkContextMenu(contextMenu, expectedEntries);
|
||||
|
||||
let hiddenContextPromise = contextMenuHidden(contextMenu);
|
||||
let hiddenContextPromise = popupHidden(contextMenu);
|
||||
contextMenu.hidePopup();
|
||||
yield hiddenContextPromise;
|
||||
});
|
||||
@ -194,7 +194,7 @@ add_task(function() {
|
||||
// show a context menu with options to move it.
|
||||
add_task(function() {
|
||||
let contextMenu = document.getElementById("customizationPaletteItemContextMenu");
|
||||
let shownPromise = contextMenuShown(contextMenu);
|
||||
let shownPromise = popupShown(contextMenu);
|
||||
let openFileButton = document.getElementById("wrapper-open-file-button");
|
||||
EventUtils.synthesizeMouse(openFileButton, 2, 2, {type: "contextmenu", button: 2});
|
||||
yield shownPromise;
|
||||
@ -205,7 +205,7 @@ add_task(function() {
|
||||
];
|
||||
checkContextMenu(contextMenu, expectedEntries);
|
||||
|
||||
let hiddenContextPromise = contextMenuHidden(contextMenu);
|
||||
let hiddenContextPromise = popupHidden(contextMenu);
|
||||
contextMenu.hidePopup();
|
||||
yield hiddenContextPromise;
|
||||
});
|
||||
@ -214,7 +214,7 @@ add_task(function() {
|
||||
// should show a context menu with options to move it.
|
||||
add_task(function() {
|
||||
let contextMenu = document.getElementById("customizationPanelItemContextMenu");
|
||||
let shownPromise = contextMenuShown(contextMenu);
|
||||
let shownPromise = popupShown(contextMenu);
|
||||
let newWindowButton = document.getElementById("wrapper-new-window-button");
|
||||
EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2});
|
||||
yield shownPromise;
|
||||
@ -227,7 +227,7 @@ add_task(function() {
|
||||
];
|
||||
checkContextMenu(contextMenu, expectedEntries);
|
||||
|
||||
let hiddenContextPromise = contextMenuHidden(contextMenu);
|
||||
let hiddenContextPromise = popupHidden(contextMenu);
|
||||
contextMenu.hidePopup();
|
||||
yield hiddenContextPromise;
|
||||
yield endCustomizing();
|
||||
@ -241,7 +241,7 @@ add_task(function() {
|
||||
yield startCustomizing(this.otherWin);
|
||||
|
||||
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");
|
||||
EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2}, this.otherWin);
|
||||
yield shownPromise;
|
||||
@ -254,7 +254,7 @@ add_task(function() {
|
||||
];
|
||||
checkContextMenu(contextMenu, expectedEntries, this.otherWin);
|
||||
|
||||
let hiddenContextPromise = contextMenuHidden(contextMenu);
|
||||
let hiddenContextPromise = popupHidden(contextMenu);
|
||||
contextMenu.hidePopup();
|
||||
yield hiddenContextPromise;
|
||||
yield endCustomizing(this.otherWin);
|
||||
@ -267,13 +267,13 @@ add_task(function() {
|
||||
add_task(function() {
|
||||
yield startCustomizing();
|
||||
let contextMenu = document.getElementById("customizationPanelItemContextMenu");
|
||||
let shownPromise = contextMenuShown(contextMenu);
|
||||
let shownPromise = popupShown(contextMenu);
|
||||
let zoomControls = document.getElementById("wrapper-zoom-controls");
|
||||
EventUtils.synthesizeMouse(zoomControls, 2, 2, {type: "contextmenu", button: 2});
|
||||
yield shownPromise;
|
||||
// Execute the command to move the item from the panel to the toolbar.
|
||||
contextMenu.childNodes[0].doCommand();
|
||||
let hiddenPromise = contextMenuHidden(contextMenu);
|
||||
let hiddenPromise = popupHidden(contextMenu);
|
||||
contextMenu.hidePopup();
|
||||
yield hiddenPromise;
|
||||
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");
|
||||
|
||||
contextMenu = document.getElementById("toolbar-context-menu");
|
||||
shownPromise = contextMenuShown(contextMenu);
|
||||
shownPromise = popupShown(contextMenu);
|
||||
EventUtils.synthesizeMouse(zoomControls, 2, 2, {type: "contextmenu", button: 2});
|
||||
yield shownPromise;
|
||||
|
||||
@ -301,7 +301,7 @@ add_task(function() {
|
||||
);
|
||||
checkContextMenu(contextMenu, expectedEntries);
|
||||
|
||||
hiddenPromise = contextMenuHidden(contextMenu);
|
||||
hiddenPromise = popupHidden(contextMenu);
|
||||
contextMenu.hidePopup();
|
||||
yield hiddenPromise;
|
||||
yield resetCustomization();
|
||||
@ -315,7 +315,7 @@ add_task(function() {
|
||||
yield PanelUI.show();
|
||||
|
||||
let contextMenu = document.getElementById("customizationPanelItemContextMenu");
|
||||
let shownContextPromise = contextMenuShown(contextMenu);
|
||||
let shownContextPromise = popupShown(contextMenu);
|
||||
let newWindowButton = document.getElementById("new-window-button");
|
||||
ok(newWindowButton, "new-window-button was found");
|
||||
EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2});
|
||||
@ -331,7 +331,7 @@ add_task(function() {
|
||||
];
|
||||
checkContextMenu(contextMenu, expectedEntries);
|
||||
|
||||
let hiddenContextPromise = contextMenuHidden(contextMenu);
|
||||
let hiddenContextPromise = popupHidden(contextMenu);
|
||||
contextMenu.hidePopup();
|
||||
yield hiddenContextPromise;
|
||||
|
||||
|
@ -27,7 +27,7 @@ add_task(function() {
|
||||
yield shownPanelPromise;
|
||||
|
||||
let contextMenu = document.getElementById("toolbar-context-menu");
|
||||
let shownContextPromise = contextMenuShown(contextMenu);
|
||||
let shownContextPromise = popupShown(contextMenu);
|
||||
let homeButton = document.getElementById("home-button");
|
||||
ok(homeButton, "home-button was found");
|
||||
is(homeButton.getAttribute("overflowedItem"), "true", "Home button is overflowing");
|
||||
@ -51,7 +51,7 @@ add_task(function() {
|
||||
);
|
||||
checkContextMenu(contextMenu, expectedEntries);
|
||||
|
||||
let hiddenContextPromise = contextMenuHidden(contextMenu);
|
||||
let hiddenContextPromise = popupHidden(contextMenu);
|
||||
let hiddenPromise = promisePanelElementHidden(window, overflowPanel);
|
||||
let moveToPanel = contextMenu.querySelector(".customize-context-moveToPanel");
|
||||
if (moveToPanel) {
|
||||
|
@ -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();
|
||||
});
|
@ -428,36 +428,49 @@ function promiseTabHistoryNavigation(aDirection = -1, aConditionFn) {
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function contextMenuShown(aContextMenu) {
|
||||
let deferred = Promise.defer();
|
||||
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 popupShown(aPopup) {
|
||||
return promisePopupEvent(aPopup, "shown");
|
||||
}
|
||||
|
||||
function contextMenuHidden(aContextMenu) {
|
||||
let deferred = Promise.defer();
|
||||
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;
|
||||
function popupHidden(aPopup) {
|
||||
return promisePopupEvent(aPopup, "hidden");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
// exists in contextmenu_common.js.
|
||||
|
@ -960,6 +960,10 @@ PlacesToolbar.prototype = {
|
||||
this._removeEventListeners(window, ["resize", "unload"], false);
|
||||
this._removeEventListeners(gBrowser.tabContainer, ["TabOpen", "TabClose"], false);
|
||||
|
||||
if (this._chevron._placesView) {
|
||||
this._chevron._placesView.uninit();
|
||||
}
|
||||
|
||||
PlacesViewBase.prototype.uninit.apply(this, arguments);
|
||||
},
|
||||
|
||||
|
@ -26,6 +26,10 @@ const BUTTON_POSITION_DONT_SAVE = 2;
|
||||
const BUTTON_POSITION_REVERT = 0;
|
||||
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 DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
|
||||
const PREF_RECENT_FILES_MAX = "devtools.scratchpad.recentFilesMax";
|
||||
@ -212,7 +216,25 @@ var Scratchpad = {
|
||||
},
|
||||
"sp-cmd-hideSidebar": () => {
|
||||
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) {
|
||||
@ -1779,6 +1801,47 @@ var Scratchpad = {
|
||||
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: [],
|
||||
|
||||
/**
|
||||
|
@ -47,6 +47,12 @@
|
||||
<command id="sp-cmd-saveas" oncommand=";"/>
|
||||
<command id="sp-cmd-revert" oncommand=";" disabled="true"/>
|
||||
<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-inspect" oncommand=";"/>
|
||||
<command id="sp-cmd-display" oncommand=";"/>
|
||||
@ -80,6 +86,18 @@
|
||||
key="&closeCmd.key;"
|
||||
command="sp-cmd-close"
|
||||
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="&run.key;"
|
||||
command="sp-cmd-run"
|
||||
@ -166,6 +184,43 @@
|
||||
</menupopup>
|
||||
</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;"
|
||||
accesskey="&editMenu.accesskey;">
|
||||
<menupopup id="sp-menu_editpopup">
|
||||
|
@ -29,10 +29,16 @@ function runTests()
|
||||
"sp-text-run": "run",
|
||||
"sp-text-inspect": "inspect",
|
||||
"sp-text-display": "display",
|
||||
"sp-text-reloadAndRun" : "reloadAndRun",
|
||||
"sp-text-reloadAndRun": "reloadAndRun",
|
||||
"sp-menu-content": "setContentContext",
|
||||
"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;
|
||||
|
@ -50,6 +50,30 @@
|
||||
<!ENTITY closeCmd.key "W">
|
||||
<!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.accesskey "E">
|
||||
|
||||
|
@ -54,7 +54,7 @@
|
||||
#nav-bar {
|
||||
background-image: linear-gradient(@toolbarHighlight@, rgba(255,255,255,0));
|
||||
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-bottom: 2px;
|
||||
/* 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 {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 1px;
|
||||
bottom: @tabToolbarNavbarOverlap@;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 0;
|
||||
|
@ -98,7 +98,7 @@ toolbarseparator {
|
||||
background-position: 0 1px, 0 0;
|
||||
|
||||
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: relative;
|
||||
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 {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
-moz-box-orient: horizontal;
|
||||
-moz-box-align: center;
|
||||
}
|
||||
|
||||
#zoom-controls[cui-areatype="toolbar"]:not([overflowedItem=true]) > #zoom-reset-button > .toolbarbutton-text {
|
||||
padding-top: 4px;
|
||||
margin: 2px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* ----- FULLSCREEN WINDOW CONTROLS ----- */
|
||||
@ -2800,7 +2801,7 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
||||
* ordinal group value (see bug 853415). */
|
||||
-moz-box-ordinal-group: 1001;
|
||||
position: absolute;
|
||||
bottom: 1px;
|
||||
bottom: @tabToolbarNavbarOverlap@;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 0;
|
||||
|
@ -11,3 +11,4 @@
|
||||
%endif
|
||||
|
||||
%define inAnyPanel :-moz-any(:not([cui-areatype="toolbar"]), [overflowedItem=true])
|
||||
%define tabToolbarNavbarOverlap 1px
|
||||
|
@ -273,7 +273,7 @@
|
||||
|
||||
.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-position: center bottom 1px;
|
||||
background-position: center bottom @tabToolbarNavbarOverlap@;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 85% 100%;
|
||||
}
|
||||
@ -286,12 +286,11 @@
|
||||
-moz-margin-start: -1.5px;
|
||||
-moz-margin-end: -1.5px;
|
||||
background-image: url(chrome://browser/skin/tabbrowser/tab-separator.png);
|
||||
background-position: left bottom;
|
||||
background-position: left bottom @tabToolbarNavbarOverlap@;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 3px 100%;
|
||||
content: "";
|
||||
display: -moz-box;
|
||||
margin-bottom: 1px;
|
||||
width: 3px;
|
||||
}
|
||||
|
||||
|
@ -267,7 +267,7 @@
|
||||
#nav-bar {
|
||||
background-image: linear-gradient(@toolbarHighlight@, rgba(255,255,255,0));
|
||||
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: relative;
|
||||
z-index: 1;
|
||||
|
@ -11,6 +11,7 @@ import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.os.Build;
|
||||
import android.os.StrictMode;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
@ -151,30 +152,20 @@ public final class GeckoSharedPrefs {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ThreadUtils.isOnBackgroundThread()) {
|
||||
Log.d(LOGTAG, "Already in background thread, migrating directly");
|
||||
// We deliberatly perform the migration in the current thread (which
|
||||
// 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);
|
||||
} else {
|
||||
Log.d(LOGTAG, "Not in background thread, migrating with lock");
|
||||
|
||||
final Object migrationLock = new Object();
|
||||
|
||||
ThreadUtils.getBackgroundHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized(migrationLock) {
|
||||
performMigration(context);
|
||||
migrationLock.notifyAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
// Avoid strict mode warnings.
|
||||
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
|
||||
StrictMode.allowThreadDiskWrites();
|
||||
|
||||
try {
|
||||
synchronized(migrationLock) {
|
||||
migrationLock.wait(MIGRATION_COMMIT_TIMEOUT_MSEC);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new IllegalStateException("Failed to commit migration before timeout");
|
||||
performMigration(context);
|
||||
} finally {
|
||||
StrictMode.setThreadPolicy(savedPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ public class BrowserSearch extends HomeFragment
|
||||
private static final int ANIMATION_DURATION = 250;
|
||||
|
||||
// Holds the current search term to use in the query
|
||||
private String mSearchTerm;
|
||||
private volatile String mSearchTerm;
|
||||
|
||||
// Adapter for the list of search results
|
||||
private SearchAdapter mAdapter;
|
||||
@ -689,26 +689,39 @@ public class BrowserSearch extends HomeFragment
|
||||
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) {
|
||||
if (TextUtils.isEmpty(searchTerm)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (TextUtils.equals(mSearchTerm, searchTerm)) {
|
||||
return;
|
||||
}
|
||||
final boolean isNewFilter = !TextUtils.equals(mSearchTerm, searchTerm);
|
||||
|
||||
mSearchTerm = searchTerm;
|
||||
mAutocompleteHandler = handler;
|
||||
|
||||
if (isVisible()) {
|
||||
// The adapter depends on the search term to determine its number
|
||||
// of items. Make it we notify the view about it.
|
||||
mAdapter.notifyDataSetChanged();
|
||||
if (isNewFilter) {
|
||||
// The adapter depends on the search term to determine its number
|
||||
// of items. Make it we notify the view about it.
|
||||
mAdapter.notifyDataSetChanged();
|
||||
|
||||
// Restart loaders with the new search term
|
||||
SearchLoader.restart(getLoaderManager(), LOADER_ID_SEARCH, mCursorLoaderCallbacks, mSearchTerm);
|
||||
filterSuggestions();
|
||||
// Restart loaders with the new search term
|
||||
restartSearchLoader();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,6 +237,9 @@ let Scheduler = {
|
||||
*/
|
||||
resetTimer: null,
|
||||
|
||||
/**
|
||||
* Prepare to kill the OS.File worker after a few seconds.
|
||||
*/
|
||||
restartTimer: function(arg) {
|
||||
let delay;
|
||||
try {
|
||||
@ -249,7 +252,98 @@ let Scheduler = {
|
||||
if (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.
|
||||
Scheduler.Debugging.latestReceived = null;
|
||||
Scheduler.Debugging.latestSent = [Date.now(), method, summarizeObject(methodArgs)];
|
||||
|
||||
// Don't kill the worker just yet
|
||||
Scheduler.restartTimer();
|
||||
|
||||
|
||||
let data;
|
||||
let reply;
|
||||
let isError = false;
|
||||
@ -346,11 +445,7 @@ let Scheduler = {
|
||||
Scheduler._updateTelemetry();
|
||||
}
|
||||
|
||||
// Don't restart the timer when reseting the worker, since that will
|
||||
// lead to an endless "resetWorker()" loop.
|
||||
if (method != "Meta_reset") {
|
||||
Scheduler.restartTimer();
|
||||
}
|
||||
Scheduler.restartTimer();
|
||||
}
|
||||
|
||||
// Check for duration and return result.
|
||||
@ -470,54 +565,9 @@ const WEB_WORKERS_SHUTDOWN_TOPIC = "web-workers-shutdown";
|
||||
const PREF_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(
|
||||
"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);
|
||||
phase.addBlocker(
|
||||
"(for testing purposes) OS.File: warn about unclosed files",
|
||||
() => warnAboutUnclosedFiles(false)
|
||||
() => Scheduler.kill({shutdown: false, reset: false})
|
||||
);
|
||||
}
|
||||
}, false);
|
||||
@ -1366,44 +1416,15 @@ DirectoryIterator.Entry.fromMsg = function fromMsg(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() {
|
||||
if (!Scheduler.launched || Scheduler.shutdown) {
|
||||
// No need to reset
|
||||
return Promise.resolve();
|
||||
}
|
||||
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);
|
||||
return Task.spawn(function*() {
|
||||
let resources = yield Scheduler.kill({shutdown: false, reset: true});
|
||||
if (resources && !resources.killed) {
|
||||
throw new Error("Could not reset worker, this would leak file descriptors: " + JSON.stringify(resources));
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Constants
|
||||
File.POS_START = SysAll.POS_START;
|
||||
File.POS_CURRENT = SysAll.POS_CURRENT;
|
||||
|
@ -272,34 +272,25 @@ const EXCEPTION_NAMES = {
|
||||
GET_DEBUG: function() {
|
||||
return SharedAll.Config.DEBUG;
|
||||
},
|
||||
Meta_getUnclosedResources: function() {
|
||||
// Return information about both opened files and opened
|
||||
// directory iterators.
|
||||
return {
|
||||
/**
|
||||
* Execute shutdown sequence, returning data on leaked file descriptors.
|
||||
*
|
||||
* @param {bool} If |true|, kill the worker if this would not cause
|
||||
* leaks.
|
||||
*/
|
||||
Meta_shutdown: function(kill) {
|
||||
let result = {
|
||||
openedFiles: OpenedFiles.listOpenedResources(),
|
||||
openedDirectoryIterators: OpenedDirectoryIterators.listOpenedResources()
|
||||
openedDirectoryIterators: OpenedDirectoryIterators.listOpenedResources(),
|
||||
killed: false // Placeholder
|
||||
};
|
||||
},
|
||||
Meta_reset: function() {
|
||||
// Attempt to stop the worker. This fails if at least one
|
||||
// resource is still open. Returns the list of files and
|
||||
// directory iterators that cannot be closed safely (or undefined
|
||||
// if there are no such files/directory iterators).
|
||||
let openedFiles = OpenedFiles.listOpenedResources();
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
// Is it safe to kill the worker?
|
||||
let safe = result.openedFiles.length == 0
|
||||
&& result.openedDirectoryIterators.length == 0;
|
||||
result.killed = safe && kill;
|
||||
|
||||
return new Meta(result, {shutdown: result.killed});
|
||||
},
|
||||
// Functions of OS.File
|
||||
stat: function stat(path, options) {
|
||||
|
@ -37,7 +37,7 @@ add_task(function file_open_cannot_reset() {
|
||||
let thrown = false;
|
||||
try {
|
||||
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;
|
||||
}
|
||||
do_check_true(thrown);
|
||||
@ -47,14 +47,14 @@ add_task(function file_open_cannot_reset() {
|
||||
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();
|
||||
do_print("Leaking directory " + TEST_DIR + ", we shouldn't be able to reset");
|
||||
let iterator = new OS.File.DirectoryIterator(TEST_DIR);
|
||||
let thrown = false;
|
||||
try {
|
||||
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;
|
||||
}
|
||||
do_check_true(thrown);
|
||||
|
@ -89,7 +89,7 @@ add_task(function system_shutdown() {
|
||||
do_check_true((yield testLeaksOf(TEST_FILE, "test.shutdown.file.leak")));
|
||||
yield openedFile.close();
|
||||
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")));
|
||||
});
|
||||
|
||||
|
||||
|
@ -605,18 +605,29 @@ var AddonManagerInternal = {
|
||||
try {
|
||||
defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED);
|
||||
} catch (e) {}
|
||||
AddonManagerPrivate.recordSimpleMeasure("default_providers", defaultProvidersEnabled);
|
||||
|
||||
// Ensure all default providers have had a chance to register themselves
|
||||
if (defaultProvidersEnabled) {
|
||||
DEFAULT_PROVIDERS.forEach(function(url) {
|
||||
for (let url of DEFAULT_PROVIDERS) {
|
||||
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) {
|
||||
AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e);
|
||||
logger.error("Exception loading default provider \"" + url + "\"", e);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Load any providers registered in the category manager
|
||||
@ -772,6 +783,7 @@ var AddonManagerInternal = {
|
||||
provider[aMethod].apply(provider, aArgs);
|
||||
}
|
||||
catch (e) {
|
||||
AddonManagerPrivate.recordException("AMI", "provider " + aMethod, e);
|
||||
logger.error("Exception calling provider " + aMethod, e);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
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/XPCOMUtils.jsm");
|
||||
@ -1545,7 +1545,7 @@ function makeSafe(aFunction) {
|
||||
}
|
||||
}
|
||||
|
||||
var XPIProvider = {
|
||||
this.XPIProvider = {
|
||||
// An array of known install locations
|
||||
installLocations: null,
|
||||
// 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
|
||||
*/
|
||||
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) {
|
||||
try {
|
||||
var dir = FileUtils.getDir(aKey, aPaths);
|
||||
@ -1811,6 +1794,22 @@ var XPIProvider = {
|
||||
}
|
||||
|
||||
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 enabledScopes = Prefs.getIntPref(PREF_EM_ENABLED_SCOPES,
|
||||
|
@ -68,10 +68,18 @@ html|html {
|
||||
background-image: linear-gradient(rgba(255, 255, 255, 0),
|
||||
rgba(255, 255, 255, 0.75));
|
||||
border: 1px solid #C3CEDF;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
%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) {
|
||||
/* Buttons */
|
||||
*|button,
|
||||
|
@ -181,15 +181,22 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.category:-moz-locale-dir(ltr) {
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
%ifdef WINDOWS_AERO
|
||||
@media (-moz-os-version: windows-vista),
|
||||
(-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) {
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
.category:-moz-locale-dir(rtl) {
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
%ifdef WINDOWS_AERO
|
||||
}
|
||||
%endif
|
||||
|
||||
.category[disabled] {
|
||||
border-top: 0;
|
||||
|
Loading…
Reference in New Issue
Block a user