Bug 748894 - Move the bookmark star button outside of the location bar.

r=Mano
This commit is contained in:
Marco Bonardo
2013-04-23 16:06:17 +02:00
parent e9151dfbf6
commit f716ee1d1b
25 changed files with 541 additions and 499 deletions

View File

@@ -2,6 +2,8 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
////////////////////////////////////////////////////////////////////////////////
//// StarUI
var StarUI = {
_itemId: -1,
@@ -218,10 +220,6 @@ var StarUI = {
gEditItemOverlay.uninitPanel(true);
},
editButtonCommand: function SU_editButtonCommand() {
this.showEditBookmarkPopup();
},
cancelButtonOnCommand: function SU_cancelButtonOnCommand() {
this._actionOnHide = "cancel";
this.panel.hidePopup();
@@ -241,6 +239,9 @@ var StarUI = {
}
}
////////////////////////////////////////////////////////////////////////////////
//// PlacesCommandHook
var PlacesCommandHook = {
/**
* Adds a bookmark to the page loaded in the given browser.
@@ -293,28 +294,37 @@ var PlacesCommandHook = {
PlacesUtils.bookmarks.DEFAULT_INDEX,
title, null, [descAnno]);
PlacesUtils.transactionManager.doTransaction(txn);
itemId = txn.item.id;
// Set the character-set
if (charset && !PrivateBrowsingUtils.isWindowPrivate(aBrowser.contentWindow))
PlacesUtils.setCharsetForURI(uri, charset);
itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
}
// Revert the contents of the location bar
if (gURLBar)
gURLBar.handleRevert();
// dock the panel to the star icon when possible, otherwise dock
// it to the content area
if (aBrowser.contentWindow == window.content) {
var starIcon = aBrowser.ownerDocument.getElementById("star-button");
if (starIcon && isElementVisible(starIcon)) {
if (aShowEditUI)
StarUI.showEditBookmarkPopup(itemId, starIcon, "bottomcenter topright");
return;
}
// If it was not requested to open directly in "edit" mode, we are done.
if (!aShowEditUI)
return;
// Try to dock the panel to:
// 1. the bookmarks menu button
// 2. the page-proxy-favicon
// 3. the content area
if (BookmarksMenuButton.anchor) {
StarUI.showEditBookmarkPopup(itemId, BookmarksMenuButton.anchor,
"bottomcenter topright");
return;
}
StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap");
let pageProxyFavicon = document.getElementById("page-proxy-favicon");
if (isElementVisible(pageProxyFavicon)) {
StarUI.showEditBookmarkPopup(itemId, pageProxyFavicon,
"bottomcenter topright");
} else {
StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap");
}
},
/**
@@ -460,6 +470,9 @@ var PlacesCommandHook = {
}
};
////////////////////////////////////////////////////////////////////////////////
//// HistoryMenu
// View for the history menu.
function HistoryMenu(aPopupShowingEvent) {
// Workaround for Bug 610187. The sidebar does not include all the Places
@@ -686,6 +699,9 @@ HistoryMenu.prototype = {
}
};
////////////////////////////////////////////////////////////////////////////////
//// BookmarksEventHandler
/**
* Functions for handling events in the Bookmarks Toolbar and menu.
*/
@@ -811,6 +827,8 @@ var BookmarksEventHandler = {
}
};
////////////////////////////////////////////////////////////////////////////////
//// PlacesMenuDNDHandler
// Handles special drag and drop functionality for Places menus that are not
// part of a Places view (e.g. the bookmarks menu in the menubar).
@@ -829,26 +847,37 @@ var PlacesMenuDNDHandler = {
if (!this._isStaticContainer(event.target))
return;
let popup = event.target.lastChild;
if (this._loadTimer || popup.state === "showing" || popup.state === "open")
return;
this._loadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._loadTimer.initWithCallback(function() {
PlacesMenuDNDHandler._loadTimer = null;
event.target.lastChild.setAttribute("autoopened", "true");
event.target.lastChild.showPopup(event.target.lastChild);
this._loadTimer.initWithCallback(() => {
this._loadTimer = null;
popup.setAttribute("autoopened", "true");
popup.showPopup(popup);
}, this._springLoadDelay, Ci.nsITimer.TYPE_ONE_SHOT);
event.preventDefault();
event.stopPropagation();
},
/**
* Handles dragexit on the <menu> element.
* Handles dragleave on the <menu> element.
* @returns true if the element is a container element (menu or
* menu-toolbarbutton), false otherwise.
*/
onDragExit: function PMDH_onDragExit(event) {
onDragLeave: function PMDH_onDragLeave(event) {
// Handle menu-button separate targets.
if (event.relatedTarget === event.currentTarget ||
event.relatedTarget.parentNode === event.currentTarget)
return;
// Closing menus in a Places popup is handled by the view itself.
if (!this._isStaticContainer(event.target))
return;
let popup = event.target.lastChild;
if (this._loadTimer) {
this._loadTimer.cancel();
this._loadTimer = null;
@@ -862,10 +891,9 @@ var PlacesMenuDNDHandler = {
inHierarchy = node == event.target;
node = node.parentNode;
}
if (!inHierarchy && event.target.lastChild &&
event.target.lastChild.hasAttribute("autoopened")) {
event.target.lastChild.removeAttribute("autoopened");
event.target.lastChild.hidePopup();
if (!inHierarchy && popup && popup.hasAttribute("autoopened")) {
popup.removeAttribute("autoopened");
popup.hidePopup();
}
}, this._springLoadDelay, Ci.nsITimer.TYPE_ONE_SHOT);
},
@@ -878,7 +906,8 @@ var PlacesMenuDNDHandler = {
_isStaticContainer: function PMDH__isContainer(node) {
let isMenu = node.localName == "menu" ||
(node.localName == "toolbarbutton" &&
node.getAttribute("type") == "menu");
(node.getAttribute("type") == "menu" ||
node.getAttribute("type") == "menu-button"));
let isStatic = !("_placesNode" in node) && node.lastChild &&
node.lastChild.hasAttribute("placespopup") &&
!node.parentNode.hasAttribute("placespopup");
@@ -915,179 +944,13 @@ var PlacesMenuDNDHandler = {
}
};
////////////////////////////////////////////////////////////////////////////////
//// PlacesToolbarHelper
var PlacesStarButton = {
_hasBookmarksObserver: false,
uninit: function PSB_uninit()
{
if (this._hasBookmarksObserver) {
PlacesUtils.removeLazyBookmarkObserver(this);
}
if (this._pendingStmt) {
this._pendingStmt.cancel();
delete this._pendingStmt;
}
},
QueryInterface: XPCOMUtils.generateQI([
Ci.nsINavBookmarkObserver
]),
get _starredTooltip()
{
delete this._starredTooltip;
return this._starredTooltip =
gNavigatorBundle.getString("starButtonOn.tooltip");
},
get _unstarredTooltip()
{
delete this._unstarredTooltip;
return this._unstarredTooltip =
gNavigatorBundle.getString("starButtonOff.tooltip");
},
updateState: function PSB_updateState()
{
this._starIcon = document.getElementById("star-button");
if (!this._starIcon || (this._uri && gBrowser.currentURI.equals(this._uri))) {
return;
}
// Reset tracked values.
this._uri = gBrowser.currentURI;
this._itemIds = [];
if (this._pendingStmt) {
this._pendingStmt.cancel();
delete this._pendingStmt;
}
// We can load about:blank before the actual page, but there is no point in handling that page.
if (isBlankPageURL(this._uri.spec)) {
return;
}
this._pendingStmt = PlacesUtils.asyncGetBookmarkIds(this._uri, function (aItemIds, aURI) {
// Safety check that the bookmarked URI equals the tracked one.
if (!aURI.equals(this._uri)) {
Components.utils.reportError("PlacesStarButton did not receive current URI");
return;
}
// It's possible that onItemAdded gets called before the async statement
// calls back. For such an edge case, retain all unique entries from both
// arrays.
this._itemIds = this._itemIds.filter(
function (id) aItemIds.indexOf(id) == -1
).concat(aItemIds);
this._updateStateInternal();
// Start observing bookmarks if needed.
if (!this._hasBookmarksObserver) {
try {
PlacesUtils.addLazyBookmarkObserver(this);
this._hasBookmarksObserver = true;
} catch(ex) {
Components.utils.reportError("PlacesStarButton failed adding a bookmarks observer: " + ex);
}
}
delete this._pendingStmt;
}, this);
},
_updateStateInternal: function PSB__updateStateInternal()
{
if (!this._starIcon) {
return;
}
if (this._itemIds.length > 0) {
this._starIcon.setAttribute("starred", "true");
this._starIcon.setAttribute("tooltiptext", this._starredTooltip);
}
else {
this._starIcon.removeAttribute("starred");
this._starIcon.setAttribute("tooltiptext", this._unstarredTooltip);
}
},
onClick: function PSB_onClick(aEvent)
{
// Ignore clicks on the star while we update its state.
if (aEvent.button == 0 && !this._pendingStmt) {
PlacesCommandHook.bookmarkCurrentPage(this._itemIds.length > 0);
}
// Don't bubble to the textbox, to avoid unwanted selection of the address.
aEvent.stopPropagation();
},
// nsINavBookmarkObserver
onItemAdded:
function PSB_onItemAdded(aItemId, aFolder, aIndex, aItemType, aURI)
{
if (!this._starIcon) {
return;
}
if (aURI && aURI.equals(this._uri)) {
// If a new bookmark has been added to the tracked uri, register it.
if (this._itemIds.indexOf(aItemId) == -1) {
this._itemIds.push(aItemId);
this._updateStateInternal();
}
}
},
onItemRemoved:
function PSB_onItemRemoved(aItemId, aFolder, aIndex, aItemType)
{
if (!this._starIcon) {
return;
}
let index = this._itemIds.indexOf(aItemId);
// If one of the tracked bookmarks has been removed, unregister it.
if (index != -1) {
this._itemIds.splice(index, 1);
this._updateStateInternal();
}
},
onItemChanged:
function PSB_onItemChanged(aItemId, aProperty, aIsAnnotationProperty,
aNewValue, aLastModified, aItemType)
{
if (!this._starIcon) {
return;
}
if (aProperty == "uri") {
let index = this._itemIds.indexOf(aItemId);
// If the changed bookmark was tracked, check if it is now pointing to
// a different uri and unregister it.
if (index != -1 && aNewValue != this._uri.spec) {
this._itemIds.splice(index, 1);
this._updateStateInternal();
}
// If another bookmark is now pointing to the tracked uri, register it.
else if (index == -1 && aNewValue == this._uri.spec) {
this._itemIds.push(aItemId);
this._updateStateInternal();
}
}
},
onBeginUpdateBatch: function () {},
onEndUpdateBatch: function () {},
onItemVisited: function () {},
onItemMoved: function () {}
};
// This object handles the initialization and uninitialization of the bookmarks
// toolbar. updateState is called when the browser window is opened and
// after closing the toolbar customization dialog.
/**
* This object handles the initialization and uninitialization of the bookmarks
* toolbar.
*/
let PlacesToolbarHelper = {
_place: "place:folder=TOOLBAR",
@@ -1127,58 +990,95 @@ let PlacesToolbarHelper = {
}
};
////////////////////////////////////////////////////////////////////////////////
//// BookmarksMenuButton
// Handles the bookmarks menu button shown when the main menubar is hidden.
/**
* Handles the bookmarks menu-button in the toolbar.
*/
let BookmarksMenuButton = {
get button() {
return document.getElementById("bookmarks-menu-button");
if (!this._button) {
this._button = document.getElementById("bookmarks-menu-button");
}
return this._button;
},
get buttonContainer() {
return document.getElementById("bookmarks-menu-button-container");
get star() {
if (!this._star && this.button) {
this._star = document.getAnonymousElementByAttribute(this.button,
"anonid",
"button");
}
return this._star;
},
get personalToolbar() {
delete this.personalToolbar;
return this.personalToolbar = document.getElementById("PersonalToolbar");
get anchor() {
if (!this._anchor && this.star && isElementVisible(this.star)) {
// Anchor to the icon, so the panel looks more natural.
this._anchor = document.getAnonymousElementByAttribute(this.star,
"class",
"toolbarbutton-icon");
}
return this._anchor;
},
get bookmarksToolbarItem() {
return document.getElementById("personal-bookmarks");
STATUS_UPDATING: -1,
STATUS_UNSTARRED: 0,
STATUS_STARRED: 1,
get status() {
if (this._pendingStmt)
return this.STATUS_UPDATING;
return this.button &&
this.button.hasAttribute("starred") ? this.STATUS_STARRED
: this.STATUS_UNSTARRED;
},
init: function BMB_init() {
this.updatePosition();
// Any other stuff that does not regard the button itself should be
// handled in the onPopupShowing handler, so it does not hit Ts.
get _starredTooltip()
{
delete this._starredTooltip;
return this._starredTooltip =
gNavigatorBundle.getString("starButtonOn.tooltip");
},
get _unstarredTooltip()
{
delete this._unstarredTooltip;
return this._unstarredTooltip =
gNavigatorBundle.getString("starButtonOff.tooltip");
},
/**
* The popup contents must be updated when the user customizes the UI, or
* changes the personal toolbar collapsed status. In such a case, any needed
* change should be handled in the popupshowing helper, for performance
* reasons.
*/
_popupNeedsUpdate: true,
onToolbarVisibilityChange: function BMB_onToolbarVisibilityChange() {
this._popupNeedsUpdate = true;
},
_popupNeedsUpdate: {},
onPopupShowing: function BMB_onPopupShowing(event) {
// Don't handle events for submenus.
if (event.target != event.currentTarget)
return;
let popup = event.target;
let needsUpdate = this._popupNeedsUpdate[popup.id];
// Check if popup contents need to be updated. Note that if needsUpdate is
// undefined we have never seen the popup, thus it should be updated.
if (needsUpdate === false)
if (!this._popupNeedsUpdate)
return;
this._popupNeedsUpdate[popup.id] = false;
this._popupNeedsUpdate = false;
function getPlacesAnonymousElement(aAnonId)
document.getAnonymousElementByAttribute(popup.parentNode,
"placesanonid",
aAnonId);
let popup = event.target;
let getPlacesAnonymousElement =
aAnonId => document.getAnonymousElementByAttribute(popup.parentNode,
"placesanonid",
aAnonId);
let viewToolbarMenuitem = getPlacesAnonymousElement("view-toolbar");
if (viewToolbarMenuitem) {
// Update View bookmarks toolbar checkbox menuitem.
viewToolbarMenuitem.setAttribute("checked",
!this.personalToolbar.collapsed);
let personalToolbar = document.getElementById("PersonalToolbar");
viewToolbarMenuitem.setAttribute("checked", !personalToolbar.collapsed);
}
let toolbarMenuitem = getPlacesAnonymousElement("toolbar-autohide");
@@ -1186,68 +1086,44 @@ let BookmarksMenuButton = {
// If bookmarks items are visible, hide Bookmarks Toolbar menu and the
// separator after it.
toolbarMenuitem.collapsed = toolbarMenuitem.nextSibling.collapsed =
isElementVisible(this.bookmarksToolbarItem);
isElementVisible(document.getElementById("personal-bookmarks"));
}
},
updatePosition: function BMB_updatePosition() {
// Popups will have to be updated when the user customizes the UI, or
// changes personal toolbar collapsed status. Both of those location call
// updatePosition(), so this is the only point asking for popup updates.
for (let popupId in this._popupNeedsUpdate) {
this._popupNeedsUpdate[popupId] = true;
/**
* Handles star styling based on page proxy state changes.
*/
onPageProxyStateChanged: function BMB_onPageProxyStateChanged(aState) {
if (!this.star) {
return;
}
let button = this.button;
if (!button)
return;
// If the toolbar containing bookmarks is visible, we want to move the
// button to bookmarksToolbarItem.
let bookmarksToolbarItem = this.bookmarksToolbarItem;
let bookmarksOnVisibleToolbar = bookmarksToolbarItem &&
!bookmarksToolbarItem.parentNode.collapsed &&
bookmarksToolbarItem.parentNode.getAttribute("autohide") != "true";
// If the container has been moved by the user to the toolbar containing
// bookmarks, we want to preserve the desired position.
let container = this.buttonContainer;
let containerNearBookmarks = container && bookmarksToolbarItem &&
container.parentNode == bookmarksToolbarItem.parentNode;
if (bookmarksOnVisibleToolbar && !containerNearBookmarks) {
if (button.parentNode != bookmarksToolbarItem) {
this._uninitView();
bookmarksToolbarItem.appendChild(button);
}
if (aState == "invalid") {
this.star.setAttribute("disabled", "true");
this.button.removeAttribute("starred");
}
else {
if (container && button.parentNode != container) {
this._uninitView();
container.appendChild(button);
}
this.star.removeAttribute("disabled");
}
this._updateStyle();
},
_updateStyle: function BMB__updateStyle() {
let button = this.button;
if (!button)
if (!this.star) {
return;
}
let container = this.buttonContainer;
let containerOnPersonalToolbar = container &&
(container.parentNode == this.personalToolbar ||
container.parentNode.parentNode == this.personalToolbar);
let personalToolbar = document.getElementById("PersonalToolbar");
let onPersonalToolbar = this.button.parentNode == personalToolbar ||
this.button.parentNode.parentNode == personalToolbar;
if (button.parentNode == this.bookmarksToolbarItem ||
containerOnPersonalToolbar) {
button.classList.add("bookmark-item");
button.classList.remove("toolbarbutton-1");
if (onPersonalToolbar) {
this.button.classList.add("bookmark-item");
this.button.classList.remove("toolbarbutton-1");
}
else {
button.classList.remove("bookmark-item");
button.classList.add("toolbarbutton-1");
this.button.classList.remove("bookmark-item");
this.button.classList.add("toolbarbutton-1");
}
},
@@ -1255,20 +1131,13 @@ let BookmarksMenuButton = {
// When an element with a placesView attached is removed and re-inserted,
// XBL reapplies the binding causing any kind of issues and possible leaks,
// so kill current view and let popupshowing generate a new one.
let button = this.button;
if (button && button._placesView)
button._placesView.uninit();
if (this.button && this.button._placesView) {
this.button._placesView.uninit();
}
},
customizeStart: function BMB_customizeStart() {
this._uninitView();
let button = this.button;
let container = this.buttonContainer;
if (button && container && button.parentNode != container) {
// Move button back to the container, so user can move or remove it.
container.appendChild(button);
this._updateStyle();
}
},
customizeChange: function BMB_customizeChange() {
@@ -1276,6 +1145,159 @@ let BookmarksMenuButton = {
},
customizeDone: function BMB_customizeDone() {
this.updatePosition();
}
delete this._button;
delete this._star;
delete this._anchor;
this.onToolbarVisibilityChange();
this._updateStyle();
},
_hasBookmarksObserver: false,
uninit: function BMB_uninit() {
this._uninitView();
if (this._hasBookmarksObserver) {
PlacesUtils.removeLazyBookmarkObserver(this);
}
if (this._pendingStmt) {
this._pendingStmt.cancel();
delete this._pendingStmt;
}
},
updateStarState: function BMB_updateStarState() {
if (!this.button || (this._uri && gBrowser.currentURI.equals(this._uri))) {
return;
}
// Reset tracked values.
this._uri = gBrowser.currentURI;
this._itemIds = [];
if (this._pendingStmt) {
this._pendingStmt.cancel();
delete this._pendingStmt;
}
// We can load about:blank before the actual page, but there is no point in handling that page.
if (isBlankPageURL(this._uri.spec)) {
return;
}
this._pendingStmt = PlacesUtils.asyncGetBookmarkIds(this._uri, function (aItemIds, aURI) {
// Safety check that the bookmarked URI equals the tracked one.
if (!aURI.equals(this._uri)) {
Components.utils.reportError("BookmarksMenuButton did not receive current URI");
return;
}
// It's possible that onItemAdded gets called before the async statement
// calls back. For such an edge case, retain all unique entries from both
// arrays.
this._itemIds = this._itemIds.filter(
function (id) aItemIds.indexOf(id) == -1
).concat(aItemIds);
this._updateStar();
// Start observing bookmarks if needed.
if (!this._hasBookmarksObserver) {
try {
PlacesUtils.addLazyBookmarkObserver(this);
this._hasBookmarksObserver = true;
} catch(ex) {
Components.utils.reportError("BookmarksMenuButton failed adding a bookmarks observer: " + ex);
}
}
delete this._pendingStmt;
}, this);
},
_updateStar: function BMB__updateStar() {
if (!this.button) {
return;
}
if (this._itemIds.length > 0) {
this.button.setAttribute("starred", "true");
this.button.setAttribute("tooltiptext", this._starredTooltip);
}
else {
this.button.removeAttribute("starred");
this.button.setAttribute("tooltiptext", this._unstarredTooltip);
}
},
onCommand: function BMB_onCommand(aEvent) {
if (aEvent.target != aEvent.currentTarget) {
return;
}
// Ignore clicks on the star if we are updating its state.
if (!this._pendingStmt) {
PlacesCommandHook.bookmarkCurrentPage(this._itemIds.length > 0);
}
},
// nsINavBookmarkObserver
onItemAdded: function BMB_onItemAdded(aItemId, aParentId, aIndex, aItemType,
aURI) {
if (!this.button) {
return;
}
if (aURI && aURI.equals(this._uri)) {
// If a new bookmark has been added to the tracked uri, register it.
if (this._itemIds.indexOf(aItemId) == -1) {
this._itemIds.push(aItemId);
this._updateStar();
}
}
},
onItemRemoved: function BMB_onItemRemoved(aItemId) {
if (!this.button) {
return;
}
let index = this._itemIds.indexOf(aItemId);
// If one of the tracked bookmarks has been removed, unregister it.
if (index != -1) {
this._itemIds.splice(index, 1);
this._updateStar();
}
},
onItemChanged: function BMB_onItemChanged(aItemId, aProperty,
aIsAnnotationProperty, aNewValue) {
if (!this.button) {
return;
}
if (aProperty == "uri") {
let index = this._itemIds.indexOf(aItemId);
// If the changed bookmark was tracked, check if it is now pointing to
// a different uri and unregister it.
if (index != -1 && aNewValue != this._uri.spec) {
this._itemIds.splice(index, 1);
this._updateStar();
}
// If another bookmark is now pointing to the tracked uri, register it.
else if (index == -1 && aNewValue == this._uri.spec) {
this._itemIds.push(aItemId);
this._updateStar();
}
}
},
onBeginUpdateBatch: function () {},
onEndUpdateBatch: function () {},
onBeforeItemRemoved: function () {},
onItemVisited: function () {},
onItemMoved: function () {},
QueryInterface: XPCOMUtils.generateQI([
Ci.nsINavBookmarkObserver
]),
};