browser/base/content/browser-places.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 # This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 # License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
michael@0 4
michael@0 5 ////////////////////////////////////////////////////////////////////////////////
michael@0 6 //// StarUI
michael@0 7
michael@0 8 var StarUI = {
michael@0 9 _itemId: -1,
michael@0 10 uri: null,
michael@0 11 _batching: false,
michael@0 12
michael@0 13 _element: function(aID) {
michael@0 14 return document.getElementById(aID);
michael@0 15 },
michael@0 16
michael@0 17 // Edit-bookmark panel
michael@0 18 get panel() {
michael@0 19 delete this.panel;
michael@0 20 var element = this._element("editBookmarkPanel");
michael@0 21 // initially the panel is hidden
michael@0 22 // to avoid impacting startup / new window performance
michael@0 23 element.hidden = false;
michael@0 24 element.addEventListener("popuphidden", this, false);
michael@0 25 element.addEventListener("keypress", this, false);
michael@0 26 return this.panel = element;
michael@0 27 },
michael@0 28
michael@0 29 // Array of command elements to disable when the panel is opened.
michael@0 30 get _blockedCommands() {
michael@0 31 delete this._blockedCommands;
michael@0 32 return this._blockedCommands =
michael@0 33 ["cmd_close", "cmd_closeWindow"].map(function (id) this._element(id), this);
michael@0 34 },
michael@0 35
michael@0 36 _blockCommands: function SU__blockCommands() {
michael@0 37 this._blockedCommands.forEach(function (elt) {
michael@0 38 // make sure not to permanently disable this item (see bug 409155)
michael@0 39 if (elt.hasAttribute("wasDisabled"))
michael@0 40 return;
michael@0 41 if (elt.getAttribute("disabled") == "true") {
michael@0 42 elt.setAttribute("wasDisabled", "true");
michael@0 43 } else {
michael@0 44 elt.setAttribute("wasDisabled", "false");
michael@0 45 elt.setAttribute("disabled", "true");
michael@0 46 }
michael@0 47 });
michael@0 48 },
michael@0 49
michael@0 50 _restoreCommandsState: function SU__restoreCommandsState() {
michael@0 51 this._blockedCommands.forEach(function (elt) {
michael@0 52 if (elt.getAttribute("wasDisabled") != "true")
michael@0 53 elt.removeAttribute("disabled");
michael@0 54 elt.removeAttribute("wasDisabled");
michael@0 55 });
michael@0 56 },
michael@0 57
michael@0 58 // nsIDOMEventListener
michael@0 59 handleEvent: function SU_handleEvent(aEvent) {
michael@0 60 switch (aEvent.type) {
michael@0 61 case "popuphidden":
michael@0 62 if (aEvent.originalTarget == this.panel) {
michael@0 63 if (!this._element("editBookmarkPanelContent").hidden)
michael@0 64 this.quitEditMode();
michael@0 65
michael@0 66 if (this._anchorToolbarButton) {
michael@0 67 this._anchorToolbarButton.removeAttribute("open");
michael@0 68 this._anchorToolbarButton = null;
michael@0 69 }
michael@0 70 this._restoreCommandsState();
michael@0 71 this._itemId = -1;
michael@0 72 if (this._batching) {
michael@0 73 PlacesUtils.transactionManager.endBatch(false);
michael@0 74 this._batching = false;
michael@0 75 }
michael@0 76
michael@0 77 switch (this._actionOnHide) {
michael@0 78 case "cancel": {
michael@0 79 PlacesUtils.transactionManager.undoTransaction();
michael@0 80 break;
michael@0 81 }
michael@0 82 case "remove": {
michael@0 83 // Remove all bookmarks for the bookmark's url, this also removes
michael@0 84 // the tags for the url.
michael@0 85 PlacesUtils.transactionManager.beginBatch(null);
michael@0 86 let itemIds = PlacesUtils.getBookmarksForURI(this._uriForRemoval);
michael@0 87 for (let i = 0; i < itemIds.length; i++) {
michael@0 88 let txn = new PlacesRemoveItemTransaction(itemIds[i]);
michael@0 89 PlacesUtils.transactionManager.doTransaction(txn);
michael@0 90 }
michael@0 91 PlacesUtils.transactionManager.endBatch(false);
michael@0 92 break;
michael@0 93 }
michael@0 94 }
michael@0 95 this._actionOnHide = "";
michael@0 96 }
michael@0 97 break;
michael@0 98 case "keypress":
michael@0 99 if (aEvent.defaultPrevented) {
michael@0 100 // The event has already been consumed inside of the panel.
michael@0 101 break;
michael@0 102 }
michael@0 103 switch (aEvent.keyCode) {
michael@0 104 case KeyEvent.DOM_VK_ESCAPE:
michael@0 105 if (!this._element("editBookmarkPanelContent").hidden)
michael@0 106 this.cancelButtonOnCommand();
michael@0 107 break;
michael@0 108 case KeyEvent.DOM_VK_RETURN:
michael@0 109 if (aEvent.target.classList.contains("expander-up") ||
michael@0 110 aEvent.target.classList.contains("expander-down") ||
michael@0 111 aEvent.target.id == "editBMPanel_newFolderButton") {
michael@0 112 //XXX Why is this necessary? The defaultPrevented check should
michael@0 113 // be enough.
michael@0 114 break;
michael@0 115 }
michael@0 116 this.panel.hidePopup();
michael@0 117 break;
michael@0 118 }
michael@0 119 break;
michael@0 120 }
michael@0 121 },
michael@0 122
michael@0 123 _overlayLoaded: false,
michael@0 124 _overlayLoading: false,
michael@0 125 showEditBookmarkPopup:
michael@0 126 function SU_showEditBookmarkPopup(aItemId, aAnchorElement, aPosition) {
michael@0 127 // Performance: load the overlay the first time the panel is opened
michael@0 128 // (see bug 392443).
michael@0 129 if (this._overlayLoading)
michael@0 130 return;
michael@0 131
michael@0 132 if (this._overlayLoaded) {
michael@0 133 this._doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition);
michael@0 134 return;
michael@0 135 }
michael@0 136
michael@0 137 this._overlayLoading = true;
michael@0 138 document.loadOverlay(
michael@0 139 "chrome://browser/content/places/editBookmarkOverlay.xul",
michael@0 140 (function (aSubject, aTopic, aData) {
michael@0 141 // Move the header (star, title, button) into the grid,
michael@0 142 // so that it aligns nicely with the other items (bug 484022).
michael@0 143 let header = this._element("editBookmarkPanelHeader");
michael@0 144 let rows = this._element("editBookmarkPanelGrid").lastChild;
michael@0 145 rows.insertBefore(header, rows.firstChild);
michael@0 146 header.hidden = false;
michael@0 147
michael@0 148 this._overlayLoading = false;
michael@0 149 this._overlayLoaded = true;
michael@0 150 this._doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition);
michael@0 151 }).bind(this)
michael@0 152 );
michael@0 153 },
michael@0 154
michael@0 155 _doShowEditBookmarkPanel:
michael@0 156 function SU__doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition) {
michael@0 157 if (this.panel.state != "closed")
michael@0 158 return;
michael@0 159
michael@0 160 this._blockCommands(); // un-done in the popuphiding handler
michael@0 161
michael@0 162 // Set panel title:
michael@0 163 // if we are batching, i.e. the bookmark has been added now,
michael@0 164 // then show Page Bookmarked, else if the bookmark did already exist,
michael@0 165 // we are about editing it, then use Edit This Bookmark.
michael@0 166 this._element("editBookmarkPanelTitle").value =
michael@0 167 this._batching ?
michael@0 168 gNavigatorBundle.getString("editBookmarkPanel.pageBookmarkedTitle") :
michael@0 169 gNavigatorBundle.getString("editBookmarkPanel.editBookmarkTitle");
michael@0 170
michael@0 171 // No description; show the Done, Cancel;
michael@0 172 this._element("editBookmarkPanelDescription").textContent = "";
michael@0 173 this._element("editBookmarkPanelBottomButtons").hidden = false;
michael@0 174 this._element("editBookmarkPanelContent").hidden = false;
michael@0 175
michael@0 176 // The remove button is shown only if we're not already batching, i.e.
michael@0 177 // if the cancel button/ESC does not remove the bookmark.
michael@0 178 this._element("editBookmarkPanelRemoveButton").hidden = this._batching;
michael@0 179
michael@0 180 // The label of the remove button differs if the URI is bookmarked
michael@0 181 // multiple times.
michael@0 182 var bookmarks = PlacesUtils.getBookmarksForURI(gBrowser.currentURI);
michael@0 183 var forms = gNavigatorBundle.getString("editBookmark.removeBookmarks.label");
michael@0 184 var label = PluralForm.get(bookmarks.length, forms).replace("#1", bookmarks.length);
michael@0 185 this._element("editBookmarkPanelRemoveButton").label = label;
michael@0 186
michael@0 187 // unset the unstarred state, if set
michael@0 188 this._element("editBookmarkPanelStarIcon").removeAttribute("unstarred");
michael@0 189
michael@0 190 this._itemId = aItemId !== undefined ? aItemId : this._itemId;
michael@0 191 this.beginBatch();
michael@0 192
michael@0 193 if (aAnchorElement) {
michael@0 194 // Set the open=true attribute if the anchor is a
michael@0 195 // descendent of a toolbarbutton.
michael@0 196 let parent = aAnchorElement.parentNode;
michael@0 197 while (parent) {
michael@0 198 if (parent.localName == "toolbarbutton") {
michael@0 199 break;
michael@0 200 }
michael@0 201 parent = parent.parentNode;
michael@0 202 }
michael@0 203 if (parent) {
michael@0 204 this._anchorToolbarButton = parent;
michael@0 205 parent.setAttribute("open", "true");
michael@0 206 }
michael@0 207 }
michael@0 208 this.panel.openPopup(aAnchorElement, aPosition);
michael@0 209
michael@0 210 gEditItemOverlay.initPanel(this._itemId,
michael@0 211 { hiddenRows: ["description", "location",
michael@0 212 "loadInSidebar", "keyword"] });
michael@0 213 },
michael@0 214
michael@0 215 panelShown:
michael@0 216 function SU_panelShown(aEvent) {
michael@0 217 if (aEvent.target == this.panel) {
michael@0 218 if (!this._element("editBookmarkPanelContent").hidden) {
michael@0 219 let fieldToFocus = "editBMPanel_" +
michael@0 220 gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField");
michael@0 221 var elt = this._element(fieldToFocus);
michael@0 222 elt.focus();
michael@0 223 elt.select();
michael@0 224 }
michael@0 225 else {
michael@0 226 // Note this isn't actually used anymore, we should remove this
michael@0 227 // once we decide not to bring back the page bookmarked notification
michael@0 228 this.panel.focus();
michael@0 229 }
michael@0 230 }
michael@0 231 },
michael@0 232
michael@0 233 quitEditMode: function SU_quitEditMode() {
michael@0 234 this._element("editBookmarkPanelContent").hidden = true;
michael@0 235 this._element("editBookmarkPanelBottomButtons").hidden = true;
michael@0 236 gEditItemOverlay.uninitPanel(true);
michael@0 237 },
michael@0 238
michael@0 239 cancelButtonOnCommand: function SU_cancelButtonOnCommand() {
michael@0 240 this._actionOnHide = "cancel";
michael@0 241 this.panel.hidePopup();
michael@0 242 },
michael@0 243
michael@0 244 removeBookmarkButtonCommand: function SU_removeBookmarkButtonCommand() {
michael@0 245 this._uriForRemoval = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
michael@0 246 this._actionOnHide = "remove";
michael@0 247 this.panel.hidePopup();
michael@0 248 },
michael@0 249
michael@0 250 beginBatch: function SU_beginBatch() {
michael@0 251 if (!this._batching) {
michael@0 252 PlacesUtils.transactionManager.beginBatch(null);
michael@0 253 this._batching = true;
michael@0 254 }
michael@0 255 }
michael@0 256 }
michael@0 257
michael@0 258 ////////////////////////////////////////////////////////////////////////////////
michael@0 259 //// PlacesCommandHook
michael@0 260
michael@0 261 var PlacesCommandHook = {
michael@0 262 /**
michael@0 263 * Adds a bookmark to the page loaded in the given browser.
michael@0 264 *
michael@0 265 * @param aBrowser
michael@0 266 * a <browser> element.
michael@0 267 * @param [optional] aParent
michael@0 268 * The folder in which to create a new bookmark if the page loaded in
michael@0 269 * aBrowser isn't bookmarked yet, defaults to the unfiled root.
michael@0 270 * @param [optional] aShowEditUI
michael@0 271 * whether or not to show the edit-bookmark UI for the bookmark item
michael@0 272 */
michael@0 273 bookmarkPage: function PCH_bookmarkPage(aBrowser, aParent, aShowEditUI) {
michael@0 274 var uri = aBrowser.currentURI;
michael@0 275 var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
michael@0 276 if (itemId == -1) {
michael@0 277 // Copied over from addBookmarkForBrowser:
michael@0 278 // Bug 52536: We obtain the URL and title from the nsIWebNavigation
michael@0 279 // associated with a <browser/> rather than from a DOMWindow.
michael@0 280 // This is because when a full page plugin is loaded, there is
michael@0 281 // no DOMWindow (?) but information about the loaded document
michael@0 282 // may still be obtained from the webNavigation.
michael@0 283 var webNav = aBrowser.webNavigation;
michael@0 284 var url = webNav.currentURI;
michael@0 285 var title;
michael@0 286 var description;
michael@0 287 var charset;
michael@0 288 try {
michael@0 289 let isErrorPage = /^about:(neterror|certerror|blocked)/
michael@0 290 .test(webNav.document.documentURI);
michael@0 291 title = isErrorPage ? PlacesUtils.history.getPageTitle(url)
michael@0 292 : webNav.document.title;
michael@0 293 title = title || url.spec;
michael@0 294 description = PlacesUIUtils.getDescriptionFromDocument(webNav.document);
michael@0 295 charset = webNav.document.characterSet;
michael@0 296 }
michael@0 297 catch (e) { }
michael@0 298
michael@0 299 if (aShowEditUI) {
michael@0 300 // If we bookmark the page here (i.e. page was not "starred" already)
michael@0 301 // but open right into the "edit" state, start batching here, so
michael@0 302 // "Cancel" in that state removes the bookmark.
michael@0 303 StarUI.beginBatch();
michael@0 304 }
michael@0 305
michael@0 306 var parent = aParent != undefined ?
michael@0 307 aParent : PlacesUtils.unfiledBookmarksFolderId;
michael@0 308 var descAnno = { name: PlacesUIUtils.DESCRIPTION_ANNO, value: description };
michael@0 309 var txn = new PlacesCreateBookmarkTransaction(uri, parent,
michael@0 310 PlacesUtils.bookmarks.DEFAULT_INDEX,
michael@0 311 title, null, [descAnno]);
michael@0 312 PlacesUtils.transactionManager.doTransaction(txn);
michael@0 313 itemId = txn.item.id;
michael@0 314 // Set the character-set
michael@0 315 if (charset && !PrivateBrowsingUtils.isWindowPrivate(aBrowser.contentWindow))
michael@0 316 PlacesUtils.setCharsetForURI(uri, charset);
michael@0 317 }
michael@0 318
michael@0 319 // Revert the contents of the location bar
michael@0 320 if (gURLBar)
michael@0 321 gURLBar.handleRevert();
michael@0 322
michael@0 323 // If it was not requested to open directly in "edit" mode, we are done.
michael@0 324 if (!aShowEditUI)
michael@0 325 return;
michael@0 326
michael@0 327 // Try to dock the panel to:
michael@0 328 // 1. the bookmarks menu button
michael@0 329 // 2. the page-proxy-favicon
michael@0 330 // 3. the content area
michael@0 331 if (BookmarkingUI.anchor) {
michael@0 332 StarUI.showEditBookmarkPopup(itemId, BookmarkingUI.anchor,
michael@0 333 "bottomcenter topright");
michael@0 334 return;
michael@0 335 }
michael@0 336
michael@0 337 let pageProxyFavicon = document.getElementById("page-proxy-favicon");
michael@0 338 if (isElementVisible(pageProxyFavicon)) {
michael@0 339 StarUI.showEditBookmarkPopup(itemId, pageProxyFavicon,
michael@0 340 "bottomcenter topright");
michael@0 341 } else {
michael@0 342 StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap");
michael@0 343 }
michael@0 344 },
michael@0 345
michael@0 346 /**
michael@0 347 * Adds a bookmark to the page loaded in the current tab.
michael@0 348 */
michael@0 349 bookmarkCurrentPage: function PCH_bookmarkCurrentPage(aShowEditUI, aParent) {
michael@0 350 this.bookmarkPage(gBrowser.selectedBrowser, aParent, aShowEditUI);
michael@0 351 },
michael@0 352
michael@0 353 /**
michael@0 354 * Adds a bookmark to the page targeted by a link.
michael@0 355 * @param aParent
michael@0 356 * The folder in which to create a new bookmark if aURL isn't
michael@0 357 * bookmarked.
michael@0 358 * @param aURL (string)
michael@0 359 * the address of the link target
michael@0 360 * @param aTitle
michael@0 361 * The link text
michael@0 362 */
michael@0 363 bookmarkLink: function PCH_bookmarkLink(aParent, aURL, aTitle) {
michael@0 364 var linkURI = makeURI(aURL);
michael@0 365 var itemId = PlacesUtils.getMostRecentBookmarkForURI(linkURI);
michael@0 366 if (itemId == -1) {
michael@0 367 PlacesUIUtils.showBookmarkDialog({ action: "add"
michael@0 368 , type: "bookmark"
michael@0 369 , uri: linkURI
michael@0 370 , title: aTitle
michael@0 371 , hiddenRows: [ "description"
michael@0 372 , "location"
michael@0 373 , "loadInSidebar"
michael@0 374 , "keyword" ]
michael@0 375 }, window);
michael@0 376 }
michael@0 377 else {
michael@0 378 PlacesUIUtils.showBookmarkDialog({ action: "edit"
michael@0 379 , type: "bookmark"
michael@0 380 , itemId: itemId
michael@0 381 }, window);
michael@0 382 }
michael@0 383 },
michael@0 384
michael@0 385 /**
michael@0 386 * List of nsIURI objects characterizing the tabs currently open in the
michael@0 387 * browser, modulo pinned tabs. The URIs will be in the order in which their
michael@0 388 * corresponding tabs appeared and duplicates are discarded.
michael@0 389 */
michael@0 390 get uniqueCurrentPages() {
michael@0 391 let uniquePages = {};
michael@0 392 let URIs = [];
michael@0 393 gBrowser.visibleTabs.forEach(function (tab) {
michael@0 394 let spec = tab.linkedBrowser.currentURI.spec;
michael@0 395 if (!tab.pinned && !(spec in uniquePages)) {
michael@0 396 uniquePages[spec] = null;
michael@0 397 URIs.push(tab.linkedBrowser.currentURI);
michael@0 398 }
michael@0 399 });
michael@0 400 return URIs;
michael@0 401 },
michael@0 402
michael@0 403 /**
michael@0 404 * Adds a folder with bookmarks to all of the currently open tabs in this
michael@0 405 * window.
michael@0 406 */
michael@0 407 bookmarkCurrentPages: function PCH_bookmarkCurrentPages() {
michael@0 408 let pages = this.uniqueCurrentPages;
michael@0 409 if (pages.length > 1) {
michael@0 410 PlacesUIUtils.showBookmarkDialog({ action: "add"
michael@0 411 , type: "folder"
michael@0 412 , URIList: pages
michael@0 413 , hiddenRows: [ "description" ]
michael@0 414 }, window);
michael@0 415 }
michael@0 416 },
michael@0 417
michael@0 418 /**
michael@0 419 * Updates disabled state for the "Bookmark All Tabs" command.
michael@0 420 */
michael@0 421 updateBookmarkAllTabsCommand:
michael@0 422 function PCH_updateBookmarkAllTabsCommand() {
michael@0 423 // There's nothing to do in non-browser windows.
michael@0 424 if (window.location.href != getBrowserURL())
michael@0 425 return;
michael@0 426
michael@0 427 // Disable "Bookmark All Tabs" if there are less than two
michael@0 428 // "unique current pages".
michael@0 429 goSetCommandEnabled("Browser:BookmarkAllTabs",
michael@0 430 this.uniqueCurrentPages.length >= 2);
michael@0 431 },
michael@0 432
michael@0 433 /**
michael@0 434 * Adds a Live Bookmark to a feed associated with the current page.
michael@0 435 * @param url
michael@0 436 * The nsIURI of the page the feed was attached to
michael@0 437 * @title title
michael@0 438 * The title of the feed. Optional.
michael@0 439 * @subtitle subtitle
michael@0 440 * A short description of the feed. Optional.
michael@0 441 */
michael@0 442 addLiveBookmark: function PCH_addLiveBookmark(url, feedTitle, feedSubtitle) {
michael@0 443 var feedURI = makeURI(url);
michael@0 444
michael@0 445 var doc = gBrowser.contentDocument;
michael@0 446 var title = (arguments.length > 1) ? feedTitle : doc.title;
michael@0 447
michael@0 448 var description;
michael@0 449 if (arguments.length > 2)
michael@0 450 description = feedSubtitle;
michael@0 451 else
michael@0 452 description = PlacesUIUtils.getDescriptionFromDocument(doc);
michael@0 453
michael@0 454 var toolbarIP = new InsertionPoint(PlacesUtils.toolbarFolderId, -1);
michael@0 455 PlacesUIUtils.showBookmarkDialog({ action: "add"
michael@0 456 , type: "livemark"
michael@0 457 , feedURI: feedURI
michael@0 458 , siteURI: gBrowser.currentURI
michael@0 459 , title: title
michael@0 460 , description: description
michael@0 461 , defaultInsertionPoint: toolbarIP
michael@0 462 , hiddenRows: [ "feedLocation"
michael@0 463 , "siteLocation"
michael@0 464 , "description" ]
michael@0 465 }, window);
michael@0 466 },
michael@0 467
michael@0 468 /**
michael@0 469 * Opens the Places Organizer.
michael@0 470 * @param aLeftPaneRoot
michael@0 471 * The query to select in the organizer window - options
michael@0 472 * are: History, AllBookmarks, BookmarksMenu, BookmarksToolbar,
michael@0 473 * UnfiledBookmarks, Tags and Downloads.
michael@0 474 */
michael@0 475 showPlacesOrganizer: function PCH_showPlacesOrganizer(aLeftPaneRoot) {
michael@0 476 var organizer = Services.wm.getMostRecentWindow("Places:Organizer");
michael@0 477 if (!organizer) {
michael@0 478 // No currently open places window, so open one with the specified mode.
michael@0 479 openDialog("chrome://browser/content/places/places.xul",
michael@0 480 "", "chrome,toolbar=yes,dialog=no,resizable", aLeftPaneRoot);
michael@0 481 }
michael@0 482 else {
michael@0 483 organizer.PlacesOrganizer.selectLeftPaneContainerByHierarchy(aLeftPaneRoot);
michael@0 484 organizer.focus();
michael@0 485 }
michael@0 486 }
michael@0 487 };
michael@0 488
michael@0 489 ////////////////////////////////////////////////////////////////////////////////
michael@0 490 //// HistoryMenu
michael@0 491
michael@0 492 XPCOMUtils.defineLazyModuleGetter(this, "RecentlyClosedTabsAndWindowsMenuUtils",
michael@0 493 "resource:///modules/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm");
michael@0 494
michael@0 495 // View for the history menu.
michael@0 496 function HistoryMenu(aPopupShowingEvent) {
michael@0 497 // Workaround for Bug 610187. The sidebar does not include all the Places
michael@0 498 // views definitions, and we don't need them there.
michael@0 499 // Defining the prototype inheritance in the prototype itself would cause
michael@0 500 // browser.js to halt on "PlacesMenu is not defined" error.
michael@0 501 this.__proto__.__proto__ = PlacesMenu.prototype;
michael@0 502 PlacesMenu.call(this, aPopupShowingEvent,
michael@0 503 "place:sort=4&maxResults=15");
michael@0 504 }
michael@0 505
michael@0 506 HistoryMenu.prototype = {
michael@0 507 toggleRecentlyClosedTabs: function HM_toggleRecentlyClosedTabs() {
michael@0 508 // enable/disable the Recently Closed Tabs sub menu
michael@0 509 var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedTabsMenu")[0];
michael@0 510
michael@0 511 // no restorable tabs, so disable menu
michael@0 512 if (SessionStore.getClosedTabCount(window) == 0)
michael@0 513 undoMenu.setAttribute("disabled", true);
michael@0 514 else
michael@0 515 undoMenu.removeAttribute("disabled");
michael@0 516 },
michael@0 517
michael@0 518 /**
michael@0 519 * Populate when the history menu is opened
michael@0 520 */
michael@0 521 populateUndoSubmenu: function PHM_populateUndoSubmenu() {
michael@0 522 var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedTabsMenu")[0];
michael@0 523 var undoPopup = undoMenu.firstChild;
michael@0 524
michael@0 525 // remove existing menu items
michael@0 526 while (undoPopup.hasChildNodes())
michael@0 527 undoPopup.removeChild(undoPopup.firstChild);
michael@0 528
michael@0 529 // no restorable tabs, so make sure menu is disabled, and return
michael@0 530 if (SessionStore.getClosedTabCount(window) == 0) {
michael@0 531 undoMenu.setAttribute("disabled", true);
michael@0 532 return;
michael@0 533 }
michael@0 534
michael@0 535 // enable menu
michael@0 536 undoMenu.removeAttribute("disabled");
michael@0 537
michael@0 538 // populate menu
michael@0 539 let tabsFragment = RecentlyClosedTabsAndWindowsMenuUtils.getTabsFragment(window, "menuitem");
michael@0 540 undoPopup.appendChild(tabsFragment);
michael@0 541 },
michael@0 542
michael@0 543 toggleRecentlyClosedWindows: function PHM_toggleRecentlyClosedWindows() {
michael@0 544 // enable/disable the Recently Closed Windows sub menu
michael@0 545 var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedWindowsMenu")[0];
michael@0 546
michael@0 547 // no restorable windows, so disable menu
michael@0 548 if (SessionStore.getClosedWindowCount() == 0)
michael@0 549 undoMenu.setAttribute("disabled", true);
michael@0 550 else
michael@0 551 undoMenu.removeAttribute("disabled");
michael@0 552 },
michael@0 553
michael@0 554 /**
michael@0 555 * Populate when the history menu is opened
michael@0 556 */
michael@0 557 populateUndoWindowSubmenu: function PHM_populateUndoWindowSubmenu() {
michael@0 558 let undoMenu = this._rootElt.getElementsByClassName("recentlyClosedWindowsMenu")[0];
michael@0 559 let undoPopup = undoMenu.firstChild;
michael@0 560 let menuLabelString = gNavigatorBundle.getString("menuUndoCloseWindowLabel");
michael@0 561 let menuLabelStringSingleTab =
michael@0 562 gNavigatorBundle.getString("menuUndoCloseWindowSingleTabLabel");
michael@0 563
michael@0 564 // remove existing menu items
michael@0 565 while (undoPopup.hasChildNodes())
michael@0 566 undoPopup.removeChild(undoPopup.firstChild);
michael@0 567
michael@0 568 // no restorable windows, so make sure menu is disabled, and return
michael@0 569 if (SessionStore.getClosedWindowCount() == 0) {
michael@0 570 undoMenu.setAttribute("disabled", true);
michael@0 571 return;
michael@0 572 }
michael@0 573
michael@0 574 // enable menu
michael@0 575 undoMenu.removeAttribute("disabled");
michael@0 576
michael@0 577 // populate menu
michael@0 578 let windowsFragment = RecentlyClosedTabsAndWindowsMenuUtils.getWindowsFragment(window, "menuitem");
michael@0 579 undoPopup.appendChild(windowsFragment);
michael@0 580 },
michael@0 581
michael@0 582 toggleTabsFromOtherComputers: function PHM_toggleTabsFromOtherComputers() {
michael@0 583 // This is a no-op if MOZ_SERVICES_SYNC isn't defined
michael@0 584 #ifdef MOZ_SERVICES_SYNC
michael@0 585 // Enable/disable the Tabs From Other Computers menu. Some of the menus handled
michael@0 586 // by HistoryMenu do not have this menuitem.
michael@0 587 let menuitem = this._rootElt.getElementsByClassName("syncTabsMenuItem")[0];
michael@0 588 if (!menuitem)
michael@0 589 return;
michael@0 590
michael@0 591 if (!PlacesUIUtils.shouldShowTabsFromOtherComputersMenuitem()) {
michael@0 592 menuitem.setAttribute("hidden", true);
michael@0 593 return;
michael@0 594 }
michael@0 595
michael@0 596 let enabled = PlacesUIUtils.shouldEnableTabsFromOtherComputersMenuitem();
michael@0 597 menuitem.setAttribute("disabled", !enabled);
michael@0 598 menuitem.setAttribute("hidden", false);
michael@0 599 #endif
michael@0 600 },
michael@0 601
michael@0 602 _onPopupShowing: function HM__onPopupShowing(aEvent) {
michael@0 603 PlacesMenu.prototype._onPopupShowing.apply(this, arguments);
michael@0 604
michael@0 605 // Don't handle events for submenus.
michael@0 606 if (aEvent.target != aEvent.currentTarget)
michael@0 607 return;
michael@0 608
michael@0 609 this.toggleRecentlyClosedTabs();
michael@0 610 this.toggleRecentlyClosedWindows();
michael@0 611 this.toggleTabsFromOtherComputers();
michael@0 612 },
michael@0 613
michael@0 614 _onCommand: function HM__onCommand(aEvent) {
michael@0 615 let placesNode = aEvent.target._placesNode;
michael@0 616 if (placesNode) {
michael@0 617 if (!PrivateBrowsingUtils.isWindowPrivate(window))
michael@0 618 PlacesUIUtils.markPageAsTyped(placesNode.uri);
michael@0 619 openUILink(placesNode.uri, aEvent, { ignoreAlt: true });
michael@0 620 }
michael@0 621 }
michael@0 622 };
michael@0 623
michael@0 624 ////////////////////////////////////////////////////////////////////////////////
michael@0 625 //// BookmarksEventHandler
michael@0 626
michael@0 627 /**
michael@0 628 * Functions for handling events in the Bookmarks Toolbar and menu.
michael@0 629 */
michael@0 630 var BookmarksEventHandler = {
michael@0 631 /**
michael@0 632 * Handler for click event for an item in the bookmarks toolbar or menu.
michael@0 633 * Menus and submenus from the folder buttons bubble up to this handler.
michael@0 634 * Left-click is handled in the onCommand function.
michael@0 635 * When items are middle-clicked (or clicked with modifier), open in tabs.
michael@0 636 * If the click came through a menu, close the menu.
michael@0 637 * @param aEvent
michael@0 638 * DOMEvent for the click
michael@0 639 * @param aView
michael@0 640 * The places view which aEvent should be associated with.
michael@0 641 */
michael@0 642 onClick: function BEH_onClick(aEvent, aView) {
michael@0 643 // Only handle middle-click or left-click with modifiers.
michael@0 644 #ifdef XP_MACOSX
michael@0 645 var modifKey = aEvent.metaKey || aEvent.shiftKey;
michael@0 646 #else
michael@0 647 var modifKey = aEvent.ctrlKey || aEvent.shiftKey;
michael@0 648 #endif
michael@0 649 if (aEvent.button == 2 || (aEvent.button == 0 && !modifKey))
michael@0 650 return;
michael@0 651
michael@0 652 var target = aEvent.originalTarget;
michael@0 653 // If this event bubbled up from a menu or menuitem, close the menus.
michael@0 654 // Do this before opening tabs, to avoid hiding the open tabs confirm-dialog.
michael@0 655 if (target.localName == "menu" || target.localName == "menuitem") {
michael@0 656 for (node = target.parentNode; node; node = node.parentNode) {
michael@0 657 if (node.localName == "menupopup")
michael@0 658 node.hidePopup();
michael@0 659 else if (node.localName != "menu" &&
michael@0 660 node.localName != "splitmenu" &&
michael@0 661 node.localName != "hbox" &&
michael@0 662 node.localName != "vbox" )
michael@0 663 break;
michael@0 664 }
michael@0 665 }
michael@0 666
michael@0 667 if (target._placesNode && PlacesUtils.nodeIsContainer(target._placesNode)) {
michael@0 668 // Don't open the root folder in tabs when the empty area on the toolbar
michael@0 669 // is middle-clicked or when a non-bookmark item except for Open in Tabs)
michael@0 670 // in a bookmarks menupopup is middle-clicked.
michael@0 671 if (target.localName == "menu" || target.localName == "toolbarbutton")
michael@0 672 PlacesUIUtils.openContainerNodeInTabs(target._placesNode, aEvent, aView);
michael@0 673 }
michael@0 674 else if (aEvent.button == 1) {
michael@0 675 // left-clicks with modifier are already served by onCommand
michael@0 676 this.onCommand(aEvent, aView);
michael@0 677 }
michael@0 678 },
michael@0 679
michael@0 680 /**
michael@0 681 * Handler for command event for an item in the bookmarks toolbar.
michael@0 682 * Menus and submenus from the folder buttons bubble up to this handler.
michael@0 683 * Opens the item.
michael@0 684 * @param aEvent
michael@0 685 * DOMEvent for the command
michael@0 686 * @param aView
michael@0 687 * The places view which aEvent should be associated with.
michael@0 688 */
michael@0 689 onCommand: function BEH_onCommand(aEvent, aView) {
michael@0 690 var target = aEvent.originalTarget;
michael@0 691 if (target._placesNode)
michael@0 692 PlacesUIUtils.openNodeWithEvent(target._placesNode, aEvent, aView);
michael@0 693 },
michael@0 694
michael@0 695 fillInBHTooltip: function BEH_fillInBHTooltip(aDocument, aEvent) {
michael@0 696 var node;
michael@0 697 var cropped = false;
michael@0 698 var targetURI;
michael@0 699
michael@0 700 if (aDocument.tooltipNode.localName == "treechildren") {
michael@0 701 var tree = aDocument.tooltipNode.parentNode;
michael@0 702 var row = {}, column = {};
michael@0 703 var tbo = tree.treeBoxObject;
michael@0 704 tbo.getCellAt(aEvent.clientX, aEvent.clientY, row, column, {});
michael@0 705 if (row.value == -1)
michael@0 706 return false;
michael@0 707 node = tree.view.nodeForTreeIndex(row.value);
michael@0 708 cropped = tbo.isCellCropped(row.value, column.value);
michael@0 709 }
michael@0 710 else {
michael@0 711 // Check whether the tooltipNode is a Places node.
michael@0 712 // In such a case use it, otherwise check for targetURI attribute.
michael@0 713 var tooltipNode = aDocument.tooltipNode;
michael@0 714 if (tooltipNode._placesNode)
michael@0 715 node = tooltipNode._placesNode;
michael@0 716 else {
michael@0 717 // This is a static non-Places node.
michael@0 718 targetURI = tooltipNode.getAttribute("targetURI");
michael@0 719 }
michael@0 720 }
michael@0 721
michael@0 722 if (!node && !targetURI)
michael@0 723 return false;
michael@0 724
michael@0 725 // Show node.label as tooltip's title for non-Places nodes.
michael@0 726 var title = node ? node.title : tooltipNode.label;
michael@0 727
michael@0 728 // Show URL only for Places URI-nodes or nodes with a targetURI attribute.
michael@0 729 var url;
michael@0 730 if (targetURI || PlacesUtils.nodeIsURI(node))
michael@0 731 url = targetURI || node.uri;
michael@0 732
michael@0 733 // Show tooltip for containers only if their title is cropped.
michael@0 734 if (!cropped && !url)
michael@0 735 return false;
michael@0 736
michael@0 737 var tooltipTitle = aDocument.getElementById("bhtTitleText");
michael@0 738 tooltipTitle.hidden = (!title || (title == url));
michael@0 739 if (!tooltipTitle.hidden)
michael@0 740 tooltipTitle.textContent = title;
michael@0 741
michael@0 742 var tooltipUrl = aDocument.getElementById("bhtUrlText");
michael@0 743 tooltipUrl.hidden = !url;
michael@0 744 if (!tooltipUrl.hidden)
michael@0 745 tooltipUrl.value = url;
michael@0 746
michael@0 747 // Show tooltip.
michael@0 748 return true;
michael@0 749 }
michael@0 750 };
michael@0 751
michael@0 752 ////////////////////////////////////////////////////////////////////////////////
michael@0 753 //// PlacesMenuDNDHandler
michael@0 754
michael@0 755 // Handles special drag and drop functionality for Places menus that are not
michael@0 756 // part of a Places view (e.g. the bookmarks menu in the menubar).
michael@0 757 var PlacesMenuDNDHandler = {
michael@0 758 _springLoadDelayMs: 350,
michael@0 759 _closeDelayMs: 500,
michael@0 760 _loadTimer: null,
michael@0 761 _closeTimer: null,
michael@0 762 _closingTimerNode: null,
michael@0 763
michael@0 764 /**
michael@0 765 * Called when the user enters the <menu> element during a drag.
michael@0 766 * @param event
michael@0 767 * The DragEnter event that spawned the opening.
michael@0 768 */
michael@0 769 onDragEnter: function PMDH_onDragEnter(event) {
michael@0 770 // Opening menus in a Places popup is handled by the view itself.
michael@0 771 if (!this._isStaticContainer(event.target))
michael@0 772 return;
michael@0 773
michael@0 774 // If we re-enter the same menu or anchor before the close timer runs out,
michael@0 775 // we should ensure that we do not close:
michael@0 776 if (this._closeTimer && this._closingTimerNode === event.currentTarget) {
michael@0 777 this._closeTimer.cancel();
michael@0 778 this._closingTimerNode = null;
michael@0 779 this._closeTimer = null;
michael@0 780 }
michael@0 781
michael@0 782 PlacesControllerDragHelper.currentDropTarget = event.target;
michael@0 783 let popup = event.target.lastChild;
michael@0 784 if (this._loadTimer || popup.state === "showing" || popup.state === "open")
michael@0 785 return;
michael@0 786
michael@0 787 this._loadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
michael@0 788 this._loadTimer.initWithCallback(() => {
michael@0 789 this._loadTimer = null;
michael@0 790 popup.setAttribute("autoopened", "true");
michael@0 791 popup.showPopup(popup);
michael@0 792 }, this._springLoadDelayMs, Ci.nsITimer.TYPE_ONE_SHOT);
michael@0 793 event.preventDefault();
michael@0 794 event.stopPropagation();
michael@0 795 },
michael@0 796
michael@0 797 /**
michael@0 798 * Handles dragleave on the <menu> element.
michael@0 799 */
michael@0 800 onDragLeave: function PMDH_onDragLeave(event) {
michael@0 801 // Handle menu-button separate targets.
michael@0 802 if (event.relatedTarget === event.currentTarget ||
michael@0 803 (event.relatedTarget &&
michael@0 804 event.relatedTarget.parentNode === event.currentTarget))
michael@0 805 return;
michael@0 806
michael@0 807 // Closing menus in a Places popup is handled by the view itself.
michael@0 808 if (!this._isStaticContainer(event.target))
michael@0 809 return;
michael@0 810
michael@0 811 PlacesControllerDragHelper.currentDropTarget = null;
michael@0 812 let popup = event.target.lastChild;
michael@0 813
michael@0 814 if (this._loadTimer) {
michael@0 815 this._loadTimer.cancel();
michael@0 816 this._loadTimer = null;
michael@0 817 }
michael@0 818 this._closeTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
michael@0 819 this._closingTimerNode = event.currentTarget;
michael@0 820 this._closeTimer.initWithCallback(function() {
michael@0 821 this._closeTimer = null;
michael@0 822 this._closingTimerNode = null;
michael@0 823 let node = PlacesControllerDragHelper.currentDropTarget;
michael@0 824 let inHierarchy = false;
michael@0 825 while (node && !inHierarchy) {
michael@0 826 inHierarchy = node == event.target;
michael@0 827 node = node.parentNode;
michael@0 828 }
michael@0 829 if (!inHierarchy && popup && popup.hasAttribute("autoopened")) {
michael@0 830 popup.removeAttribute("autoopened");
michael@0 831 popup.hidePopup();
michael@0 832 }
michael@0 833 }, this._closeDelayMs, Ci.nsITimer.TYPE_ONE_SHOT);
michael@0 834 },
michael@0 835
michael@0 836 /**
michael@0 837 * Determines if a XUL element represents a static container.
michael@0 838 * @returns true if the element is a container element (menu or
michael@0 839 *` menu-toolbarbutton), false otherwise.
michael@0 840 */
michael@0 841 _isStaticContainer: function PMDH__isContainer(node) {
michael@0 842 let isMenu = node.localName == "menu" ||
michael@0 843 (node.localName == "toolbarbutton" &&
michael@0 844 (node.getAttribute("type") == "menu" ||
michael@0 845 node.getAttribute("type") == "menu-button"));
michael@0 846 let isStatic = !("_placesNode" in node) && node.lastChild &&
michael@0 847 node.lastChild.hasAttribute("placespopup") &&
michael@0 848 !node.parentNode.hasAttribute("placespopup");
michael@0 849 return isMenu && isStatic;
michael@0 850 },
michael@0 851
michael@0 852 /**
michael@0 853 * Called when the user drags over the <menu> element.
michael@0 854 * @param event
michael@0 855 * The DragOver event.
michael@0 856 */
michael@0 857 onDragOver: function PMDH_onDragOver(event) {
michael@0 858 let ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
michael@0 859 PlacesUtils.bookmarks.DEFAULT_INDEX,
michael@0 860 Ci.nsITreeView.DROP_ON);
michael@0 861 if (ip && PlacesControllerDragHelper.canDrop(ip, event.dataTransfer))
michael@0 862 event.preventDefault();
michael@0 863
michael@0 864 event.stopPropagation();
michael@0 865 },
michael@0 866
michael@0 867 /**
michael@0 868 * Called when the user drops on the <menu> element.
michael@0 869 * @param event
michael@0 870 * The Drop event.
michael@0 871 */
michael@0 872 onDrop: function PMDH_onDrop(event) {
michael@0 873 // Put the item at the end of bookmark menu.
michael@0 874 let ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
michael@0 875 PlacesUtils.bookmarks.DEFAULT_INDEX,
michael@0 876 Ci.nsITreeView.DROP_ON);
michael@0 877 PlacesControllerDragHelper.onDrop(ip, event.dataTransfer);
michael@0 878 PlacesControllerDragHelper.currentDropTarget = null;
michael@0 879 event.stopPropagation();
michael@0 880 }
michael@0 881 };
michael@0 882
michael@0 883 ////////////////////////////////////////////////////////////////////////////////
michael@0 884 //// PlacesToolbarHelper
michael@0 885
michael@0 886 /**
michael@0 887 * This object handles the initialization and uninitialization of the bookmarks
michael@0 888 * toolbar.
michael@0 889 */
michael@0 890 let PlacesToolbarHelper = {
michael@0 891 _place: "place:folder=TOOLBAR",
michael@0 892
michael@0 893 get _viewElt() {
michael@0 894 return document.getElementById("PlacesToolbar");
michael@0 895 },
michael@0 896
michael@0 897 get _placeholder() {
michael@0 898 return document.getElementById("bookmarks-toolbar-placeholder");
michael@0 899 },
michael@0 900
michael@0 901 init: function PTH_init(forceToolbarOverflowCheck) {
michael@0 902 let viewElt = this._viewElt;
michael@0 903 if (!viewElt || viewElt._placesView)
michael@0 904 return;
michael@0 905
michael@0 906 // CustomizableUI.addListener is idempotent, so we can safely
michael@0 907 // call this multiple times.
michael@0 908 CustomizableUI.addListener(this);
michael@0 909
michael@0 910 // If the bookmarks toolbar item is:
michael@0 911 // - not in a toolbar, or;
michael@0 912 // - the toolbar is collapsed, or;
michael@0 913 // - the toolbar is hidden some other way:
michael@0 914 // don't initialize. Also, there is no need to initialize the toolbar if
michael@0 915 // customizing, because that will happen when the customization is done.
michael@0 916 let toolbar = this._getParentToolbar(viewElt);
michael@0 917 if (!toolbar || toolbar.collapsed || this._isCustomizing ||
michael@0 918 getComputedStyle(toolbar, "").display == "none")
michael@0 919 return;
michael@0 920
michael@0 921 new PlacesToolbar(this._place);
michael@0 922 if (forceToolbarOverflowCheck) {
michael@0 923 viewElt._placesView.updateOverflowStatus();
michael@0 924 }
michael@0 925 this._setupPlaceholder();
michael@0 926 },
michael@0 927
michael@0 928 uninit: function PTH_uninit() {
michael@0 929 CustomizableUI.removeListener(this);
michael@0 930 },
michael@0 931
michael@0 932 customizeStart: function PTH_customizeStart() {
michael@0 933 try {
michael@0 934 let viewElt = this._viewElt;
michael@0 935 if (viewElt && viewElt._placesView)
michael@0 936 viewElt._placesView.uninit();
michael@0 937 } finally {
michael@0 938 this._isCustomizing = true;
michael@0 939 }
michael@0 940 this._shouldWrap = this._getShouldWrap();
michael@0 941 },
michael@0 942
michael@0 943 customizeChange: function PTH_customizeChange() {
michael@0 944 this._setupPlaceholder();
michael@0 945 },
michael@0 946
michael@0 947 _setupPlaceholder: function PTH_setupPlaceholder() {
michael@0 948 let placeholder = this._placeholder;
michael@0 949 if (!placeholder) {
michael@0 950 return;
michael@0 951 }
michael@0 952
michael@0 953 let shouldWrapNow = this._getShouldWrap();
michael@0 954 if (this._shouldWrap != shouldWrapNow) {
michael@0 955 if (shouldWrapNow) {
michael@0 956 placeholder.setAttribute("wrap", "true");
michael@0 957 } else {
michael@0 958 placeholder.removeAttribute("wrap");
michael@0 959 }
michael@0 960 this._shouldWrap = shouldWrapNow;
michael@0 961 }
michael@0 962 },
michael@0 963
michael@0 964 customizeDone: function PTH_customizeDone() {
michael@0 965 this._isCustomizing = false;
michael@0 966 this.init(true);
michael@0 967 },
michael@0 968
michael@0 969 _getShouldWrap: function PTH_getShouldWrap() {
michael@0 970 let placement = CustomizableUI.getPlacementOfWidget("personal-bookmarks");
michael@0 971 let area = placement && placement.area;
michael@0 972 let areaType = area && CustomizableUI.getAreaType(area);
michael@0 973 return !area || CustomizableUI.TYPE_MENU_PANEL == areaType;
michael@0 974 },
michael@0 975
michael@0 976 onPlaceholderCommand: function () {
michael@0 977 let widgetGroup = CustomizableUI.getWidget("personal-bookmarks");
michael@0 978 let widget = widgetGroup.forWindow(window);
michael@0 979 if (widget.overflowed ||
michael@0 980 widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
michael@0 981 PlacesCommandHook.showPlacesOrganizer("BookmarksToolbar");
michael@0 982 }
michael@0 983 },
michael@0 984
michael@0 985 _getParentToolbar: function(element) {
michael@0 986 while (element) {
michael@0 987 if (element.localName == "toolbar") {
michael@0 988 return element;
michael@0 989 }
michael@0 990 element = element.parentNode;
michael@0 991 }
michael@0 992 return null;
michael@0 993 },
michael@0 994
michael@0 995 onWidgetUnderflow: function(aNode, aContainer) {
michael@0 996 // The view gets broken by being removed and reinserted by the overflowable
michael@0 997 // toolbar, so we have to force an uninit and reinit.
michael@0 998 let win = aNode.ownerDocument.defaultView;
michael@0 999 if (aNode.id == "personal-bookmarks" && win == window) {
michael@0 1000 this._resetView();
michael@0 1001 }
michael@0 1002 },
michael@0 1003
michael@0 1004 onWidgetAdded: function(aWidgetId, aArea, aPosition) {
michael@0 1005 if (aWidgetId == "personal-bookmarks" && !this._isCustomizing) {
michael@0 1006 // It's possible (with the "Add to Menu", "Add to Toolbar" context
michael@0 1007 // options) that the Places Toolbar Items have been moved without
michael@0 1008 // letting us prepare and handle it with with customizeStart and
michael@0 1009 // customizeDone. If that's the case, we need to reset the views
michael@0 1010 // since they're probably broken from the DOM reparenting.
michael@0 1011 this._resetView();
michael@0 1012 }
michael@0 1013 },
michael@0 1014
michael@0 1015 _resetView: function() {
michael@0 1016 if (this._viewElt) {
michael@0 1017 // It's possible that the placesView might not exist, and we need to
michael@0 1018 // do a full init. This could happen if the Bookmarks Toolbar Items are
michael@0 1019 // moved to the Menu Panel, and then to the toolbar with the "Add to Toolbar"
michael@0 1020 // context menu option, outside of customize mode.
michael@0 1021 if (this._viewElt._placesView) {
michael@0 1022 this._viewElt._placesView.uninit();
michael@0 1023 }
michael@0 1024 this.init(true);
michael@0 1025 }
michael@0 1026 },
michael@0 1027 };
michael@0 1028
michael@0 1029 ////////////////////////////////////////////////////////////////////////////////
michael@0 1030 //// BookmarkingUI
michael@0 1031
michael@0 1032 /**
michael@0 1033 * Handles the bookmarks menu-button in the toolbar.
michael@0 1034 */
michael@0 1035
michael@0 1036 let BookmarkingUI = {
michael@0 1037 BOOKMARK_BUTTON_ID: "bookmarks-menu-button",
michael@0 1038 get button() {
michael@0 1039 delete this.button;
michael@0 1040 let widgetGroup = CustomizableUI.getWidget(this.BOOKMARK_BUTTON_ID);
michael@0 1041 return this.button = widgetGroup.forWindow(window).node;
michael@0 1042 },
michael@0 1043
michael@0 1044 /* Can't make this a self-deleting getter because it's anonymous content
michael@0 1045 * and might lose/regain bindings at some point. */
michael@0 1046 get star() {
michael@0 1047 return document.getAnonymousElementByAttribute(this.button, "anonid",
michael@0 1048 "button");
michael@0 1049 },
michael@0 1050
michael@0 1051 get anchor() {
michael@0 1052 if (!this._shouldUpdateStarState()) {
michael@0 1053 return null;
michael@0 1054 }
michael@0 1055 let widget = CustomizableUI.getWidget(this.BOOKMARK_BUTTON_ID)
michael@0 1056 .forWindow(window);
michael@0 1057 if (widget.overflowed)
michael@0 1058 return widget.anchor;
michael@0 1059
michael@0 1060 let star = this.star;
michael@0 1061 return star ? document.getAnonymousElementByAttribute(star, "class",
michael@0 1062 "toolbarbutton-icon")
michael@0 1063 : null;
michael@0 1064 },
michael@0 1065
michael@0 1066 get notifier() {
michael@0 1067 delete this.notifier;
michael@0 1068 return this.notifier = document.getElementById("bookmarked-notification-anchor");
michael@0 1069 },
michael@0 1070
michael@0 1071 get dropmarkerNotifier() {
michael@0 1072 delete this.dropmarkerNotifier;
michael@0 1073 return this.dropmarkerNotifier = document.getElementById("bookmarked-notification-dropmarker-anchor");
michael@0 1074 },
michael@0 1075
michael@0 1076 get broadcaster() {
michael@0 1077 delete this.broadcaster;
michael@0 1078 let broadcaster = document.getElementById("bookmarkThisPageBroadcaster");
michael@0 1079 return this.broadcaster = broadcaster;
michael@0 1080 },
michael@0 1081
michael@0 1082 STATUS_UPDATING: -1,
michael@0 1083 STATUS_UNSTARRED: 0,
michael@0 1084 STATUS_STARRED: 1,
michael@0 1085 get status() {
michael@0 1086 if (!this._shouldUpdateStarState()) {
michael@0 1087 return this.STATUS_UNSTARRED;
michael@0 1088 }
michael@0 1089 if (this._pendingStmt)
michael@0 1090 return this.STATUS_UPDATING;
michael@0 1091 return this.button.hasAttribute("starred") ? this.STATUS_STARRED
michael@0 1092 : this.STATUS_UNSTARRED;
michael@0 1093 },
michael@0 1094
michael@0 1095 get _starredTooltip()
michael@0 1096 {
michael@0 1097 delete this._starredTooltip;
michael@0 1098 return this._starredTooltip =
michael@0 1099 gNavigatorBundle.getString("starButtonOn.tooltip");
michael@0 1100 },
michael@0 1101
michael@0 1102 get _unstarredTooltip()
michael@0 1103 {
michael@0 1104 delete this._unstarredTooltip;
michael@0 1105 return this._unstarredTooltip =
michael@0 1106 gNavigatorBundle.getString("starButtonOff.tooltip");
michael@0 1107 },
michael@0 1108
michael@0 1109 /**
michael@0 1110 * The type of the area in which the button is currently located.
michael@0 1111 * When in the panel, we don't update the button's icon.
michael@0 1112 */
michael@0 1113 _currentAreaType: null,
michael@0 1114 _shouldUpdateStarState: function() {
michael@0 1115 return this._currentAreaType == CustomizableUI.TYPE_TOOLBAR;
michael@0 1116 },
michael@0 1117
michael@0 1118 /**
michael@0 1119 * The popup contents must be updated when the user customizes the UI, or
michael@0 1120 * changes the personal toolbar collapsed status. In such a case, any needed
michael@0 1121 * change should be handled in the popupshowing helper, for performance
michael@0 1122 * reasons.
michael@0 1123 */
michael@0 1124 _popupNeedsUpdate: true,
michael@0 1125 onToolbarVisibilityChange: function BUI_onToolbarVisibilityChange() {
michael@0 1126 this._popupNeedsUpdate = true;
michael@0 1127 },
michael@0 1128
michael@0 1129 onPopupShowing: function BUI_onPopupShowing(event) {
michael@0 1130 // Don't handle events for submenus.
michael@0 1131 if (event.target != event.currentTarget)
michael@0 1132 return;
michael@0 1133
michael@0 1134 // Ideally this code would never be reached, but if you click the outer
michael@0 1135 // button's border, some cpp code for the menu button's so-called XBL binding
michael@0 1136 // decides to open the popup even though the dropmarker is invisible.
michael@0 1137 if (this._currentAreaType == CustomizableUI.TYPE_MENU_PANEL) {
michael@0 1138 this._showSubview();
michael@0 1139 event.preventDefault();
michael@0 1140 event.stopPropagation();
michael@0 1141 return;
michael@0 1142 }
michael@0 1143
michael@0 1144 let widget = CustomizableUI.getWidget(this.BOOKMARK_BUTTON_ID)
michael@0 1145 .forWindow(window);
michael@0 1146 if (widget.overflowed) {
michael@0 1147 // Don't open a popup in the overflow popup, rather just open the Library.
michael@0 1148 event.preventDefault();
michael@0 1149 widget.node.removeAttribute("closemenu");
michael@0 1150 PlacesCommandHook.showPlacesOrganizer("BookmarksMenu");
michael@0 1151 return;
michael@0 1152 }
michael@0 1153
michael@0 1154 if (!this._popupNeedsUpdate)
michael@0 1155 return;
michael@0 1156 this._popupNeedsUpdate = false;
michael@0 1157
michael@0 1158 let popup = event.target;
michael@0 1159 let getPlacesAnonymousElement =
michael@0 1160 aAnonId => document.getAnonymousElementByAttribute(popup.parentNode,
michael@0 1161 "placesanonid",
michael@0 1162 aAnonId);
michael@0 1163
michael@0 1164 let viewToolbarMenuitem = getPlacesAnonymousElement("view-toolbar");
michael@0 1165 if (viewToolbarMenuitem) {
michael@0 1166 // Update View bookmarks toolbar checkbox menuitem.
michael@0 1167 viewToolbarMenuitem.classList.add("subviewbutton");
michael@0 1168 let personalToolbar = document.getElementById("PersonalToolbar");
michael@0 1169 viewToolbarMenuitem.setAttribute("checked", !personalToolbar.collapsed);
michael@0 1170 }
michael@0 1171 },
michael@0 1172
michael@0 1173 attachPlacesView: function(event, node) {
michael@0 1174 // If the view is already there, bail out early.
michael@0 1175 if (node.parentNode._placesView)
michael@0 1176 return;
michael@0 1177
michael@0 1178 new PlacesMenu(event, "place:folder=BOOKMARKS_MENU", {
michael@0 1179 extraClasses: {
michael@0 1180 entry: "subviewbutton",
michael@0 1181 footer: "panel-subview-footer"
michael@0 1182 },
michael@0 1183 insertionPoint: ".panel-subview-footer"
michael@0 1184 });
michael@0 1185 },
michael@0 1186
michael@0 1187 /**
michael@0 1188 * Handles star styling based on page proxy state changes.
michael@0 1189 */
michael@0 1190 onPageProxyStateChanged: function BUI_onPageProxyStateChanged(aState) {
michael@0 1191 if (!this._shouldUpdateStarState() || !this.star) {
michael@0 1192 return;
michael@0 1193 }
michael@0 1194
michael@0 1195 if (aState == "invalid") {
michael@0 1196 this.star.setAttribute("disabled", "true");
michael@0 1197 this.button.removeAttribute("starred");
michael@0 1198 this.button.setAttribute("buttontooltiptext", "");
michael@0 1199 }
michael@0 1200 else {
michael@0 1201 this.star.removeAttribute("disabled");
michael@0 1202 this._updateStar();
michael@0 1203 }
michael@0 1204 this._updateToolbarStyle();
michael@0 1205 },
michael@0 1206
michael@0 1207 _updateCustomizationState: function BUI__updateCustomizationState() {
michael@0 1208 let placement = CustomizableUI.getPlacementOfWidget(this.BOOKMARK_BUTTON_ID);
michael@0 1209 this._currentAreaType = placement && CustomizableUI.getAreaType(placement.area);
michael@0 1210 },
michael@0 1211
michael@0 1212 _updateToolbarStyle: function BUI__updateToolbarStyle() {
michael@0 1213 let onPersonalToolbar = false;
michael@0 1214 if (this._currentAreaType == CustomizableUI.TYPE_TOOLBAR) {
michael@0 1215 let personalToolbar = document.getElementById("PersonalToolbar");
michael@0 1216 onPersonalToolbar = this.button.parentNode == personalToolbar ||
michael@0 1217 this.button.parentNode.parentNode == personalToolbar;
michael@0 1218 }
michael@0 1219
michael@0 1220 if (onPersonalToolbar)
michael@0 1221 this.button.classList.add("bookmark-item");
michael@0 1222 else
michael@0 1223 this.button.classList.remove("bookmark-item");
michael@0 1224 },
michael@0 1225
michael@0 1226 _uninitView: function BUI__uninitView() {
michael@0 1227 // When an element with a placesView attached is removed and re-inserted,
michael@0 1228 // XBL reapplies the binding causing any kind of issues and possible leaks,
michael@0 1229 // so kill current view and let popupshowing generate a new one.
michael@0 1230 if (this.button._placesView)
michael@0 1231 this.button._placesView.uninit();
michael@0 1232
michael@0 1233 // We have to do the same thing for the "special" views underneath the
michael@0 1234 // the bookmarks menu.
michael@0 1235 const kSpecialViewNodeIDs = ["BMB_bookmarksToolbar", "BMB_unsortedBookmarks"];
michael@0 1236 for (let viewNodeID of kSpecialViewNodeIDs) {
michael@0 1237 let elem = document.getElementById(viewNodeID);
michael@0 1238 if (elem && elem._placesView) {
michael@0 1239 elem._placesView.uninit();
michael@0 1240 }
michael@0 1241 }
michael@0 1242 },
michael@0 1243
michael@0 1244 onCustomizeStart: function BUI_customizeStart(aWindow) {
michael@0 1245 if (aWindow == window) {
michael@0 1246 this._uninitView();
michael@0 1247 this._isCustomizing = true;
michael@0 1248 }
michael@0 1249 },
michael@0 1250
michael@0 1251 onWidgetAdded: function BUI_widgetAdded(aWidgetId) {
michael@0 1252 if (aWidgetId == this.BOOKMARK_BUTTON_ID) {
michael@0 1253 this._onWidgetWasMoved();
michael@0 1254 }
michael@0 1255 },
michael@0 1256
michael@0 1257 onWidgetRemoved: function BUI_widgetRemoved(aWidgetId) {
michael@0 1258 if (aWidgetId == this.BOOKMARK_BUTTON_ID) {
michael@0 1259 this._onWidgetWasMoved();
michael@0 1260 }
michael@0 1261 },
michael@0 1262
michael@0 1263 onWidgetReset: function BUI_widgetReset(aNode, aContainer) {
michael@0 1264 if (aNode == this.button) {
michael@0 1265 this._onWidgetWasMoved();
michael@0 1266 }
michael@0 1267 },
michael@0 1268
michael@0 1269 onWidgetUndoMove: function BUI_undoWidgetUndoMove(aNode, aContainer) {
michael@0 1270 if (aNode == this.button) {
michael@0 1271 this._onWidgetWasMoved();
michael@0 1272 }
michael@0 1273 },
michael@0 1274
michael@0 1275 _onWidgetWasMoved: function BUI_widgetWasMoved() {
michael@0 1276 let usedToUpdateStarState = this._shouldUpdateStarState();
michael@0 1277 this._updateCustomizationState();
michael@0 1278 if (!usedToUpdateStarState && this._shouldUpdateStarState()) {
michael@0 1279 this.updateStarState();
michael@0 1280 } else if (usedToUpdateStarState && !this._shouldUpdateStarState()) {
michael@0 1281 this._updateStar();
michael@0 1282 }
michael@0 1283 // If we're moved outside of customize mode, we need to uninit
michael@0 1284 // our view so it gets reconstructed.
michael@0 1285 if (!this._isCustomizing) {
michael@0 1286 this._uninitView();
michael@0 1287 }
michael@0 1288 this._updateToolbarStyle();
michael@0 1289 },
michael@0 1290
michael@0 1291 onCustomizeEnd: function BUI_customizeEnd(aWindow) {
michael@0 1292 if (aWindow == window) {
michael@0 1293 this._isCustomizing = false;
michael@0 1294 this.onToolbarVisibilityChange();
michael@0 1295 this._updateToolbarStyle();
michael@0 1296 }
michael@0 1297 },
michael@0 1298
michael@0 1299 init: function() {
michael@0 1300 CustomizableUI.addListener(this);
michael@0 1301 this._updateCustomizationState();
michael@0 1302 },
michael@0 1303
michael@0 1304 _hasBookmarksObserver: false,
michael@0 1305 _itemIds: [],
michael@0 1306 uninit: function BUI_uninit() {
michael@0 1307 this._updateBookmarkPageMenuItem(true);
michael@0 1308 CustomizableUI.removeListener(this);
michael@0 1309
michael@0 1310 this._uninitView();
michael@0 1311
michael@0 1312 if (this._hasBookmarksObserver) {
michael@0 1313 PlacesUtils.removeLazyBookmarkObserver(this);
michael@0 1314 }
michael@0 1315
michael@0 1316 if (this._pendingStmt) {
michael@0 1317 this._pendingStmt.cancel();
michael@0 1318 delete this._pendingStmt;
michael@0 1319 }
michael@0 1320 },
michael@0 1321
michael@0 1322 onLocationChange: function BUI_onLocationChange() {
michael@0 1323 if (this._uri && gBrowser.currentURI.equals(this._uri)) {
michael@0 1324 return;
michael@0 1325 }
michael@0 1326 this.updateStarState();
michael@0 1327 },
michael@0 1328
michael@0 1329 updateStarState: function BUI_updateStarState() {
michael@0 1330 // Reset tracked values.
michael@0 1331 this._uri = gBrowser.currentURI;
michael@0 1332 this._itemIds = [];
michael@0 1333
michael@0 1334 if (this._pendingStmt) {
michael@0 1335 this._pendingStmt.cancel();
michael@0 1336 delete this._pendingStmt;
michael@0 1337 }
michael@0 1338
michael@0 1339 // We can load about:blank before the actual page, but there is no point in handling that page.
michael@0 1340 if (isBlankPageURL(this._uri.spec)) {
michael@0 1341 return;
michael@0 1342 }
michael@0 1343
michael@0 1344 this._pendingStmt = PlacesUtils.asyncGetBookmarkIds(this._uri, (aItemIds, aURI) => {
michael@0 1345 // Safety check that the bookmarked URI equals the tracked one.
michael@0 1346 if (!aURI.equals(this._uri)) {
michael@0 1347 Components.utils.reportError("BookmarkingUI did not receive current URI");
michael@0 1348 return;
michael@0 1349 }
michael@0 1350
michael@0 1351 // It's possible that onItemAdded gets called before the async statement
michael@0 1352 // calls back. For such an edge case, retain all unique entries from both
michael@0 1353 // arrays.
michael@0 1354 this._itemIds = this._itemIds.filter(
michael@0 1355 function (id) aItemIds.indexOf(id) == -1
michael@0 1356 ).concat(aItemIds);
michael@0 1357
michael@0 1358 this._updateStar();
michael@0 1359
michael@0 1360 // Start observing bookmarks if needed.
michael@0 1361 if (!this._hasBookmarksObserver) {
michael@0 1362 try {
michael@0 1363 PlacesUtils.addLazyBookmarkObserver(this);
michael@0 1364 this._hasBookmarksObserver = true;
michael@0 1365 } catch(ex) {
michael@0 1366 Components.utils.reportError("BookmarkingUI failed adding a bookmarks observer: " + ex);
michael@0 1367 }
michael@0 1368 }
michael@0 1369
michael@0 1370 delete this._pendingStmt;
michael@0 1371 });
michael@0 1372 },
michael@0 1373
michael@0 1374 _updateStar: function BUI__updateStar() {
michael@0 1375 if (!this._shouldUpdateStarState()) {
michael@0 1376 if (this.button.hasAttribute("starred")) {
michael@0 1377 this.button.removeAttribute("starred");
michael@0 1378 this.button.removeAttribute("buttontooltiptext");
michael@0 1379 }
michael@0 1380 return;
michael@0 1381 }
michael@0 1382
michael@0 1383 if (this._itemIds.length > 0) {
michael@0 1384 this.button.setAttribute("starred", "true");
michael@0 1385 this.button.setAttribute("buttontooltiptext", this._starredTooltip);
michael@0 1386 if (this.button.getAttribute("overflowedItem") == "true") {
michael@0 1387 this.button.setAttribute("label", this._starButtonOverflowedStarredLabel);
michael@0 1388 }
michael@0 1389 }
michael@0 1390 else {
michael@0 1391 this.button.removeAttribute("starred");
michael@0 1392 this.button.setAttribute("buttontooltiptext", this._unstarredTooltip);
michael@0 1393 if (this.button.getAttribute("overflowedItem") == "true") {
michael@0 1394 this.button.setAttribute("label", this._starButtonOverflowedLabel);
michael@0 1395 }
michael@0 1396 }
michael@0 1397 },
michael@0 1398
michael@0 1399 /**
michael@0 1400 * forceReset is passed when we're destroyed and the label should go back
michael@0 1401 * to the default (Bookmark This Page) for OS X.
michael@0 1402 */
michael@0 1403 _updateBookmarkPageMenuItem: function BUI__updateBookmarkPageMenuItem(forceReset) {
michael@0 1404 let isStarred = !forceReset && this._itemIds.length > 0;
michael@0 1405 let label = isStarred ? "editlabel" : "bookmarklabel";
michael@0 1406 this.broadcaster.setAttribute("label", this.broadcaster.getAttribute(label));
michael@0 1407 },
michael@0 1408
michael@0 1409 onMainMenuPopupShowing: function BUI_onMainMenuPopupShowing(event) {
michael@0 1410 this._updateBookmarkPageMenuItem();
michael@0 1411 PlacesCommandHook.updateBookmarkAllTabsCommand();
michael@0 1412 },
michael@0 1413
michael@0 1414 _showBookmarkedNotification: function BUI_showBookmarkedNotification() {
michael@0 1415 function getCenteringTransformForRects(rectToPosition, referenceRect) {
michael@0 1416 let topDiff = referenceRect.top - rectToPosition.top;
michael@0 1417 let leftDiff = referenceRect.left - rectToPosition.left;
michael@0 1418 let heightDiff = referenceRect.height - rectToPosition.height;
michael@0 1419 let widthDiff = referenceRect.width - rectToPosition.width;
michael@0 1420 return [(leftDiff + .5 * widthDiff) + "px", (topDiff + .5 * heightDiff) + "px"];
michael@0 1421 }
michael@0 1422
michael@0 1423 if (this._notificationTimeout) {
michael@0 1424 clearTimeout(this._notificationTimeout);
michael@0 1425 }
michael@0 1426
michael@0 1427 if (this.notifier.style.transform == '') {
michael@0 1428 // Get all the relevant nodes and computed style objects
michael@0 1429 let dropmarker = document.getAnonymousElementByAttribute(this.button, "anonid", "dropmarker");
michael@0 1430 let dropmarkerIcon = document.getAnonymousElementByAttribute(dropmarker, "class", "dropmarker-icon");
michael@0 1431 let dropmarkerStyle = getComputedStyle(dropmarkerIcon);
michael@0 1432
michael@0 1433 // Check for RTL and get bounds
michael@0 1434 let isRTL = getComputedStyle(this.button).direction == "rtl";
michael@0 1435 let buttonRect = this.button.getBoundingClientRect();
michael@0 1436 let notifierRect = this.notifier.getBoundingClientRect();
michael@0 1437 let dropmarkerRect = dropmarkerIcon.getBoundingClientRect();
michael@0 1438 let dropmarkerNotifierRect = this.dropmarkerNotifier.getBoundingClientRect();
michael@0 1439
michael@0 1440 // Compute, but do not set, transform for star icon
michael@0 1441 let [translateX, translateY] = getCenteringTransformForRects(notifierRect, buttonRect);
michael@0 1442 let starIconTransform = "translate(" + translateX + ", " + translateY + ")";
michael@0 1443 if (isRTL) {
michael@0 1444 starIconTransform += " scaleX(-1)";
michael@0 1445 }
michael@0 1446
michael@0 1447 // Compute, but do not set, transform for dropmarker
michael@0 1448 [translateX, translateY] = getCenteringTransformForRects(dropmarkerNotifierRect, dropmarkerRect);
michael@0 1449 let dropmarkerTransform = "translate(" + translateX + ", " + translateY + ")";
michael@0 1450
michael@0 1451 // Do all layout invalidation in one go:
michael@0 1452 this.notifier.style.transform = starIconTransform;
michael@0 1453 this.dropmarkerNotifier.style.transform = dropmarkerTransform;
michael@0 1454
michael@0 1455 let dropmarkerAnimationNode = this.dropmarkerNotifier.firstChild;
michael@0 1456 dropmarkerAnimationNode.style.MozImageRegion = dropmarkerStyle.MozImageRegion;
michael@0 1457 dropmarkerAnimationNode.style.listStyleImage = dropmarkerStyle.listStyleImage;
michael@0 1458 }
michael@0 1459
michael@0 1460 let isInOverflowPanel = this.button.getAttribute("overflowedItem") == "true";
michael@0 1461 if (!isInOverflowPanel) {
michael@0 1462 this.notifier.setAttribute("notification", "finish");
michael@0 1463 this.button.setAttribute("notification", "finish");
michael@0 1464 this.dropmarkerNotifier.setAttribute("notification", "finish");
michael@0 1465 }
michael@0 1466
michael@0 1467 this._notificationTimeout = setTimeout( () => {
michael@0 1468 this.notifier.removeAttribute("notification");
michael@0 1469 this.dropmarkerNotifier.removeAttribute("notification");
michael@0 1470 this.button.removeAttribute("notification");
michael@0 1471
michael@0 1472 this.dropmarkerNotifier.style.transform = '';
michael@0 1473 this.notifier.style.transform = '';
michael@0 1474 }, 1000);
michael@0 1475 },
michael@0 1476
michael@0 1477 _showSubview: function() {
michael@0 1478 let view = document.getElementById("PanelUI-bookmarks");
michael@0 1479 view.addEventListener("ViewShowing", this);
michael@0 1480 view.addEventListener("ViewHiding", this);
michael@0 1481 let anchor = document.getElementById(this.BOOKMARK_BUTTON_ID);
michael@0 1482 anchor.setAttribute("closemenu", "none");
michael@0 1483 PanelUI.showSubView("PanelUI-bookmarks", anchor,
michael@0 1484 CustomizableUI.AREA_PANEL);
michael@0 1485 },
michael@0 1486
michael@0 1487 onCommand: function BUI_onCommand(aEvent) {
michael@0 1488 if (aEvent.target != aEvent.currentTarget) {
michael@0 1489 return;
michael@0 1490 }
michael@0 1491
michael@0 1492 // Handle special case when the button is in the panel.
michael@0 1493 let isBookmarked = this._itemIds.length > 0;
michael@0 1494
michael@0 1495 if (this._currentAreaType == CustomizableUI.TYPE_MENU_PANEL) {
michael@0 1496 this._showSubview();
michael@0 1497 return;
michael@0 1498 }
michael@0 1499 let widget = CustomizableUI.getWidget(this.BOOKMARK_BUTTON_ID)
michael@0 1500 .forWindow(window);
michael@0 1501 if (widget.overflowed) {
michael@0 1502 // Allow to close the panel if the page is already bookmarked, cause
michael@0 1503 // we are going to open the edit bookmark panel.
michael@0 1504 if (isBookmarked)
michael@0 1505 widget.node.removeAttribute("closemenu");
michael@0 1506 else
michael@0 1507 widget.node.setAttribute("closemenu", "none");
michael@0 1508 }
michael@0 1509
michael@0 1510 // Ignore clicks on the star if we are updating its state.
michael@0 1511 if (!this._pendingStmt) {
michael@0 1512 if (!isBookmarked)
michael@0 1513 this._showBookmarkedNotification();
michael@0 1514 PlacesCommandHook.bookmarkCurrentPage(isBookmarked);
michael@0 1515 }
michael@0 1516 },
michael@0 1517
michael@0 1518 handleEvent: function BUI_handleEvent(aEvent) {
michael@0 1519 switch (aEvent.type) {
michael@0 1520 case "ViewShowing":
michael@0 1521 this.onPanelMenuViewShowing(aEvent);
michael@0 1522 break;
michael@0 1523 case "ViewHiding":
michael@0 1524 this.onPanelMenuViewHiding(aEvent);
michael@0 1525 break;
michael@0 1526 }
michael@0 1527 },
michael@0 1528
michael@0 1529 onPanelMenuViewShowing: function BUI_onViewShowing(aEvent) {
michael@0 1530 this._updateBookmarkPageMenuItem();
michael@0 1531 // Update checked status of the toolbar toggle.
michael@0 1532 let viewToolbar = document.getElementById("panelMenu_viewBookmarksToolbar");
michael@0 1533 let personalToolbar = document.getElementById("PersonalToolbar");
michael@0 1534 if (personalToolbar.collapsed)
michael@0 1535 viewToolbar.removeAttribute("checked");
michael@0 1536 else
michael@0 1537 viewToolbar.setAttribute("checked", "true");
michael@0 1538 // Setup the Places view.
michael@0 1539 this._panelMenuView = new PlacesPanelMenuView("place:folder=BOOKMARKS_MENU",
michael@0 1540 "panelMenu_bookmarksMenu",
michael@0 1541 "panelMenu_bookmarksMenu", {
michael@0 1542 extraClasses: {
michael@0 1543 entry: "subviewbutton",
michael@0 1544 footer: "panel-subview-footer"
michael@0 1545 }
michael@0 1546 });
michael@0 1547 aEvent.target.removeEventListener("ViewShowing", this);
michael@0 1548 },
michael@0 1549
michael@0 1550 onPanelMenuViewHiding: function BUI_onViewHiding(aEvent) {
michael@0 1551 this._panelMenuView.uninit();
michael@0 1552 delete this._panelMenuView;
michael@0 1553 aEvent.target.removeEventListener("ViewHiding", this);
michael@0 1554 },
michael@0 1555
michael@0 1556 onPanelMenuViewCommand: function BUI_onPanelMenuViewCommand(aEvent, aView) {
michael@0 1557 let target = aEvent.originalTarget;
michael@0 1558 if (!target._placesNode)
michael@0 1559 return;
michael@0 1560 if (PlacesUtils.nodeIsContainer(target._placesNode))
michael@0 1561 PlacesCommandHook.showPlacesOrganizer([ "BookmarksMenu", target._placesNode.itemId ]);
michael@0 1562 else
michael@0 1563 PlacesUIUtils.openNodeWithEvent(target._placesNode, aEvent, aView);
michael@0 1564 PanelUI.hide();
michael@0 1565 },
michael@0 1566
michael@0 1567 // nsINavBookmarkObserver
michael@0 1568 onItemAdded: function BUI_onItemAdded(aItemId, aParentId, aIndex, aItemType,
michael@0 1569 aURI) {
michael@0 1570 if (aURI && aURI.equals(this._uri)) {
michael@0 1571 // If a new bookmark has been added to the tracked uri, register it.
michael@0 1572 if (this._itemIds.indexOf(aItemId) == -1) {
michael@0 1573 this._itemIds.push(aItemId);
michael@0 1574 // Only need to update the UI if it wasn't marked as starred before:
michael@0 1575 if (this._itemIds.length == 1) {
michael@0 1576 this._updateStar();
michael@0 1577 }
michael@0 1578 }
michael@0 1579 }
michael@0 1580 },
michael@0 1581
michael@0 1582 onItemRemoved: function BUI_onItemRemoved(aItemId) {
michael@0 1583 let index = this._itemIds.indexOf(aItemId);
michael@0 1584 // If one of the tracked bookmarks has been removed, unregister it.
michael@0 1585 if (index != -1) {
michael@0 1586 this._itemIds.splice(index, 1);
michael@0 1587 // Only need to update the UI if the page is no longer starred
michael@0 1588 if (this._itemIds.length == 0) {
michael@0 1589 this._updateStar();
michael@0 1590 }
michael@0 1591 }
michael@0 1592 },
michael@0 1593
michael@0 1594 onItemChanged: function BUI_onItemChanged(aItemId, aProperty,
michael@0 1595 aIsAnnotationProperty, aNewValue) {
michael@0 1596 if (aProperty == "uri") {
michael@0 1597 let index = this._itemIds.indexOf(aItemId);
michael@0 1598 // If the changed bookmark was tracked, check if it is now pointing to
michael@0 1599 // a different uri and unregister it.
michael@0 1600 if (index != -1 && aNewValue != this._uri.spec) {
michael@0 1601 this._itemIds.splice(index, 1);
michael@0 1602 // Only need to update the UI if the page is no longer starred
michael@0 1603 if (this._itemIds.length == 0) {
michael@0 1604 this._updateStar();
michael@0 1605 }
michael@0 1606 }
michael@0 1607 // If another bookmark is now pointing to the tracked uri, register it.
michael@0 1608 else if (index == -1 && aNewValue == this._uri.spec) {
michael@0 1609 this._itemIds.push(aItemId);
michael@0 1610 // Only need to update the UI if it wasn't marked as starred before:
michael@0 1611 if (this._itemIds.length == 1) {
michael@0 1612 this._updateStar();
michael@0 1613 }
michael@0 1614 }
michael@0 1615 }
michael@0 1616 },
michael@0 1617
michael@0 1618 onBeginUpdateBatch: function () {},
michael@0 1619 onEndUpdateBatch: function () {},
michael@0 1620 onBeforeItemRemoved: function () {},
michael@0 1621 onItemVisited: function () {},
michael@0 1622 onItemMoved: function () {},
michael@0 1623
michael@0 1624 // CustomizableUI events:
michael@0 1625 _starButtonLabel: null,
michael@0 1626 get _starButtonOverflowedLabel() {
michael@0 1627 delete this._starButtonOverflowedLabel;
michael@0 1628 return this._starButtonOverflowedLabel =
michael@0 1629 gNavigatorBundle.getString("starButtonOverflowed.label");
michael@0 1630 },
michael@0 1631 get _starButtonOverflowedStarredLabel() {
michael@0 1632 delete this._starButtonOverflowedStarredLabel;
michael@0 1633 return this._starButtonOverflowedStarredLabel =
michael@0 1634 gNavigatorBundle.getString("starButtonOverflowedStarred.label");
michael@0 1635 },
michael@0 1636 onWidgetOverflow: function(aNode, aContainer) {
michael@0 1637 let win = aNode.ownerDocument.defaultView;
michael@0 1638 if (aNode.id != this.BOOKMARK_BUTTON_ID || win != window)
michael@0 1639 return;
michael@0 1640
michael@0 1641 let currentLabel = aNode.getAttribute("label");
michael@0 1642 if (!this._starButtonLabel)
michael@0 1643 this._starButtonLabel = currentLabel;
michael@0 1644
michael@0 1645 if (currentLabel == this._starButtonLabel) {
michael@0 1646 let desiredLabel = this._itemIds.length > 0 ? this._starButtonOverflowedStarredLabel
michael@0 1647 : this._starButtonOverflowedLabel;
michael@0 1648 aNode.setAttribute("label", desiredLabel);
michael@0 1649 }
michael@0 1650 },
michael@0 1651
michael@0 1652 onWidgetUnderflow: function(aNode, aContainer) {
michael@0 1653 let win = aNode.ownerDocument.defaultView;
michael@0 1654 if (aNode.id != this.BOOKMARK_BUTTON_ID || win != window)
michael@0 1655 return;
michael@0 1656
michael@0 1657 // The view gets broken by being removed and reinserted. Uninit
michael@0 1658 // here so popupshowing will generate a new one:
michael@0 1659 this._uninitView();
michael@0 1660
michael@0 1661 if (aNode.getAttribute("label") != this._starButtonLabel)
michael@0 1662 aNode.setAttribute("label", this._starButtonLabel);
michael@0 1663 },
michael@0 1664
michael@0 1665 QueryInterface: XPCOMUtils.generateQI([
michael@0 1666 Ci.nsINavBookmarkObserver
michael@0 1667 ])
michael@0 1668 };

mercurial