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.

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

mercurial