browser/base/content/browser-places.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/base/content/browser-places.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1668 @@
     1.4 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.7 +
     1.8 +////////////////////////////////////////////////////////////////////////////////
     1.9 +//// StarUI
    1.10 +
    1.11 +var StarUI = {
    1.12 +  _itemId: -1,
    1.13 +  uri: null,
    1.14 +  _batching: false,
    1.15 +
    1.16 +  _element: function(aID) {
    1.17 +    return document.getElementById(aID);
    1.18 +  },
    1.19 +
    1.20 +  // Edit-bookmark panel
    1.21 +  get panel() {
    1.22 +    delete this.panel;
    1.23 +    var element = this._element("editBookmarkPanel");
    1.24 +    // initially the panel is hidden
    1.25 +    // to avoid impacting startup / new window performance
    1.26 +    element.hidden = false;
    1.27 +    element.addEventListener("popuphidden", this, false);
    1.28 +    element.addEventListener("keypress", this, false);
    1.29 +    return this.panel = element;
    1.30 +  },
    1.31 +
    1.32 +  // Array of command elements to disable when the panel is opened.
    1.33 +  get _blockedCommands() {
    1.34 +    delete this._blockedCommands;
    1.35 +    return this._blockedCommands =
    1.36 +      ["cmd_close", "cmd_closeWindow"].map(function (id) this._element(id), this);
    1.37 +  },
    1.38 +
    1.39 +  _blockCommands: function SU__blockCommands() {
    1.40 +    this._blockedCommands.forEach(function (elt) {
    1.41 +      // make sure not to permanently disable this item (see bug 409155)
    1.42 +      if (elt.hasAttribute("wasDisabled"))
    1.43 +        return;
    1.44 +      if (elt.getAttribute("disabled") == "true") {
    1.45 +        elt.setAttribute("wasDisabled", "true");
    1.46 +      } else {
    1.47 +        elt.setAttribute("wasDisabled", "false");
    1.48 +        elt.setAttribute("disabled", "true");
    1.49 +      }
    1.50 +    });
    1.51 +  },
    1.52 +
    1.53 +  _restoreCommandsState: function SU__restoreCommandsState() {
    1.54 +    this._blockedCommands.forEach(function (elt) {
    1.55 +      if (elt.getAttribute("wasDisabled") != "true")
    1.56 +        elt.removeAttribute("disabled");
    1.57 +      elt.removeAttribute("wasDisabled");
    1.58 +    });
    1.59 +  },
    1.60 +
    1.61 +  // nsIDOMEventListener
    1.62 +  handleEvent: function SU_handleEvent(aEvent) {
    1.63 +    switch (aEvent.type) {
    1.64 +      case "popuphidden":
    1.65 +        if (aEvent.originalTarget == this.panel) {
    1.66 +          if (!this._element("editBookmarkPanelContent").hidden)
    1.67 +            this.quitEditMode();
    1.68 +
    1.69 +          if (this._anchorToolbarButton) {
    1.70 +            this._anchorToolbarButton.removeAttribute("open");
    1.71 +            this._anchorToolbarButton = null;
    1.72 +          }
    1.73 +          this._restoreCommandsState();
    1.74 +          this._itemId = -1;
    1.75 +          if (this._batching) {
    1.76 +            PlacesUtils.transactionManager.endBatch(false);
    1.77 +            this._batching = false;
    1.78 +          }
    1.79 +
    1.80 +          switch (this._actionOnHide) {
    1.81 +            case "cancel": {
    1.82 +              PlacesUtils.transactionManager.undoTransaction();
    1.83 +              break;
    1.84 +            }
    1.85 +            case "remove": {
    1.86 +              // Remove all bookmarks for the bookmark's url, this also removes
    1.87 +              // the tags for the url.
    1.88 +              PlacesUtils.transactionManager.beginBatch(null);
    1.89 +              let itemIds = PlacesUtils.getBookmarksForURI(this._uriForRemoval);
    1.90 +              for (let i = 0; i < itemIds.length; i++) {
    1.91 +                let txn = new PlacesRemoveItemTransaction(itemIds[i]);
    1.92 +                PlacesUtils.transactionManager.doTransaction(txn);
    1.93 +              }
    1.94 +              PlacesUtils.transactionManager.endBatch(false);
    1.95 +              break;
    1.96 +            }
    1.97 +          }
    1.98 +          this._actionOnHide = "";
    1.99 +        }
   1.100 +        break;
   1.101 +      case "keypress":
   1.102 +        if (aEvent.defaultPrevented) {
   1.103 +          // The event has already been consumed inside of the panel.
   1.104 +          break;
   1.105 +        }
   1.106 +        switch (aEvent.keyCode) {
   1.107 +          case KeyEvent.DOM_VK_ESCAPE:
   1.108 +            if (!this._element("editBookmarkPanelContent").hidden)
   1.109 +              this.cancelButtonOnCommand();
   1.110 +            break;
   1.111 +          case KeyEvent.DOM_VK_RETURN:
   1.112 +            if (aEvent.target.classList.contains("expander-up") ||
   1.113 +                aEvent.target.classList.contains("expander-down") ||
   1.114 +                aEvent.target.id == "editBMPanel_newFolderButton")  {
   1.115 +              //XXX Why is this necessary? The defaultPrevented check should
   1.116 +              //    be enough.
   1.117 +              break;
   1.118 +            }
   1.119 +            this.panel.hidePopup();
   1.120 +            break;
   1.121 +        }
   1.122 +        break;
   1.123 +    }
   1.124 +  },
   1.125 +
   1.126 +  _overlayLoaded: false,
   1.127 +  _overlayLoading: false,
   1.128 +  showEditBookmarkPopup:
   1.129 +  function SU_showEditBookmarkPopup(aItemId, aAnchorElement, aPosition) {
   1.130 +    // Performance: load the overlay the first time the panel is opened
   1.131 +    // (see bug 392443).
   1.132 +    if (this._overlayLoading)
   1.133 +      return;
   1.134 +
   1.135 +    if (this._overlayLoaded) {
   1.136 +      this._doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition);
   1.137 +      return;
   1.138 +    }
   1.139 +
   1.140 +    this._overlayLoading = true;
   1.141 +    document.loadOverlay(
   1.142 +      "chrome://browser/content/places/editBookmarkOverlay.xul",
   1.143 +      (function (aSubject, aTopic, aData) {
   1.144 +        // Move the header (star, title, button) into the grid,
   1.145 +        // so that it aligns nicely with the other items (bug 484022).
   1.146 +        let header = this._element("editBookmarkPanelHeader");
   1.147 +        let rows = this._element("editBookmarkPanelGrid").lastChild;
   1.148 +        rows.insertBefore(header, rows.firstChild);
   1.149 +        header.hidden = false;
   1.150 +
   1.151 +        this._overlayLoading = false;
   1.152 +        this._overlayLoaded = true;
   1.153 +        this._doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition);
   1.154 +      }).bind(this)
   1.155 +    );
   1.156 +  },
   1.157 +
   1.158 +  _doShowEditBookmarkPanel:
   1.159 +  function SU__doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition) {
   1.160 +    if (this.panel.state != "closed")
   1.161 +      return;
   1.162 +
   1.163 +    this._blockCommands(); // un-done in the popuphiding handler
   1.164 +
   1.165 +    // Set panel title:
   1.166 +    // if we are batching, i.e. the bookmark has been added now,
   1.167 +    // then show Page Bookmarked, else if the bookmark did already exist,
   1.168 +    // we are about editing it, then use Edit This Bookmark.
   1.169 +    this._element("editBookmarkPanelTitle").value =
   1.170 +      this._batching ?
   1.171 +        gNavigatorBundle.getString("editBookmarkPanel.pageBookmarkedTitle") :
   1.172 +        gNavigatorBundle.getString("editBookmarkPanel.editBookmarkTitle");
   1.173 +
   1.174 +    // No description; show the Done, Cancel;
   1.175 +    this._element("editBookmarkPanelDescription").textContent = "";
   1.176 +    this._element("editBookmarkPanelBottomButtons").hidden = false;
   1.177 +    this._element("editBookmarkPanelContent").hidden = false;
   1.178 +
   1.179 +    // The remove button is shown only if we're not already batching, i.e.
   1.180 +    // if the cancel button/ESC does not remove the bookmark.
   1.181 +    this._element("editBookmarkPanelRemoveButton").hidden = this._batching;
   1.182 +
   1.183 +    // The label of the remove button differs if the URI is bookmarked
   1.184 +    // multiple times.
   1.185 +    var bookmarks = PlacesUtils.getBookmarksForURI(gBrowser.currentURI);
   1.186 +    var forms = gNavigatorBundle.getString("editBookmark.removeBookmarks.label");
   1.187 +    var label = PluralForm.get(bookmarks.length, forms).replace("#1", bookmarks.length);
   1.188 +    this._element("editBookmarkPanelRemoveButton").label = label;
   1.189 +
   1.190 +    // unset the unstarred state, if set
   1.191 +    this._element("editBookmarkPanelStarIcon").removeAttribute("unstarred");
   1.192 +
   1.193 +    this._itemId = aItemId !== undefined ? aItemId : this._itemId;
   1.194 +    this.beginBatch();
   1.195 +
   1.196 +    if (aAnchorElement) {
   1.197 +      // Set the open=true attribute if the anchor is a
   1.198 +      // descendent of a toolbarbutton.
   1.199 +      let parent = aAnchorElement.parentNode;
   1.200 +      while (parent) {
   1.201 +        if (parent.localName == "toolbarbutton") {
   1.202 +          break;
   1.203 +        }
   1.204 +        parent = parent.parentNode;
   1.205 +      }
   1.206 +      if (parent) {
   1.207 +        this._anchorToolbarButton = parent;
   1.208 +        parent.setAttribute("open", "true");
   1.209 +      }
   1.210 +    }
   1.211 +    this.panel.openPopup(aAnchorElement, aPosition);
   1.212 +
   1.213 +    gEditItemOverlay.initPanel(this._itemId,
   1.214 +                               { hiddenRows: ["description", "location",
   1.215 +                                              "loadInSidebar", "keyword"] });
   1.216 +  },
   1.217 +
   1.218 +  panelShown:
   1.219 +  function SU_panelShown(aEvent) {
   1.220 +    if (aEvent.target == this.panel) {
   1.221 +      if (!this._element("editBookmarkPanelContent").hidden) {
   1.222 +        let fieldToFocus = "editBMPanel_" +
   1.223 +          gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField");
   1.224 +        var elt = this._element(fieldToFocus);
   1.225 +        elt.focus();
   1.226 +        elt.select();
   1.227 +      }
   1.228 +      else {
   1.229 +        // Note this isn't actually used anymore, we should remove this
   1.230 +        // once we decide not to bring back the page bookmarked notification
   1.231 +        this.panel.focus();
   1.232 +      }
   1.233 +    }
   1.234 +  },
   1.235 +
   1.236 +  quitEditMode: function SU_quitEditMode() {
   1.237 +    this._element("editBookmarkPanelContent").hidden = true;
   1.238 +    this._element("editBookmarkPanelBottomButtons").hidden = true;
   1.239 +    gEditItemOverlay.uninitPanel(true);
   1.240 +  },
   1.241 +
   1.242 +  cancelButtonOnCommand: function SU_cancelButtonOnCommand() {
   1.243 +    this._actionOnHide = "cancel";
   1.244 +    this.panel.hidePopup();
   1.245 +  },
   1.246 +
   1.247 +  removeBookmarkButtonCommand: function SU_removeBookmarkButtonCommand() {
   1.248 +    this._uriForRemoval = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
   1.249 +    this._actionOnHide = "remove";
   1.250 +    this.panel.hidePopup();
   1.251 +  },
   1.252 +
   1.253 +  beginBatch: function SU_beginBatch() {
   1.254 +    if (!this._batching) {
   1.255 +      PlacesUtils.transactionManager.beginBatch(null);
   1.256 +      this._batching = true;
   1.257 +    }
   1.258 +  }
   1.259 +}
   1.260 +
   1.261 +////////////////////////////////////////////////////////////////////////////////
   1.262 +//// PlacesCommandHook
   1.263 +
   1.264 +var PlacesCommandHook = {
   1.265 +  /**
   1.266 +   * Adds a bookmark to the page loaded in the given browser.
   1.267 +   *
   1.268 +   * @param aBrowser
   1.269 +   *        a <browser> element.
   1.270 +   * @param [optional] aParent
   1.271 +   *        The folder in which to create a new bookmark if the page loaded in
   1.272 +   *        aBrowser isn't bookmarked yet, defaults to the unfiled root.
   1.273 +   * @param [optional] aShowEditUI
   1.274 +   *        whether or not to show the edit-bookmark UI for the bookmark item
   1.275 +   */  
   1.276 +  bookmarkPage: function PCH_bookmarkPage(aBrowser, aParent, aShowEditUI) {
   1.277 +    var uri = aBrowser.currentURI;
   1.278 +    var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
   1.279 +    if (itemId == -1) {
   1.280 +      // Copied over from addBookmarkForBrowser:
   1.281 +      // Bug 52536: We obtain the URL and title from the nsIWebNavigation
   1.282 +      // associated with a <browser/> rather than from a DOMWindow.
   1.283 +      // This is because when a full page plugin is loaded, there is
   1.284 +      // no DOMWindow (?) but information about the loaded document
   1.285 +      // may still be obtained from the webNavigation.
   1.286 +      var webNav = aBrowser.webNavigation;
   1.287 +      var url = webNav.currentURI;
   1.288 +      var title;
   1.289 +      var description;
   1.290 +      var charset;
   1.291 +      try {
   1.292 +        let isErrorPage = /^about:(neterror|certerror|blocked)/
   1.293 +                          .test(webNav.document.documentURI);
   1.294 +        title = isErrorPage ? PlacesUtils.history.getPageTitle(url)
   1.295 +                            : webNav.document.title;
   1.296 +        title = title || url.spec;
   1.297 +        description = PlacesUIUtils.getDescriptionFromDocument(webNav.document);
   1.298 +        charset = webNav.document.characterSet;
   1.299 +      }
   1.300 +      catch (e) { }
   1.301 +
   1.302 +      if (aShowEditUI) {
   1.303 +        // If we bookmark the page here (i.e. page was not "starred" already)
   1.304 +        // but open right into the "edit" state, start batching here, so
   1.305 +        // "Cancel" in that state removes the bookmark.
   1.306 +        StarUI.beginBatch();
   1.307 +      }
   1.308 +
   1.309 +      var parent = aParent != undefined ?
   1.310 +                   aParent : PlacesUtils.unfiledBookmarksFolderId;
   1.311 +      var descAnno = { name: PlacesUIUtils.DESCRIPTION_ANNO, value: description };
   1.312 +      var txn = new PlacesCreateBookmarkTransaction(uri, parent, 
   1.313 +                                                    PlacesUtils.bookmarks.DEFAULT_INDEX,
   1.314 +                                                    title, null, [descAnno]);
   1.315 +      PlacesUtils.transactionManager.doTransaction(txn);
   1.316 +      itemId = txn.item.id;
   1.317 +      // Set the character-set
   1.318 +      if (charset && !PrivateBrowsingUtils.isWindowPrivate(aBrowser.contentWindow))
   1.319 +        PlacesUtils.setCharsetForURI(uri, charset);
   1.320 +    }
   1.321 +
   1.322 +    // Revert the contents of the location bar
   1.323 +    if (gURLBar)
   1.324 +      gURLBar.handleRevert();
   1.325 +
   1.326 +    // If it was not requested to open directly in "edit" mode, we are done.
   1.327 +    if (!aShowEditUI)
   1.328 +      return;
   1.329 +
   1.330 +    // Try to dock the panel to:
   1.331 +    // 1. the bookmarks menu button
   1.332 +    // 2. the page-proxy-favicon
   1.333 +    // 3. the content area
   1.334 +    if (BookmarkingUI.anchor) {
   1.335 +      StarUI.showEditBookmarkPopup(itemId, BookmarkingUI.anchor,
   1.336 +                                   "bottomcenter topright");
   1.337 +      return;
   1.338 +    }
   1.339 +
   1.340 +    let pageProxyFavicon = document.getElementById("page-proxy-favicon");
   1.341 +    if (isElementVisible(pageProxyFavicon)) {
   1.342 +      StarUI.showEditBookmarkPopup(itemId, pageProxyFavicon,
   1.343 +                                   "bottomcenter topright");
   1.344 +    } else {
   1.345 +      StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap");
   1.346 +    }
   1.347 +  },
   1.348 +
   1.349 +  /**
   1.350 +   * Adds a bookmark to the page loaded in the current tab. 
   1.351 +   */
   1.352 +  bookmarkCurrentPage: function PCH_bookmarkCurrentPage(aShowEditUI, aParent) {
   1.353 +    this.bookmarkPage(gBrowser.selectedBrowser, aParent, aShowEditUI);
   1.354 +  },
   1.355 +
   1.356 +  /**
   1.357 +   * Adds a bookmark to the page targeted by a link.
   1.358 +   * @param aParent
   1.359 +   *        The folder in which to create a new bookmark if aURL isn't
   1.360 +   *        bookmarked.
   1.361 +   * @param aURL (string)
   1.362 +   *        the address of the link target
   1.363 +   * @param aTitle
   1.364 +   *        The link text
   1.365 +   */
   1.366 +  bookmarkLink: function PCH_bookmarkLink(aParent, aURL, aTitle) {
   1.367 +    var linkURI = makeURI(aURL);
   1.368 +    var itemId = PlacesUtils.getMostRecentBookmarkForURI(linkURI);
   1.369 +    if (itemId == -1) {
   1.370 +      PlacesUIUtils.showBookmarkDialog({ action: "add"
   1.371 +                                       , type: "bookmark"
   1.372 +                                       , uri: linkURI
   1.373 +                                       , title: aTitle
   1.374 +                                       , hiddenRows: [ "description"
   1.375 +                                                     , "location"
   1.376 +                                                     , "loadInSidebar"
   1.377 +                                                     , "keyword" ]
   1.378 +                                       }, window);
   1.379 +    }
   1.380 +    else {
   1.381 +      PlacesUIUtils.showBookmarkDialog({ action: "edit"
   1.382 +                                       , type: "bookmark"
   1.383 +                                       , itemId: itemId
   1.384 +                                       }, window);
   1.385 +    }
   1.386 +  },
   1.387 +
   1.388 +  /**
   1.389 +   * List of nsIURI objects characterizing the tabs currently open in the
   1.390 +   * browser, modulo pinned tabs.  The URIs will be in the order in which their
   1.391 +   * corresponding tabs appeared and duplicates are discarded.
   1.392 +   */
   1.393 +  get uniqueCurrentPages() {
   1.394 +    let uniquePages = {};
   1.395 +    let URIs = [];
   1.396 +    gBrowser.visibleTabs.forEach(function (tab) {
   1.397 +      let spec = tab.linkedBrowser.currentURI.spec;
   1.398 +      if (!tab.pinned && !(spec in uniquePages)) {
   1.399 +        uniquePages[spec] = null;
   1.400 +        URIs.push(tab.linkedBrowser.currentURI);
   1.401 +      }
   1.402 +    });
   1.403 +    return URIs;
   1.404 +  },
   1.405 +
   1.406 +  /**
   1.407 +   * Adds a folder with bookmarks to all of the currently open tabs in this 
   1.408 +   * window.
   1.409 +   */
   1.410 +  bookmarkCurrentPages: function PCH_bookmarkCurrentPages() {
   1.411 +    let pages = this.uniqueCurrentPages;
   1.412 +    if (pages.length > 1) {
   1.413 +    PlacesUIUtils.showBookmarkDialog({ action: "add"
   1.414 +                                     , type: "folder"
   1.415 +                                     , URIList: pages
   1.416 +                                     , hiddenRows: [ "description" ]
   1.417 +                                     }, window);
   1.418 +    }
   1.419 +  },
   1.420 +
   1.421 +  /**
   1.422 +   * Updates disabled state for the "Bookmark All Tabs" command.
   1.423 +   */
   1.424 +  updateBookmarkAllTabsCommand:
   1.425 +  function PCH_updateBookmarkAllTabsCommand() {
   1.426 +    // There's nothing to do in non-browser windows.
   1.427 +    if (window.location.href != getBrowserURL())
   1.428 +      return;
   1.429 +
   1.430 +    // Disable "Bookmark All Tabs" if there are less than two
   1.431 +    // "unique current pages".
   1.432 +    goSetCommandEnabled("Browser:BookmarkAllTabs",
   1.433 +                        this.uniqueCurrentPages.length >= 2);
   1.434 +  },
   1.435 +
   1.436 +  /**
   1.437 +   * Adds a Live Bookmark to a feed associated with the current page. 
   1.438 +   * @param     url
   1.439 +   *            The nsIURI of the page the feed was attached to
   1.440 +   * @title     title
   1.441 +   *            The title of the feed. Optional.
   1.442 +   * @subtitle  subtitle
   1.443 +   *            A short description of the feed. Optional.
   1.444 +   */
   1.445 +  addLiveBookmark: function PCH_addLiveBookmark(url, feedTitle, feedSubtitle) {
   1.446 +    var feedURI = makeURI(url);
   1.447 +    
   1.448 +    var doc = gBrowser.contentDocument;
   1.449 +    var title = (arguments.length > 1) ? feedTitle : doc.title;
   1.450 + 
   1.451 +    var description;
   1.452 +    if (arguments.length > 2)
   1.453 +      description = feedSubtitle;
   1.454 +    else
   1.455 +      description = PlacesUIUtils.getDescriptionFromDocument(doc);
   1.456 +
   1.457 +    var toolbarIP = new InsertionPoint(PlacesUtils.toolbarFolderId, -1);
   1.458 +    PlacesUIUtils.showBookmarkDialog({ action: "add"
   1.459 +                                     , type: "livemark"
   1.460 +                                     , feedURI: feedURI
   1.461 +                                     , siteURI: gBrowser.currentURI
   1.462 +                                     , title: title
   1.463 +                                     , description: description
   1.464 +                                     , defaultInsertionPoint: toolbarIP
   1.465 +                                     , hiddenRows: [ "feedLocation"
   1.466 +                                                   , "siteLocation"
   1.467 +                                                   , "description" ]
   1.468 +                                     }, window);
   1.469 +  },
   1.470 +
   1.471 +  /**
   1.472 +   * Opens the Places Organizer. 
   1.473 +   * @param   aLeftPaneRoot
   1.474 +   *          The query to select in the organizer window - options
   1.475 +   *          are: History, AllBookmarks, BookmarksMenu, BookmarksToolbar,
   1.476 +   *          UnfiledBookmarks, Tags and Downloads.
   1.477 +   */
   1.478 +  showPlacesOrganizer: function PCH_showPlacesOrganizer(aLeftPaneRoot) {
   1.479 +    var organizer = Services.wm.getMostRecentWindow("Places:Organizer");
   1.480 +    if (!organizer) {
   1.481 +      // No currently open places window, so open one with the specified mode.
   1.482 +      openDialog("chrome://browser/content/places/places.xul", 
   1.483 +                 "", "chrome,toolbar=yes,dialog=no,resizable", aLeftPaneRoot);
   1.484 +    }
   1.485 +    else {
   1.486 +      organizer.PlacesOrganizer.selectLeftPaneContainerByHierarchy(aLeftPaneRoot);
   1.487 +      organizer.focus();
   1.488 +    }
   1.489 +  }
   1.490 +};
   1.491 +
   1.492 +////////////////////////////////////////////////////////////////////////////////
   1.493 +//// HistoryMenu
   1.494 +
   1.495 +XPCOMUtils.defineLazyModuleGetter(this, "RecentlyClosedTabsAndWindowsMenuUtils",
   1.496 +  "resource:///modules/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm");
   1.497 +
   1.498 +// View for the history menu.
   1.499 +function HistoryMenu(aPopupShowingEvent) {
   1.500 +  // Workaround for Bug 610187.  The sidebar does not include all the Places
   1.501 +  // views definitions, and we don't need them there.
   1.502 +  // Defining the prototype inheritance in the prototype itself would cause
   1.503 +  // browser.js to halt on "PlacesMenu is not defined" error.
   1.504 +  this.__proto__.__proto__ = PlacesMenu.prototype;
   1.505 +  PlacesMenu.call(this, aPopupShowingEvent,
   1.506 +                  "place:sort=4&maxResults=15");
   1.507 +}
   1.508 +
   1.509 +HistoryMenu.prototype = {
   1.510 +  toggleRecentlyClosedTabs: function HM_toggleRecentlyClosedTabs() {
   1.511 +    // enable/disable the Recently Closed Tabs sub menu
   1.512 +    var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedTabsMenu")[0];
   1.513 +
   1.514 +    // no restorable tabs, so disable menu
   1.515 +    if (SessionStore.getClosedTabCount(window) == 0)
   1.516 +      undoMenu.setAttribute("disabled", true);
   1.517 +    else
   1.518 +      undoMenu.removeAttribute("disabled");
   1.519 +  },
   1.520 +
   1.521 +  /**
   1.522 +   * Populate when the history menu is opened
   1.523 +   */
   1.524 +  populateUndoSubmenu: function PHM_populateUndoSubmenu() {
   1.525 +    var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedTabsMenu")[0];
   1.526 +    var undoPopup = undoMenu.firstChild;
   1.527 +
   1.528 +    // remove existing menu items
   1.529 +    while (undoPopup.hasChildNodes())
   1.530 +      undoPopup.removeChild(undoPopup.firstChild);
   1.531 +
   1.532 +    // no restorable tabs, so make sure menu is disabled, and return
   1.533 +    if (SessionStore.getClosedTabCount(window) == 0) {
   1.534 +      undoMenu.setAttribute("disabled", true);
   1.535 +      return;
   1.536 +    }
   1.537 +
   1.538 +    // enable menu
   1.539 +    undoMenu.removeAttribute("disabled");
   1.540 +
   1.541 +    // populate menu
   1.542 +    let tabsFragment = RecentlyClosedTabsAndWindowsMenuUtils.getTabsFragment(window, "menuitem");
   1.543 +    undoPopup.appendChild(tabsFragment);
   1.544 +  },
   1.545 +
   1.546 +  toggleRecentlyClosedWindows: function PHM_toggleRecentlyClosedWindows() {
   1.547 +    // enable/disable the Recently Closed Windows sub menu
   1.548 +    var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedWindowsMenu")[0];
   1.549 +
   1.550 +    // no restorable windows, so disable menu
   1.551 +    if (SessionStore.getClosedWindowCount() == 0)
   1.552 +      undoMenu.setAttribute("disabled", true);
   1.553 +    else
   1.554 +      undoMenu.removeAttribute("disabled");
   1.555 +  },
   1.556 +
   1.557 +  /**
   1.558 +   * Populate when the history menu is opened
   1.559 +   */
   1.560 +  populateUndoWindowSubmenu: function PHM_populateUndoWindowSubmenu() {
   1.561 +    let undoMenu = this._rootElt.getElementsByClassName("recentlyClosedWindowsMenu")[0];
   1.562 +    let undoPopup = undoMenu.firstChild;
   1.563 +    let menuLabelString = gNavigatorBundle.getString("menuUndoCloseWindowLabel");
   1.564 +    let menuLabelStringSingleTab =
   1.565 +      gNavigatorBundle.getString("menuUndoCloseWindowSingleTabLabel");
   1.566 +
   1.567 +    // remove existing menu items
   1.568 +    while (undoPopup.hasChildNodes())
   1.569 +      undoPopup.removeChild(undoPopup.firstChild);
   1.570 +
   1.571 +    // no restorable windows, so make sure menu is disabled, and return
   1.572 +    if (SessionStore.getClosedWindowCount() == 0) {
   1.573 +      undoMenu.setAttribute("disabled", true);
   1.574 +      return;
   1.575 +    }
   1.576 +
   1.577 +    // enable menu
   1.578 +    undoMenu.removeAttribute("disabled");
   1.579 +
   1.580 +    // populate menu
   1.581 +    let windowsFragment = RecentlyClosedTabsAndWindowsMenuUtils.getWindowsFragment(window, "menuitem");
   1.582 +    undoPopup.appendChild(windowsFragment);
   1.583 +  },
   1.584 +
   1.585 +  toggleTabsFromOtherComputers: function PHM_toggleTabsFromOtherComputers() {
   1.586 +    // This is a no-op if MOZ_SERVICES_SYNC isn't defined
   1.587 +#ifdef MOZ_SERVICES_SYNC
   1.588 +    // Enable/disable the Tabs From Other Computers menu. Some of the menus handled
   1.589 +    // by HistoryMenu do not have this menuitem.
   1.590 +    let menuitem = this._rootElt.getElementsByClassName("syncTabsMenuItem")[0];
   1.591 +    if (!menuitem)
   1.592 +      return;
   1.593 +
   1.594 +    if (!PlacesUIUtils.shouldShowTabsFromOtherComputersMenuitem()) {
   1.595 +      menuitem.setAttribute("hidden", true);
   1.596 +      return;
   1.597 +    }
   1.598 +
   1.599 +    let enabled = PlacesUIUtils.shouldEnableTabsFromOtherComputersMenuitem();
   1.600 +    menuitem.setAttribute("disabled", !enabled);
   1.601 +    menuitem.setAttribute("hidden", false);
   1.602 +#endif
   1.603 +  },
   1.604 +
   1.605 +  _onPopupShowing: function HM__onPopupShowing(aEvent) {
   1.606 +    PlacesMenu.prototype._onPopupShowing.apply(this, arguments);
   1.607 +
   1.608 +    // Don't handle events for submenus.
   1.609 +    if (aEvent.target != aEvent.currentTarget)
   1.610 +      return;
   1.611 +
   1.612 +    this.toggleRecentlyClosedTabs();
   1.613 +    this.toggleRecentlyClosedWindows();
   1.614 +    this.toggleTabsFromOtherComputers();
   1.615 +  },
   1.616 +
   1.617 +  _onCommand: function HM__onCommand(aEvent) {
   1.618 +    let placesNode = aEvent.target._placesNode;
   1.619 +    if (placesNode) {
   1.620 +      if (!PrivateBrowsingUtils.isWindowPrivate(window))
   1.621 +        PlacesUIUtils.markPageAsTyped(placesNode.uri);
   1.622 +      openUILink(placesNode.uri, aEvent, { ignoreAlt: true });
   1.623 +    }
   1.624 +  }
   1.625 +};
   1.626 +
   1.627 +////////////////////////////////////////////////////////////////////////////////
   1.628 +//// BookmarksEventHandler
   1.629 +
   1.630 +/**
   1.631 + * Functions for handling events in the Bookmarks Toolbar and menu.
   1.632 + */
   1.633 +var BookmarksEventHandler = {
   1.634 +  /**
   1.635 +   * Handler for click event for an item in the bookmarks toolbar or menu.
   1.636 +   * Menus and submenus from the folder buttons bubble up to this handler.
   1.637 +   * Left-click is handled in the onCommand function.
   1.638 +   * When items are middle-clicked (or clicked with modifier), open in tabs.
   1.639 +   * If the click came through a menu, close the menu.
   1.640 +   * @param aEvent
   1.641 +   *        DOMEvent for the click
   1.642 +   * @param aView
   1.643 +   *        The places view which aEvent should be associated with.
   1.644 +   */
   1.645 +  onClick: function BEH_onClick(aEvent, aView) {
   1.646 +    // Only handle middle-click or left-click with modifiers.
   1.647 +#ifdef XP_MACOSX
   1.648 +    var modifKey = aEvent.metaKey || aEvent.shiftKey;
   1.649 +#else
   1.650 +    var modifKey = aEvent.ctrlKey || aEvent.shiftKey;
   1.651 +#endif
   1.652 +    if (aEvent.button == 2 || (aEvent.button == 0 && !modifKey))
   1.653 +      return;
   1.654 +
   1.655 +    var target = aEvent.originalTarget;
   1.656 +    // If this event bubbled up from a menu or menuitem, close the menus.
   1.657 +    // Do this before opening tabs, to avoid hiding the open tabs confirm-dialog.
   1.658 +    if (target.localName == "menu" || target.localName == "menuitem") {
   1.659 +      for (node = target.parentNode; node; node = node.parentNode) {
   1.660 +        if (node.localName == "menupopup")
   1.661 +          node.hidePopup();
   1.662 +        else if (node.localName != "menu" &&
   1.663 +                 node.localName != "splitmenu" &&
   1.664 +                 node.localName != "hbox" &&
   1.665 +                 node.localName != "vbox" )
   1.666 +          break;
   1.667 +      }
   1.668 +    }
   1.669 +
   1.670 +    if (target._placesNode && PlacesUtils.nodeIsContainer(target._placesNode)) {
   1.671 +      // Don't open the root folder in tabs when the empty area on the toolbar
   1.672 +      // is middle-clicked or when a non-bookmark item except for Open in Tabs)
   1.673 +      // in a bookmarks menupopup is middle-clicked.
   1.674 +      if (target.localName == "menu" || target.localName == "toolbarbutton")
   1.675 +        PlacesUIUtils.openContainerNodeInTabs(target._placesNode, aEvent, aView);
   1.676 +    }
   1.677 +    else if (aEvent.button == 1) {
   1.678 +      // left-clicks with modifier are already served by onCommand
   1.679 +      this.onCommand(aEvent, aView);
   1.680 +    }
   1.681 +  },
   1.682 +
   1.683 +  /**
   1.684 +   * Handler for command event for an item in the bookmarks toolbar.
   1.685 +   * Menus and submenus from the folder buttons bubble up to this handler.
   1.686 +   * Opens the item.
   1.687 +   * @param aEvent 
   1.688 +   *        DOMEvent for the command
   1.689 +   * @param aView
   1.690 +   *        The places view which aEvent should be associated with.
   1.691 +   */
   1.692 +  onCommand: function BEH_onCommand(aEvent, aView) {
   1.693 +    var target = aEvent.originalTarget;
   1.694 +    if (target._placesNode)
   1.695 +      PlacesUIUtils.openNodeWithEvent(target._placesNode, aEvent, aView);
   1.696 +  },
   1.697 +
   1.698 +  fillInBHTooltip: function BEH_fillInBHTooltip(aDocument, aEvent) {
   1.699 +    var node;
   1.700 +    var cropped = false;
   1.701 +    var targetURI;
   1.702 +
   1.703 +    if (aDocument.tooltipNode.localName == "treechildren") {
   1.704 +      var tree = aDocument.tooltipNode.parentNode;
   1.705 +      var row = {}, column = {};
   1.706 +      var tbo = tree.treeBoxObject;
   1.707 +      tbo.getCellAt(aEvent.clientX, aEvent.clientY, row, column, {});
   1.708 +      if (row.value == -1)
   1.709 +        return false;
   1.710 +      node = tree.view.nodeForTreeIndex(row.value);
   1.711 +      cropped = tbo.isCellCropped(row.value, column.value);
   1.712 +    }
   1.713 +    else {
   1.714 +      // Check whether the tooltipNode is a Places node.
   1.715 +      // In such a case use it, otherwise check for targetURI attribute.
   1.716 +      var tooltipNode = aDocument.tooltipNode;
   1.717 +      if (tooltipNode._placesNode)
   1.718 +        node = tooltipNode._placesNode;
   1.719 +      else {
   1.720 +        // This is a static non-Places node.
   1.721 +        targetURI = tooltipNode.getAttribute("targetURI");
   1.722 +      }
   1.723 +    }
   1.724 +
   1.725 +    if (!node && !targetURI)
   1.726 +      return false;
   1.727 +
   1.728 +    // Show node.label as tooltip's title for non-Places nodes.
   1.729 +    var title = node ? node.title : tooltipNode.label;
   1.730 +
   1.731 +    // Show URL only for Places URI-nodes or nodes with a targetURI attribute.
   1.732 +    var url;
   1.733 +    if (targetURI || PlacesUtils.nodeIsURI(node))
   1.734 +      url = targetURI || node.uri;
   1.735 +
   1.736 +    // Show tooltip for containers only if their title is cropped.
   1.737 +    if (!cropped && !url)
   1.738 +      return false;
   1.739 +
   1.740 +    var tooltipTitle = aDocument.getElementById("bhtTitleText");
   1.741 +    tooltipTitle.hidden = (!title || (title == url));
   1.742 +    if (!tooltipTitle.hidden)
   1.743 +      tooltipTitle.textContent = title;
   1.744 +
   1.745 +    var tooltipUrl = aDocument.getElementById("bhtUrlText");
   1.746 +    tooltipUrl.hidden = !url;
   1.747 +    if (!tooltipUrl.hidden)
   1.748 +      tooltipUrl.value = url;
   1.749 +
   1.750 +    // Show tooltip.
   1.751 +    return true;
   1.752 +  }
   1.753 +};
   1.754 +
   1.755 +////////////////////////////////////////////////////////////////////////////////
   1.756 +//// PlacesMenuDNDHandler
   1.757 +
   1.758 +// Handles special drag and drop functionality for Places menus that are not
   1.759 +// part of a Places view (e.g. the bookmarks menu in the menubar).
   1.760 +var PlacesMenuDNDHandler = {
   1.761 +  _springLoadDelayMs: 350,
   1.762 +  _closeDelayMs: 500,
   1.763 +  _loadTimer: null,
   1.764 +  _closeTimer: null,
   1.765 +  _closingTimerNode: null,
   1.766 +
   1.767 +  /**
   1.768 +   * Called when the user enters the <menu> element during a drag.
   1.769 +   * @param   event
   1.770 +   *          The DragEnter event that spawned the opening. 
   1.771 +   */
   1.772 +  onDragEnter: function PMDH_onDragEnter(event) {
   1.773 +    // Opening menus in a Places popup is handled by the view itself.
   1.774 +    if (!this._isStaticContainer(event.target))
   1.775 +      return;
   1.776 +
   1.777 +    // If we re-enter the same menu or anchor before the close timer runs out,
   1.778 +    // we should ensure that we do not close:
   1.779 +    if (this._closeTimer && this._closingTimerNode === event.currentTarget) {
   1.780 +      this._closeTimer.cancel();
   1.781 +      this._closingTimerNode = null;
   1.782 +      this._closeTimer = null;
   1.783 +    }
   1.784 +
   1.785 +    PlacesControllerDragHelper.currentDropTarget = event.target;
   1.786 +    let popup = event.target.lastChild;
   1.787 +    if (this._loadTimer || popup.state === "showing" || popup.state === "open")
   1.788 +      return;
   1.789 +
   1.790 +    this._loadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   1.791 +    this._loadTimer.initWithCallback(() => {
   1.792 +      this._loadTimer = null;
   1.793 +      popup.setAttribute("autoopened", "true");
   1.794 +      popup.showPopup(popup);
   1.795 +    }, this._springLoadDelayMs, Ci.nsITimer.TYPE_ONE_SHOT);
   1.796 +    event.preventDefault();
   1.797 +    event.stopPropagation();
   1.798 +  },
   1.799 +
   1.800 +  /**
   1.801 +   * Handles dragleave on the <menu> element.
   1.802 +   */
   1.803 +  onDragLeave: function PMDH_onDragLeave(event) {
   1.804 +    // Handle menu-button separate targets.
   1.805 +    if (event.relatedTarget === event.currentTarget ||
   1.806 +        (event.relatedTarget &&
   1.807 +         event.relatedTarget.parentNode === event.currentTarget))
   1.808 +      return;
   1.809 +
   1.810 +    // Closing menus in a Places popup is handled by the view itself.
   1.811 +    if (!this._isStaticContainer(event.target))
   1.812 +      return;
   1.813 +
   1.814 +    PlacesControllerDragHelper.currentDropTarget = null;
   1.815 +    let popup = event.target.lastChild;
   1.816 +
   1.817 +    if (this._loadTimer) {
   1.818 +      this._loadTimer.cancel();
   1.819 +      this._loadTimer = null;
   1.820 +    }
   1.821 +    this._closeTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   1.822 +    this._closingTimerNode = event.currentTarget;
   1.823 +    this._closeTimer.initWithCallback(function() {
   1.824 +      this._closeTimer = null;
   1.825 +      this._closingTimerNode = null;
   1.826 +      let node = PlacesControllerDragHelper.currentDropTarget;
   1.827 +      let inHierarchy = false;
   1.828 +      while (node && !inHierarchy) {
   1.829 +        inHierarchy = node == event.target;
   1.830 +        node = node.parentNode;
   1.831 +      }
   1.832 +      if (!inHierarchy && popup && popup.hasAttribute("autoopened")) {
   1.833 +        popup.removeAttribute("autoopened");
   1.834 +        popup.hidePopup();
   1.835 +      }
   1.836 +    }, this._closeDelayMs, Ci.nsITimer.TYPE_ONE_SHOT);
   1.837 +  },
   1.838 +
   1.839 +  /**
   1.840 +   * Determines if a XUL element represents a static container.
   1.841 +   * @returns true if the element is a container element (menu or 
   1.842 +   *`         menu-toolbarbutton), false otherwise.
   1.843 +   */
   1.844 +  _isStaticContainer: function PMDH__isContainer(node) {
   1.845 +    let isMenu = node.localName == "menu" ||
   1.846 +                 (node.localName == "toolbarbutton" &&
   1.847 +                  (node.getAttribute("type") == "menu" ||
   1.848 +                   node.getAttribute("type") == "menu-button"));
   1.849 +    let isStatic = !("_placesNode" in node) && node.lastChild &&
   1.850 +                   node.lastChild.hasAttribute("placespopup") &&
   1.851 +                   !node.parentNode.hasAttribute("placespopup");
   1.852 +    return isMenu && isStatic;
   1.853 +  },
   1.854 +
   1.855 +  /**
   1.856 +   * Called when the user drags over the <menu> element.
   1.857 +   * @param   event
   1.858 +   *          The DragOver event. 
   1.859 +   */
   1.860 +  onDragOver: function PMDH_onDragOver(event) {
   1.861 +    let ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
   1.862 +                                PlacesUtils.bookmarks.DEFAULT_INDEX,
   1.863 +                                Ci.nsITreeView.DROP_ON);
   1.864 +    if (ip && PlacesControllerDragHelper.canDrop(ip, event.dataTransfer))
   1.865 +      event.preventDefault();
   1.866 +
   1.867 +    event.stopPropagation();
   1.868 +  },
   1.869 +
   1.870 +  /**
   1.871 +   * Called when the user drops on the <menu> element.
   1.872 +   * @param   event
   1.873 +   *          The Drop event. 
   1.874 +   */
   1.875 +  onDrop: function PMDH_onDrop(event) {
   1.876 +    // Put the item at the end of bookmark menu.
   1.877 +    let ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
   1.878 +                                PlacesUtils.bookmarks.DEFAULT_INDEX,
   1.879 +                                Ci.nsITreeView.DROP_ON);
   1.880 +    PlacesControllerDragHelper.onDrop(ip, event.dataTransfer);
   1.881 +    PlacesControllerDragHelper.currentDropTarget = null;
   1.882 +    event.stopPropagation();
   1.883 +  }
   1.884 +};
   1.885 +
   1.886 +////////////////////////////////////////////////////////////////////////////////
   1.887 +//// PlacesToolbarHelper
   1.888 +
   1.889 +/**
   1.890 + * This object handles the initialization and uninitialization of the bookmarks
   1.891 + * toolbar.
   1.892 + */
   1.893 +let PlacesToolbarHelper = {
   1.894 +  _place: "place:folder=TOOLBAR",
   1.895 +
   1.896 +  get _viewElt() {
   1.897 +    return document.getElementById("PlacesToolbar");
   1.898 +  },
   1.899 +
   1.900 +  get _placeholder() {
   1.901 +    return document.getElementById("bookmarks-toolbar-placeholder");
   1.902 +  },
   1.903 +
   1.904 +  init: function PTH_init(forceToolbarOverflowCheck) {
   1.905 +    let viewElt = this._viewElt;
   1.906 +    if (!viewElt || viewElt._placesView)
   1.907 +      return;
   1.908 +
   1.909 +    // CustomizableUI.addListener is idempotent, so we can safely
   1.910 +    // call this multiple times.
   1.911 +    CustomizableUI.addListener(this);
   1.912 +
   1.913 +    // If the bookmarks toolbar item is:
   1.914 +    // - not in a toolbar, or;
   1.915 +    // - the toolbar is collapsed, or;
   1.916 +    // - the toolbar is hidden some other way:
   1.917 +    // don't initialize.  Also, there is no need to initialize the toolbar if
   1.918 +    // customizing, because that will happen when the customization is done.
   1.919 +    let toolbar = this._getParentToolbar(viewElt);
   1.920 +    if (!toolbar || toolbar.collapsed || this._isCustomizing ||
   1.921 +        getComputedStyle(toolbar, "").display == "none")
   1.922 +      return;
   1.923 +
   1.924 +    new PlacesToolbar(this._place);
   1.925 +    if (forceToolbarOverflowCheck) {
   1.926 +      viewElt._placesView.updateOverflowStatus();
   1.927 +    }
   1.928 +    this._setupPlaceholder();
   1.929 +  },
   1.930 +
   1.931 +  uninit: function PTH_uninit() {
   1.932 +    CustomizableUI.removeListener(this);
   1.933 +  },
   1.934 +
   1.935 +  customizeStart: function PTH_customizeStart() {
   1.936 +    try {
   1.937 +      let viewElt = this._viewElt;
   1.938 +      if (viewElt && viewElt._placesView)
   1.939 +        viewElt._placesView.uninit();
   1.940 +    } finally {
   1.941 +      this._isCustomizing = true;
   1.942 +    }
   1.943 +    this._shouldWrap = this._getShouldWrap();
   1.944 +  },
   1.945 +
   1.946 +  customizeChange: function PTH_customizeChange() {
   1.947 +    this._setupPlaceholder();
   1.948 +  },
   1.949 +
   1.950 +  _setupPlaceholder: function PTH_setupPlaceholder() {
   1.951 +    let placeholder = this._placeholder;
   1.952 +    if (!placeholder) {
   1.953 +      return;
   1.954 +    }
   1.955 +
   1.956 +    let shouldWrapNow = this._getShouldWrap();
   1.957 +    if (this._shouldWrap != shouldWrapNow) {
   1.958 +      if (shouldWrapNow) {
   1.959 +        placeholder.setAttribute("wrap", "true");
   1.960 +      } else {
   1.961 +        placeholder.removeAttribute("wrap");
   1.962 +      }
   1.963 +      this._shouldWrap = shouldWrapNow;
   1.964 +    }
   1.965 +  },
   1.966 +
   1.967 +  customizeDone: function PTH_customizeDone() {
   1.968 +    this._isCustomizing = false;
   1.969 +    this.init(true);
   1.970 +  },
   1.971 +
   1.972 +  _getShouldWrap: function PTH_getShouldWrap() {
   1.973 +    let placement = CustomizableUI.getPlacementOfWidget("personal-bookmarks");
   1.974 +    let area = placement && placement.area;
   1.975 +    let areaType = area && CustomizableUI.getAreaType(area);
   1.976 +    return !area || CustomizableUI.TYPE_MENU_PANEL == areaType;
   1.977 +  },
   1.978 +
   1.979 +  onPlaceholderCommand: function () {
   1.980 +    let widgetGroup = CustomizableUI.getWidget("personal-bookmarks");
   1.981 +    let widget = widgetGroup.forWindow(window);
   1.982 +    if (widget.overflowed ||
   1.983 +        widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
   1.984 +      PlacesCommandHook.showPlacesOrganizer("BookmarksToolbar");
   1.985 +    }
   1.986 +  },
   1.987 +
   1.988 +  _getParentToolbar: function(element) {
   1.989 +    while (element) {
   1.990 +      if (element.localName == "toolbar") {
   1.991 +        return element;
   1.992 +      }
   1.993 +      element = element.parentNode;
   1.994 +    }
   1.995 +    return null;
   1.996 +  },
   1.997 +
   1.998 +  onWidgetUnderflow: function(aNode, aContainer) {
   1.999 +    // The view gets broken by being removed and reinserted by the overflowable
  1.1000 +    // toolbar, so we have to force an uninit and reinit.
  1.1001 +    let win = aNode.ownerDocument.defaultView;
  1.1002 +    if (aNode.id == "personal-bookmarks" && win == window) {
  1.1003 +      this._resetView();
  1.1004 +    }
  1.1005 +  },
  1.1006 +
  1.1007 +  onWidgetAdded: function(aWidgetId, aArea, aPosition) {
  1.1008 +    if (aWidgetId == "personal-bookmarks" && !this._isCustomizing) {
  1.1009 +      // It's possible (with the "Add to Menu", "Add to Toolbar" context
  1.1010 +      // options) that the Places Toolbar Items have been moved without
  1.1011 +      // letting us prepare and handle it with with customizeStart and
  1.1012 +      // customizeDone. If that's the case, we need to reset the views
  1.1013 +      // since they're probably broken from the DOM reparenting.
  1.1014 +      this._resetView();
  1.1015 +    }
  1.1016 +  },
  1.1017 +
  1.1018 +  _resetView: function() {
  1.1019 +    if (this._viewElt) {
  1.1020 +      // It's possible that the placesView might not exist, and we need to
  1.1021 +      // do a full init. This could happen if the Bookmarks Toolbar Items are
  1.1022 +      // moved to the Menu Panel, and then to the toolbar with the "Add to Toolbar"
  1.1023 +      // context menu option, outside of customize mode.
  1.1024 +      if (this._viewElt._placesView) {
  1.1025 +        this._viewElt._placesView.uninit();
  1.1026 +      }
  1.1027 +      this.init(true);
  1.1028 +    }
  1.1029 +  },
  1.1030 +};
  1.1031 +
  1.1032 +////////////////////////////////////////////////////////////////////////////////
  1.1033 +//// BookmarkingUI
  1.1034 +
  1.1035 +/**
  1.1036 + * Handles the bookmarks menu-button in the toolbar.
  1.1037 + */
  1.1038 +
  1.1039 +let BookmarkingUI = {
  1.1040 +  BOOKMARK_BUTTON_ID: "bookmarks-menu-button",
  1.1041 +  get button() {
  1.1042 +    delete this.button;
  1.1043 +    let widgetGroup = CustomizableUI.getWidget(this.BOOKMARK_BUTTON_ID);
  1.1044 +    return this.button = widgetGroup.forWindow(window).node;
  1.1045 +  },
  1.1046 +
  1.1047 +  /* Can't make this a self-deleting getter because it's anonymous content
  1.1048 +   * and might lose/regain bindings at some point. */
  1.1049 +  get star() {
  1.1050 +    return document.getAnonymousElementByAttribute(this.button, "anonid",
  1.1051 +                                                   "button");
  1.1052 +  },
  1.1053 +
  1.1054 +  get anchor() {
  1.1055 +    if (!this._shouldUpdateStarState()) {
  1.1056 +      return null;
  1.1057 +    }
  1.1058 +    let widget = CustomizableUI.getWidget(this.BOOKMARK_BUTTON_ID)
  1.1059 +                               .forWindow(window);
  1.1060 +    if (widget.overflowed)
  1.1061 +      return widget.anchor;
  1.1062 +
  1.1063 +    let star = this.star;
  1.1064 +    return star ? document.getAnonymousElementByAttribute(star, "class",
  1.1065 +                                                          "toolbarbutton-icon")
  1.1066 +                : null;
  1.1067 +  },
  1.1068 +
  1.1069 +  get notifier() {
  1.1070 +    delete this.notifier;
  1.1071 +    return this.notifier = document.getElementById("bookmarked-notification-anchor");
  1.1072 +  },
  1.1073 +
  1.1074 +  get dropmarkerNotifier() {
  1.1075 +    delete this.dropmarkerNotifier;
  1.1076 +    return this.dropmarkerNotifier = document.getElementById("bookmarked-notification-dropmarker-anchor");
  1.1077 +  },
  1.1078 +
  1.1079 +  get broadcaster() {
  1.1080 +    delete this.broadcaster;
  1.1081 +    let broadcaster = document.getElementById("bookmarkThisPageBroadcaster");
  1.1082 +    return this.broadcaster = broadcaster;
  1.1083 +  },
  1.1084 +
  1.1085 +  STATUS_UPDATING: -1,
  1.1086 +  STATUS_UNSTARRED: 0,
  1.1087 +  STATUS_STARRED: 1,
  1.1088 +  get status() {
  1.1089 +    if (!this._shouldUpdateStarState()) {
  1.1090 +      return this.STATUS_UNSTARRED;
  1.1091 +    }
  1.1092 +    if (this._pendingStmt)
  1.1093 +      return this.STATUS_UPDATING;
  1.1094 +    return this.button.hasAttribute("starred") ? this.STATUS_STARRED
  1.1095 +                                               : this.STATUS_UNSTARRED;
  1.1096 +  },
  1.1097 +
  1.1098 +  get _starredTooltip()
  1.1099 +  {
  1.1100 +    delete this._starredTooltip;
  1.1101 +    return this._starredTooltip =
  1.1102 +      gNavigatorBundle.getString("starButtonOn.tooltip");
  1.1103 +  },
  1.1104 +
  1.1105 +  get _unstarredTooltip()
  1.1106 +  {
  1.1107 +    delete this._unstarredTooltip;
  1.1108 +    return this._unstarredTooltip =
  1.1109 +      gNavigatorBundle.getString("starButtonOff.tooltip");
  1.1110 +  },
  1.1111 +
  1.1112 +  /**
  1.1113 +   * The type of the area in which the button is currently located.
  1.1114 +   * When in the panel, we don't update the button's icon.
  1.1115 +   */
  1.1116 +  _currentAreaType: null,
  1.1117 +  _shouldUpdateStarState: function() {
  1.1118 +    return this._currentAreaType == CustomizableUI.TYPE_TOOLBAR;
  1.1119 +  },
  1.1120 +
  1.1121 +  /**
  1.1122 +   * The popup contents must be updated when the user customizes the UI, or
  1.1123 +   * changes the personal toolbar collapsed status.  In such a case, any needed
  1.1124 +   * change should be handled in the popupshowing helper, for performance
  1.1125 +   * reasons.
  1.1126 +   */
  1.1127 +  _popupNeedsUpdate: true,
  1.1128 +  onToolbarVisibilityChange: function BUI_onToolbarVisibilityChange() {
  1.1129 +    this._popupNeedsUpdate = true;
  1.1130 +  },
  1.1131 +
  1.1132 +  onPopupShowing: function BUI_onPopupShowing(event) {
  1.1133 +    // Don't handle events for submenus.
  1.1134 +    if (event.target != event.currentTarget)
  1.1135 +      return;
  1.1136 +
  1.1137 +    // Ideally this code would never be reached, but if you click the outer
  1.1138 +    // button's border, some cpp code for the menu button's so-called XBL binding
  1.1139 +    // decides to open the popup even though the dropmarker is invisible.
  1.1140 +    if (this._currentAreaType == CustomizableUI.TYPE_MENU_PANEL) {
  1.1141 +      this._showSubview();
  1.1142 +      event.preventDefault();
  1.1143 +      event.stopPropagation();
  1.1144 +      return;
  1.1145 +    }
  1.1146 +
  1.1147 +    let widget = CustomizableUI.getWidget(this.BOOKMARK_BUTTON_ID)
  1.1148 +                               .forWindow(window);
  1.1149 +    if (widget.overflowed) {
  1.1150 +      // Don't open a popup in the overflow popup, rather just open the Library.
  1.1151 +      event.preventDefault();
  1.1152 +      widget.node.removeAttribute("closemenu");
  1.1153 +      PlacesCommandHook.showPlacesOrganizer("BookmarksMenu");
  1.1154 +      return;
  1.1155 +    }
  1.1156 +
  1.1157 +    if (!this._popupNeedsUpdate)
  1.1158 +      return;
  1.1159 +    this._popupNeedsUpdate = false;
  1.1160 +
  1.1161 +    let popup = event.target;
  1.1162 +    let getPlacesAnonymousElement =
  1.1163 +      aAnonId => document.getAnonymousElementByAttribute(popup.parentNode,
  1.1164 +                                                         "placesanonid",
  1.1165 +                                                         aAnonId);
  1.1166 +
  1.1167 +    let viewToolbarMenuitem = getPlacesAnonymousElement("view-toolbar");
  1.1168 +    if (viewToolbarMenuitem) {
  1.1169 +      // Update View bookmarks toolbar checkbox menuitem.
  1.1170 +      viewToolbarMenuitem.classList.add("subviewbutton");
  1.1171 +      let personalToolbar = document.getElementById("PersonalToolbar");
  1.1172 +      viewToolbarMenuitem.setAttribute("checked", !personalToolbar.collapsed);
  1.1173 +    }
  1.1174 +  },
  1.1175 +
  1.1176 +  attachPlacesView: function(event, node) {
  1.1177 +    // If the view is already there, bail out early.
  1.1178 +    if (node.parentNode._placesView)
  1.1179 +      return;
  1.1180 +
  1.1181 +    new PlacesMenu(event, "place:folder=BOOKMARKS_MENU", {
  1.1182 +      extraClasses: {
  1.1183 +        entry: "subviewbutton",
  1.1184 +        footer: "panel-subview-footer"
  1.1185 +      },
  1.1186 +      insertionPoint: ".panel-subview-footer"
  1.1187 +    });
  1.1188 +  },
  1.1189 +
  1.1190 +  /**
  1.1191 +   * Handles star styling based on page proxy state changes.
  1.1192 +   */
  1.1193 +  onPageProxyStateChanged: function BUI_onPageProxyStateChanged(aState) {
  1.1194 +    if (!this._shouldUpdateStarState() || !this.star) {
  1.1195 +      return;
  1.1196 +    }
  1.1197 +
  1.1198 +    if (aState == "invalid") {
  1.1199 +      this.star.setAttribute("disabled", "true");
  1.1200 +      this.button.removeAttribute("starred");
  1.1201 +      this.button.setAttribute("buttontooltiptext", "");
  1.1202 +    }
  1.1203 +    else {
  1.1204 +      this.star.removeAttribute("disabled");
  1.1205 +      this._updateStar();
  1.1206 +    }
  1.1207 +    this._updateToolbarStyle();
  1.1208 +  },
  1.1209 +
  1.1210 +  _updateCustomizationState: function BUI__updateCustomizationState() {
  1.1211 +    let placement = CustomizableUI.getPlacementOfWidget(this.BOOKMARK_BUTTON_ID);
  1.1212 +    this._currentAreaType = placement && CustomizableUI.getAreaType(placement.area);
  1.1213 +  },
  1.1214 +
  1.1215 +  _updateToolbarStyle: function BUI__updateToolbarStyle() {
  1.1216 +    let onPersonalToolbar = false;
  1.1217 +    if (this._currentAreaType == CustomizableUI.TYPE_TOOLBAR) {
  1.1218 +      let personalToolbar = document.getElementById("PersonalToolbar");
  1.1219 +      onPersonalToolbar = this.button.parentNode == personalToolbar ||
  1.1220 +                          this.button.parentNode.parentNode == personalToolbar;
  1.1221 +    }
  1.1222 +
  1.1223 +    if (onPersonalToolbar)
  1.1224 +      this.button.classList.add("bookmark-item");
  1.1225 +    else
  1.1226 +      this.button.classList.remove("bookmark-item");
  1.1227 +  },
  1.1228 +
  1.1229 +  _uninitView: function BUI__uninitView() {
  1.1230 +    // When an element with a placesView attached is removed and re-inserted,
  1.1231 +    // XBL reapplies the binding causing any kind of issues and possible leaks,
  1.1232 +    // so kill current view and let popupshowing generate a new one.
  1.1233 +    if (this.button._placesView)
  1.1234 +      this.button._placesView.uninit();
  1.1235 +
  1.1236 +    // We have to do the same thing for the "special" views underneath the
  1.1237 +    // the bookmarks menu.
  1.1238 +    const kSpecialViewNodeIDs = ["BMB_bookmarksToolbar", "BMB_unsortedBookmarks"];
  1.1239 +    for (let viewNodeID of kSpecialViewNodeIDs) {
  1.1240 +      let elem = document.getElementById(viewNodeID);
  1.1241 +      if (elem && elem._placesView) {
  1.1242 +        elem._placesView.uninit();
  1.1243 +      }
  1.1244 +    }
  1.1245 +  },
  1.1246 +
  1.1247 +  onCustomizeStart: function BUI_customizeStart(aWindow) {
  1.1248 +    if (aWindow == window) {
  1.1249 +      this._uninitView();
  1.1250 +      this._isCustomizing = true;
  1.1251 +    }
  1.1252 +  },
  1.1253 +
  1.1254 +  onWidgetAdded: function BUI_widgetAdded(aWidgetId) {
  1.1255 +    if (aWidgetId == this.BOOKMARK_BUTTON_ID) {
  1.1256 +      this._onWidgetWasMoved();
  1.1257 +    }
  1.1258 +  },
  1.1259 +
  1.1260 +  onWidgetRemoved: function BUI_widgetRemoved(aWidgetId) {
  1.1261 +    if (aWidgetId == this.BOOKMARK_BUTTON_ID) {
  1.1262 +      this._onWidgetWasMoved();
  1.1263 +    }
  1.1264 +  },
  1.1265 +
  1.1266 +  onWidgetReset: function BUI_widgetReset(aNode, aContainer) {
  1.1267 +    if (aNode == this.button) {
  1.1268 +      this._onWidgetWasMoved();
  1.1269 +    }
  1.1270 +  },
  1.1271 +
  1.1272 +  onWidgetUndoMove: function BUI_undoWidgetUndoMove(aNode, aContainer) {
  1.1273 +    if (aNode == this.button) {
  1.1274 +      this._onWidgetWasMoved();
  1.1275 +    }
  1.1276 +  },
  1.1277 +
  1.1278 +  _onWidgetWasMoved: function BUI_widgetWasMoved() {
  1.1279 +    let usedToUpdateStarState = this._shouldUpdateStarState();
  1.1280 +    this._updateCustomizationState();
  1.1281 +    if (!usedToUpdateStarState && this._shouldUpdateStarState()) {
  1.1282 +      this.updateStarState();
  1.1283 +    } else if (usedToUpdateStarState && !this._shouldUpdateStarState()) {
  1.1284 +      this._updateStar();
  1.1285 +    }
  1.1286 +    // If we're moved outside of customize mode, we need to uninit
  1.1287 +    // our view so it gets reconstructed.
  1.1288 +    if (!this._isCustomizing) {
  1.1289 +      this._uninitView();
  1.1290 +    }
  1.1291 +    this._updateToolbarStyle();
  1.1292 +  },
  1.1293 +
  1.1294 +  onCustomizeEnd: function BUI_customizeEnd(aWindow) {
  1.1295 +    if (aWindow == window) {
  1.1296 +      this._isCustomizing = false;
  1.1297 +      this.onToolbarVisibilityChange();
  1.1298 +      this._updateToolbarStyle();
  1.1299 +    }
  1.1300 +  },
  1.1301 +
  1.1302 +  init: function() {
  1.1303 +    CustomizableUI.addListener(this);
  1.1304 +    this._updateCustomizationState();
  1.1305 +  },
  1.1306 +
  1.1307 +  _hasBookmarksObserver: false,
  1.1308 +  _itemIds: [],
  1.1309 +  uninit: function BUI_uninit() {
  1.1310 +    this._updateBookmarkPageMenuItem(true);
  1.1311 +    CustomizableUI.removeListener(this);
  1.1312 +
  1.1313 +    this._uninitView();
  1.1314 +
  1.1315 +    if (this._hasBookmarksObserver) {
  1.1316 +      PlacesUtils.removeLazyBookmarkObserver(this);
  1.1317 +    }
  1.1318 +
  1.1319 +    if (this._pendingStmt) {
  1.1320 +      this._pendingStmt.cancel();
  1.1321 +      delete this._pendingStmt;
  1.1322 +    }
  1.1323 +  },
  1.1324 +
  1.1325 +  onLocationChange: function BUI_onLocationChange() {
  1.1326 +    if (this._uri && gBrowser.currentURI.equals(this._uri)) {
  1.1327 +      return;
  1.1328 +    }
  1.1329 +    this.updateStarState();
  1.1330 +  },
  1.1331 +
  1.1332 +  updateStarState: function BUI_updateStarState() {
  1.1333 +    // Reset tracked values.
  1.1334 +    this._uri = gBrowser.currentURI;
  1.1335 +    this._itemIds = [];
  1.1336 +
  1.1337 +    if (this._pendingStmt) {
  1.1338 +      this._pendingStmt.cancel();
  1.1339 +      delete this._pendingStmt;
  1.1340 +    }
  1.1341 +
  1.1342 +    // We can load about:blank before the actual page, but there is no point in handling that page.
  1.1343 +    if (isBlankPageURL(this._uri.spec)) {
  1.1344 +      return;
  1.1345 +    }
  1.1346 +
  1.1347 +    this._pendingStmt = PlacesUtils.asyncGetBookmarkIds(this._uri, (aItemIds, aURI) => {
  1.1348 +      // Safety check that the bookmarked URI equals the tracked one.
  1.1349 +      if (!aURI.equals(this._uri)) {
  1.1350 +        Components.utils.reportError("BookmarkingUI did not receive current URI");
  1.1351 +        return;
  1.1352 +      }
  1.1353 +
  1.1354 +      // It's possible that onItemAdded gets called before the async statement
  1.1355 +      // calls back.  For such an edge case, retain all unique entries from both
  1.1356 +      // arrays.
  1.1357 +      this._itemIds = this._itemIds.filter(
  1.1358 +        function (id) aItemIds.indexOf(id) == -1
  1.1359 +      ).concat(aItemIds);
  1.1360 +
  1.1361 +      this._updateStar();
  1.1362 +
  1.1363 +      // Start observing bookmarks if needed.
  1.1364 +      if (!this._hasBookmarksObserver) {
  1.1365 +        try {
  1.1366 +          PlacesUtils.addLazyBookmarkObserver(this);
  1.1367 +          this._hasBookmarksObserver = true;
  1.1368 +        } catch(ex) {
  1.1369 +          Components.utils.reportError("BookmarkingUI failed adding a bookmarks observer: " + ex);
  1.1370 +        }
  1.1371 +      }
  1.1372 +
  1.1373 +      delete this._pendingStmt;
  1.1374 +    });
  1.1375 +  },
  1.1376 +
  1.1377 +  _updateStar: function BUI__updateStar() {
  1.1378 +    if (!this._shouldUpdateStarState()) {
  1.1379 +      if (this.button.hasAttribute("starred")) {
  1.1380 +        this.button.removeAttribute("starred");
  1.1381 +        this.button.removeAttribute("buttontooltiptext");
  1.1382 +      }
  1.1383 +      return;
  1.1384 +    }
  1.1385 +
  1.1386 +    if (this._itemIds.length > 0) {
  1.1387 +      this.button.setAttribute("starred", "true");
  1.1388 +      this.button.setAttribute("buttontooltiptext", this._starredTooltip);
  1.1389 +      if (this.button.getAttribute("overflowedItem") == "true") {
  1.1390 +        this.button.setAttribute("label", this._starButtonOverflowedStarredLabel);
  1.1391 +      }
  1.1392 +    }
  1.1393 +    else {
  1.1394 +      this.button.removeAttribute("starred");
  1.1395 +      this.button.setAttribute("buttontooltiptext", this._unstarredTooltip);
  1.1396 +      if (this.button.getAttribute("overflowedItem") == "true") {
  1.1397 +        this.button.setAttribute("label", this._starButtonOverflowedLabel);
  1.1398 +      }
  1.1399 +    }
  1.1400 +  },
  1.1401 +
  1.1402 +  /**
  1.1403 +   * forceReset is passed when we're destroyed and the label should go back
  1.1404 +   * to the default (Bookmark This Page) for OS X.
  1.1405 +   */
  1.1406 +  _updateBookmarkPageMenuItem: function BUI__updateBookmarkPageMenuItem(forceReset) {
  1.1407 +    let isStarred = !forceReset && this._itemIds.length > 0;
  1.1408 +    let label = isStarred ? "editlabel" : "bookmarklabel";
  1.1409 +    this.broadcaster.setAttribute("label", this.broadcaster.getAttribute(label));
  1.1410 +  },
  1.1411 +
  1.1412 +  onMainMenuPopupShowing: function BUI_onMainMenuPopupShowing(event) {
  1.1413 +    this._updateBookmarkPageMenuItem();
  1.1414 +    PlacesCommandHook.updateBookmarkAllTabsCommand();
  1.1415 +  },
  1.1416 +
  1.1417 +  _showBookmarkedNotification: function BUI_showBookmarkedNotification() {
  1.1418 +    function getCenteringTransformForRects(rectToPosition, referenceRect) {
  1.1419 +      let topDiff = referenceRect.top - rectToPosition.top;
  1.1420 +      let leftDiff = referenceRect.left - rectToPosition.left;
  1.1421 +      let heightDiff = referenceRect.height - rectToPosition.height;
  1.1422 +      let widthDiff = referenceRect.width - rectToPosition.width;
  1.1423 +      return [(leftDiff + .5 * widthDiff) + "px", (topDiff + .5 * heightDiff) + "px"];
  1.1424 +    }
  1.1425 +
  1.1426 +    if (this._notificationTimeout) {
  1.1427 +      clearTimeout(this._notificationTimeout);
  1.1428 +    }
  1.1429 +
  1.1430 +    if (this.notifier.style.transform == '') {
  1.1431 +      // Get all the relevant nodes and computed style objects
  1.1432 +      let dropmarker = document.getAnonymousElementByAttribute(this.button, "anonid", "dropmarker");
  1.1433 +      let dropmarkerIcon = document.getAnonymousElementByAttribute(dropmarker, "class", "dropmarker-icon");
  1.1434 +      let dropmarkerStyle = getComputedStyle(dropmarkerIcon);
  1.1435 +
  1.1436 +      // Check for RTL and get bounds
  1.1437 +      let isRTL = getComputedStyle(this.button).direction == "rtl";
  1.1438 +      let buttonRect = this.button.getBoundingClientRect();
  1.1439 +      let notifierRect = this.notifier.getBoundingClientRect();
  1.1440 +      let dropmarkerRect = dropmarkerIcon.getBoundingClientRect();
  1.1441 +      let dropmarkerNotifierRect = this.dropmarkerNotifier.getBoundingClientRect();
  1.1442 +
  1.1443 +      // Compute, but do not set, transform for star icon
  1.1444 +      let [translateX, translateY] = getCenteringTransformForRects(notifierRect, buttonRect);
  1.1445 +      let starIconTransform = "translate(" +  translateX + ", " + translateY + ")";
  1.1446 +      if (isRTL) {
  1.1447 +        starIconTransform += " scaleX(-1)";
  1.1448 +      }
  1.1449 +
  1.1450 +      // Compute, but do not set, transform for dropmarker
  1.1451 +      [translateX, translateY] = getCenteringTransformForRects(dropmarkerNotifierRect, dropmarkerRect);
  1.1452 +      let dropmarkerTransform = "translate(" + translateX + ", " + translateY + ")";
  1.1453 +
  1.1454 +      // Do all layout invalidation in one go:
  1.1455 +      this.notifier.style.transform = starIconTransform;
  1.1456 +      this.dropmarkerNotifier.style.transform = dropmarkerTransform;
  1.1457 +
  1.1458 +      let dropmarkerAnimationNode = this.dropmarkerNotifier.firstChild;
  1.1459 +      dropmarkerAnimationNode.style.MozImageRegion = dropmarkerStyle.MozImageRegion;
  1.1460 +      dropmarkerAnimationNode.style.listStyleImage = dropmarkerStyle.listStyleImage;
  1.1461 +    }
  1.1462 +
  1.1463 +    let isInOverflowPanel = this.button.getAttribute("overflowedItem") == "true";
  1.1464 +    if (!isInOverflowPanel) {
  1.1465 +      this.notifier.setAttribute("notification", "finish");
  1.1466 +      this.button.setAttribute("notification", "finish");
  1.1467 +      this.dropmarkerNotifier.setAttribute("notification", "finish");
  1.1468 +    }
  1.1469 +
  1.1470 +    this._notificationTimeout = setTimeout( () => {
  1.1471 +      this.notifier.removeAttribute("notification");
  1.1472 +      this.dropmarkerNotifier.removeAttribute("notification");
  1.1473 +      this.button.removeAttribute("notification");
  1.1474 +
  1.1475 +      this.dropmarkerNotifier.style.transform = '';
  1.1476 +      this.notifier.style.transform = '';
  1.1477 +    }, 1000);
  1.1478 +  },
  1.1479 +
  1.1480 +  _showSubview: function() {
  1.1481 +    let view = document.getElementById("PanelUI-bookmarks");
  1.1482 +    view.addEventListener("ViewShowing", this);
  1.1483 +    view.addEventListener("ViewHiding", this);
  1.1484 +    let anchor = document.getElementById(this.BOOKMARK_BUTTON_ID);
  1.1485 +    anchor.setAttribute("closemenu", "none");
  1.1486 +    PanelUI.showSubView("PanelUI-bookmarks", anchor,
  1.1487 +                        CustomizableUI.AREA_PANEL);
  1.1488 +  },
  1.1489 +
  1.1490 +  onCommand: function BUI_onCommand(aEvent) {
  1.1491 +    if (aEvent.target != aEvent.currentTarget) {
  1.1492 +      return;
  1.1493 +    }
  1.1494 +
  1.1495 +    // Handle special case when the button is in the panel.
  1.1496 +    let isBookmarked = this._itemIds.length > 0;
  1.1497 +
  1.1498 +    if (this._currentAreaType == CustomizableUI.TYPE_MENU_PANEL) {
  1.1499 +      this._showSubview();
  1.1500 +      return;
  1.1501 +    }
  1.1502 +    let widget = CustomizableUI.getWidget(this.BOOKMARK_BUTTON_ID)
  1.1503 +                               .forWindow(window);
  1.1504 +    if (widget.overflowed) {
  1.1505 +      // Allow to close the panel if the page is already bookmarked, cause
  1.1506 +      // we are going to open the edit bookmark panel.
  1.1507 +      if (isBookmarked)
  1.1508 +        widget.node.removeAttribute("closemenu");
  1.1509 +      else
  1.1510 +        widget.node.setAttribute("closemenu", "none");
  1.1511 +    }
  1.1512 +
  1.1513 +    // Ignore clicks on the star if we are updating its state.
  1.1514 +    if (!this._pendingStmt) {
  1.1515 +      if (!isBookmarked)
  1.1516 +        this._showBookmarkedNotification();
  1.1517 +      PlacesCommandHook.bookmarkCurrentPage(isBookmarked);
  1.1518 +    }
  1.1519 +  },
  1.1520 +
  1.1521 +  handleEvent: function BUI_handleEvent(aEvent) {
  1.1522 +    switch (aEvent.type) {
  1.1523 +      case "ViewShowing":
  1.1524 +        this.onPanelMenuViewShowing(aEvent);
  1.1525 +        break;
  1.1526 +      case "ViewHiding":
  1.1527 +        this.onPanelMenuViewHiding(aEvent);
  1.1528 +        break;
  1.1529 +    }
  1.1530 +  },
  1.1531 +
  1.1532 +  onPanelMenuViewShowing: function BUI_onViewShowing(aEvent) {
  1.1533 +    this._updateBookmarkPageMenuItem();
  1.1534 +    // Update checked status of the toolbar toggle.
  1.1535 +    let viewToolbar = document.getElementById("panelMenu_viewBookmarksToolbar");
  1.1536 +    let personalToolbar = document.getElementById("PersonalToolbar");
  1.1537 +    if (personalToolbar.collapsed)
  1.1538 +      viewToolbar.removeAttribute("checked");
  1.1539 +    else
  1.1540 +      viewToolbar.setAttribute("checked", "true");
  1.1541 +    // Setup the Places view.
  1.1542 +    this._panelMenuView = new PlacesPanelMenuView("place:folder=BOOKMARKS_MENU",
  1.1543 +                                                  "panelMenu_bookmarksMenu",
  1.1544 +                                                  "panelMenu_bookmarksMenu", {
  1.1545 +                                                    extraClasses: {
  1.1546 +                                                      entry: "subviewbutton",
  1.1547 +                                                      footer: "panel-subview-footer"
  1.1548 +                                                    }
  1.1549 +                                                  });
  1.1550 +    aEvent.target.removeEventListener("ViewShowing", this);
  1.1551 +  },
  1.1552 +
  1.1553 +  onPanelMenuViewHiding: function BUI_onViewHiding(aEvent) {
  1.1554 +    this._panelMenuView.uninit();
  1.1555 +    delete this._panelMenuView;
  1.1556 +    aEvent.target.removeEventListener("ViewHiding", this);
  1.1557 +  },
  1.1558 +
  1.1559 +  onPanelMenuViewCommand: function BUI_onPanelMenuViewCommand(aEvent, aView) {
  1.1560 +    let target = aEvent.originalTarget;
  1.1561 +    if (!target._placesNode)
  1.1562 +      return;
  1.1563 +    if (PlacesUtils.nodeIsContainer(target._placesNode))
  1.1564 +      PlacesCommandHook.showPlacesOrganizer([ "BookmarksMenu", target._placesNode.itemId ]);
  1.1565 +    else
  1.1566 +      PlacesUIUtils.openNodeWithEvent(target._placesNode, aEvent, aView);
  1.1567 +    PanelUI.hide();
  1.1568 +  },
  1.1569 +
  1.1570 +  // nsINavBookmarkObserver
  1.1571 +  onItemAdded: function BUI_onItemAdded(aItemId, aParentId, aIndex, aItemType,
  1.1572 +                                        aURI) {
  1.1573 +    if (aURI && aURI.equals(this._uri)) {
  1.1574 +      // If a new bookmark has been added to the tracked uri, register it.
  1.1575 +      if (this._itemIds.indexOf(aItemId) == -1) {
  1.1576 +        this._itemIds.push(aItemId);
  1.1577 +        // Only need to update the UI if it wasn't marked as starred before:
  1.1578 +        if (this._itemIds.length == 1) {
  1.1579 +          this._updateStar();
  1.1580 +        }
  1.1581 +      }
  1.1582 +    }
  1.1583 +  },
  1.1584 +
  1.1585 +  onItemRemoved: function BUI_onItemRemoved(aItemId) {
  1.1586 +    let index = this._itemIds.indexOf(aItemId);
  1.1587 +    // If one of the tracked bookmarks has been removed, unregister it.
  1.1588 +    if (index != -1) {
  1.1589 +      this._itemIds.splice(index, 1);
  1.1590 +      // Only need to update the UI if the page is no longer starred
  1.1591 +      if (this._itemIds.length == 0) {
  1.1592 +        this._updateStar();
  1.1593 +      }
  1.1594 +    }
  1.1595 +  },
  1.1596 +
  1.1597 +  onItemChanged: function BUI_onItemChanged(aItemId, aProperty,
  1.1598 +                                            aIsAnnotationProperty, aNewValue) {
  1.1599 +    if (aProperty == "uri") {
  1.1600 +      let index = this._itemIds.indexOf(aItemId);
  1.1601 +      // If the changed bookmark was tracked, check if it is now pointing to
  1.1602 +      // a different uri and unregister it.
  1.1603 +      if (index != -1 && aNewValue != this._uri.spec) {
  1.1604 +        this._itemIds.splice(index, 1);
  1.1605 +        // Only need to update the UI if the page is no longer starred
  1.1606 +        if (this._itemIds.length == 0) {
  1.1607 +          this._updateStar();
  1.1608 +        }
  1.1609 +      }
  1.1610 +      // If another bookmark is now pointing to the tracked uri, register it.
  1.1611 +      else if (index == -1 && aNewValue == this._uri.spec) {
  1.1612 +        this._itemIds.push(aItemId);
  1.1613 +        // Only need to update the UI if it wasn't marked as starred before:
  1.1614 +        if (this._itemIds.length == 1) {
  1.1615 +          this._updateStar();
  1.1616 +        }
  1.1617 +      }
  1.1618 +    }
  1.1619 +  },
  1.1620 +
  1.1621 +  onBeginUpdateBatch: function () {},
  1.1622 +  onEndUpdateBatch: function () {},
  1.1623 +  onBeforeItemRemoved: function () {},
  1.1624 +  onItemVisited: function () {},
  1.1625 +  onItemMoved: function () {},
  1.1626 +
  1.1627 +  // CustomizableUI events:
  1.1628 +  _starButtonLabel: null,
  1.1629 +  get _starButtonOverflowedLabel() {
  1.1630 +    delete this._starButtonOverflowedLabel;
  1.1631 +    return this._starButtonOverflowedLabel =
  1.1632 +      gNavigatorBundle.getString("starButtonOverflowed.label");
  1.1633 +  },
  1.1634 +  get _starButtonOverflowedStarredLabel() {
  1.1635 +    delete this._starButtonOverflowedStarredLabel;
  1.1636 +    return this._starButtonOverflowedStarredLabel =
  1.1637 +      gNavigatorBundle.getString("starButtonOverflowedStarred.label");
  1.1638 +  },
  1.1639 +  onWidgetOverflow: function(aNode, aContainer) {
  1.1640 +    let win = aNode.ownerDocument.defaultView;
  1.1641 +    if (aNode.id != this.BOOKMARK_BUTTON_ID || win != window)
  1.1642 +      return;
  1.1643 +
  1.1644 +    let currentLabel = aNode.getAttribute("label");
  1.1645 +    if (!this._starButtonLabel)
  1.1646 +      this._starButtonLabel = currentLabel;
  1.1647 +
  1.1648 +    if (currentLabel == this._starButtonLabel) {
  1.1649 +      let desiredLabel = this._itemIds.length > 0 ? this._starButtonOverflowedStarredLabel
  1.1650 +                                                 : this._starButtonOverflowedLabel;
  1.1651 +      aNode.setAttribute("label", desiredLabel);
  1.1652 +    }
  1.1653 +  },
  1.1654 +
  1.1655 +  onWidgetUnderflow: function(aNode, aContainer) {
  1.1656 +    let win = aNode.ownerDocument.defaultView;
  1.1657 +    if (aNode.id != this.BOOKMARK_BUTTON_ID || win != window)
  1.1658 +      return;
  1.1659 +
  1.1660 +    // The view gets broken by being removed and reinserted. Uninit
  1.1661 +    // here so popupshowing will generate a new one:
  1.1662 +    this._uninitView();
  1.1663 +
  1.1664 +    if (aNode.getAttribute("label") != this._starButtonLabel)
  1.1665 +      aNode.setAttribute("label", this._starButtonLabel);
  1.1666 +  },
  1.1667 +
  1.1668 +  QueryInterface: XPCOMUtils.generateQI([
  1.1669 +    Ci.nsINavBookmarkObserver
  1.1670 +  ])
  1.1671 +};

mercurial