browser-places.js
Go to the documentation of this file.
1 # ***** BEGIN LICENSE BLOCK *****
2 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 #
4 # The contents of this file are subject to the Mozilla Public License Version
5 # 1.1 (the "License"); you may not use this file except in compliance with
6 # the License. You may obtain a copy of the License at
7 # http://www.mozilla.org/MPL/
8 #
9 # Software distributed under the License is distributed on an "AS IS" basis,
10 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 # for the specific language governing rights and limitations under the
12 # License.
13 #
14 # The Original Code is the Places Browser Integration
15 #
16 # The Initial Developer of the Original Code is Google Inc.
17 # Portions created by the Initial Developer are Copyright (C) 2006
18 # the Initial Developer. All Rights Reserved.
19 #
20 # Contributor(s):
21 # Ben Goodger <beng@google.com>
22 # Annie Sullivan <annie.sullivan@gmail.com>
23 # Joe Hughes <joe@retrovirus.com>
24 # Asaf Romano <mano@mozilla.com>
25 # Ehsan Akhgari <ehsan.akhgari@gmail.com>
26 #
27 # Alternatively, the contents of this file may be used under the terms of
28 # either the GNU General Public License Version 2 or later (the "GPL"), or
29 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 # in which case the provisions of the GPL or the LGPL are applicable instead
31 # of those above. If you wish to allow use of your version of this file only
32 # under the terms of either the GPL or the LGPL, and not to allow others to
33 # use your version of this file under the terms of the MPL, indicate your
34 # decision by deleting the provisions above and replace them with the notice
35 # and other provisions required by the GPL or the LGPL. If you do not delete
36 # the provisions above, a recipient may use your version of this file under
37 # the terms of any one of the MPL, the GPL or the LGPL.
38 #
39 # ***** END LICENSE BLOCK *****
40 
41 
42 var StarUI = {
43  _itemId: -1,
44  uri: null,
45  _batching: false,
46 
47  // nsISupports
48  QueryInterface: function SU_QueryInterface(aIID) {
49  if (aIID.equals(Ci.nsIDOMEventListener) ||
50  aIID.equals(Ci.nsISupports))
51  return this;
52 
53  throw Cr.NS_NOINTERFACE;
54  },
55 
56  _element: function(aID) {
57  return document.getElementById(aID);
58  },
59 
60  // Edit-bookmark panel
61  get panel() {
62  delete this.panel;
63  var element = this._element("editBookmarkPanel");
64  // initially the panel is hidden
65  // to avoid impacting startup / new window performance
66  element.hidden = false;
67  element.addEventListener("popuphidden", this, false);
68  element.addEventListener("keypress", this, false);
69  return this.panel = element;
70  },
71 
72  // list of command elements (by id) to disable when the panel is opened
73  _blockedCommands: ["cmd_close", "cmd_closeWindow"],
74  _blockCommands: function SU__blockCommands() {
75  for each(var key in this._blockedCommands) {
76  var elt = this._element(key);
77  // make sure not to permanently disable this item (see bug 409155)
78  if (elt.hasAttribute("wasDisabled"))
79  continue;
80  if (elt.getAttribute("disabled") == "true")
81  elt.setAttribute("wasDisabled", "true");
82  else {
83  elt.setAttribute("wasDisabled", "false");
84  elt.setAttribute("disabled", "true");
85  }
86  }
87  },
88 
89  _restoreCommandsState: function SU__restoreCommandsState() {
90  for each(var key in this._blockedCommands) {
91  var elt = this._element(key);
92  if (elt.getAttribute("wasDisabled") != "true")
93  elt.removeAttribute("disabled");
94  elt.removeAttribute("wasDisabled");
95  }
96  },
97 
98  // nsIDOMEventListener
99  handleEvent: function SU_handleEvent(aEvent) {
100  switch (aEvent.type) {
101  case "popuphidden":
102  if (aEvent.originalTarget == this.panel) {
103  if (!this._element("editBookmarkPanelContent").hidden)
104  this.quitEditMode();
105  this._restoreCommandsState();
106  this._itemId = -1;
107  this._uri = null;
108  if (this._batching) {
109  PlacesUIUtils.ptm.endBatch();
110  this._batching = false;
111  }
112  }
113  break;
114  case "keypress":
115  if (aEvent.getPreventDefault()) {
116  // The event has already been consumed inside of the panel.
117  break;
118  }
119  switch (aEvent.keyCode) {
120  case KeyEvent.DOM_VK_ESCAPE:
121  if (!this._element("editBookmarkPanelContent").hidden)
122  this.cancelButtonOnCommand();
123  break;
124  case KeyEvent.DOM_VK_RETURN:
125  if (aEvent.target.className == "expander-up" ||
126  aEvent.target.className == "expander-down" ||
127  aEvent.target.id == "editBMPanel_newFolderButton") {
128  //XXX Why is this necessary? The getPreventDefault() check should
129  // be enough.
130  break;
131  }
132  this.panel.hidePopup();
133  break;
134  }
135  break;
136  }
137  },
138 
139  _overlayLoaded: false,
140  _overlayLoading: false,
141  showEditBookmarkPopup:
142  function SU_showEditBookmarkPopup(aItemId, aAnchorElement, aPosition) {
143  // Performance: load the overlay the first time the panel is opened
144  // (see bug 392443).
145  if (this._overlayLoading)
146  return;
147 
148  if (this._overlayLoaded) {
149  this._doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition);
150  return;
151  }
152 
153  var loadObserver = {
154  _self: this,
155  _itemId: aItemId,
156  _anchorElement: aAnchorElement,
157  _position: aPosition,
158  observe: function (aSubject, aTopic, aData) {
159  this._self._overlayLoading = false;
160  this._self._overlayLoaded = true;
161  this._self._doShowEditBookmarkPanel(this._itemId, this._anchorElement,
162  this._position);
163  }
164  };
165  this._overlayLoading = true;
166  document.loadOverlay("chrome://browser/content/places/editBookmarkOverlay.xul",
167  loadObserver);
168  },
169 
170  _doShowEditBookmarkPanel:
171  function SU__doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition) {
172  if (this.panel.state != "closed")
173  return;
174 
175  this._blockCommands(); // un-done in the popuphiding handler
176 
177  // Move the header (star, title, possibly a button) into the grid,
178  // so that it aligns nicely with the other items (bug 484022).
179  var rows = this._element("editBookmarkPanelGrid").lastChild;
180  var header = this._element("editBookmarkPanelHeader");
181  rows.insertBefore(header, rows.firstChild);
182  header.hidden = false;
183 
184  // Set panel title:
185  // if we are batching, i.e. the bookmark has been added now,
186  // then show Page Bookmarked, else if the bookmark did already exist,
187  // we are about editing it, then use Edit This Bookmark.
188  this._element("editBookmarkPanelTitle").value =
189  this._batching ?
190  gNavigatorBundle.getString("editBookmarkPanel.pageBookmarkedTitle") :
191  gNavigatorBundle.getString("editBookmarkPanel.editBookmarkTitle");
192 
193  // No description; show the Done, Cancel;
194  // hide the Edit, Undo buttons
195  this._element("editBookmarkPanelDescription").textContent = "";
196  this._element("editBookmarkPanelBottomButtons").hidden = false;
197  this._element("editBookmarkPanelContent").hidden = false;
198  this._element("editBookmarkPanelEditButton").hidden = true;
199  this._element("editBookmarkPanelUndoRemoveButton").hidden = true;
200 
201  // The remove button is shown only if we're not already batching, i.e.
202  // if the cancel button/ESC does not remove the bookmark.
203  this._element("editBookmarkPanelRemoveButton").hidden = this._batching;
204 
205  // The label of the remove button differs if the URI is bookmarked
206  // multiple times.
207  var bookmarks = PlacesUtils.getBookmarksForURI(gBrowser.currentURI);
208  var forms = gNavigatorBundle.getString("editBookmark.removeBookmarks.label");
209  var label = PluralForm.get(bookmarks.length, forms).replace("#1", bookmarks.length);
210  this._element("editBookmarkPanelRemoveButton").label = label;
211 
212  // unset the unstarred state, if set
213  this._element("editBookmarkPanelStarIcon").removeAttribute("unstarred");
214 
215  this._itemId = aItemId !== undefined ? aItemId : this._itemId;
216  this.beginBatch();
217 
218  // Consume dismiss clicks, see bug 400924
219  this.panel.popupBoxObject
220  .setConsumeRollupEvent(Ci.nsIPopupBoxObject.ROLLUP_CONSUME);
221  this.panel.openPopup(aAnchorElement, aPosition, -1, -1);
222 
223  gEditItemOverlay.initPanel(this._itemId,
224  { hiddenRows: ["description", "location",
225  "loadInSidebar", "keyword"] });
226  },
227 
228  panelShown:
229  function SU_panelShown(aEvent) {
230  if (aEvent.target == this.panel) {
231  if (!this._element("editBookmarkPanelContent").hidden) {
232  fieldToFocus = "editBMPanel_" +
233  gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField");
234  var elt = this._element(fieldToFocus);
235  elt.focus();
236  elt.select();
237  }
238  else {
239  // Note this isn't actually used anymore, we should remove this
240  // once we decide not to bring back the page bookmarked notification
241  this.panel.focus();
242  }
243  }
244  },
245 
246  showPageBookmarkedNotification:
247  function PCH_showPageBookmarkedNotification(aItemId, aAnchorElement, aPosition) {
248  this._blockCommands(); // un-done in the popuphiding handler
249 
250  var brandBundle = this._element("bundle_brand");
251  var brandShortName = brandBundle.getString("brandShortName");
252 
253  // "Page Bookmarked" title
254  this._element("editBookmarkPanelTitle").value =
255  gNavigatorBundle.getString("editBookmarkPanel.pageBookmarkedTitle");
256 
257  // description
258  this._element("editBookmarkPanelDescription").textContent =
259  gNavigatorBundle.getFormattedString("editBookmarkPanel.pageBookmarkedDescription",
260  [brandShortName]);
261 
262  // show the "Edit.." button and the Remove Bookmark button, hide the
263  // undo-remove-bookmark button.
264  this._element("editBookmarkPanelEditButton").hidden = false;
265  this._element("editBookmarkPanelRemoveButton").hidden = false;
266  this._element("editBookmarkPanelUndoRemoveButton").hidden = true;
267 
268  // unset the unstarred state, if set
269  this._element("editBookmarkPanelStarIcon").removeAttribute("unstarred");
270 
271  this._itemId = aItemId !== undefined ? aItemId : this._itemId;
272  if (this.panel.state == "closed") {
273  // Consume dismiss clicks, see bug 400924
274  this.panel.popupBoxObject
275  .setConsumeRollupEvent(Ci.nsIPopupBoxObject.ROLLUP_CONSUME);
276  this.panel.openPopup(aAnchorElement, aPosition, -1, -1);
277  }
278  else
279  this.panel.focus();
280  },
281 
282  quitEditMode: function SU_quitEditMode() {
283  this._element("editBookmarkPanelContent").hidden = true;
284  this._element("editBookmarkPanelBottomButtons").hidden = true;
285  gEditItemOverlay.uninitPanel(true);
286  },
287 
288  editButtonCommand: function SU_editButtonCommand() {
289  this.showEditBookmarkPopup();
290  },
291 
292  cancelButtonOnCommand: function SU_cancelButtonOnCommand() {
293  // The order here is important! We have to hide the panel first, otherwise
294  // changes done as part of Undo may change the panel contents and by
295  // that force it to commit more transactions
296  this.panel.hidePopup();
297  this.endBatch();
298  PlacesUIUtils.ptm.undoTransaction();
299  },
300 
301  removeBookmarkButtonCommand: function SU_removeBookmarkButtonCommand() {
302 #ifdef ADVANCED_STARRING_UI
303  // In minimal mode ("page bookmarked" notification), the bookmark
304  // is removed and the panel is hidden immediately. In full edit mode,
305  // a "Bookmark Removed" notification along with an Undo button is
306  // shown
307  if (this._batching) {
308  PlacesUIUtils.ptm.endBatch();
309  PlacesUIUtils.ptm.beginBatch(); // allow undo from within the notification
310 
311  // "Bookmark Removed" title (the description field is already empty in
312  // this mode)
313  this._element("editBookmarkPanelTitle").value =
314  gNavigatorBundle.getString("editBookmarkPanel.bookmarkedRemovedTitle");
315 
316  // hide the edit panel
317  this.quitEditMode();
318 
319  // Hide the remove bookmark button, show the undo-remove-bookmark
320  // button.
321  this._element("editBookmarkPanelUndoRemoveButton").hidden = false;
322  this._element("editBookmarkPanelRemoveButton").hidden = true;
323  this._element("editBookmarkPanelStarIcon").setAttribute("unstarred", "true");
324  this.panel.focus();
325  }
326 #endif
327 
328  // cache its uri so we can get the new itemId in the case of undo
329  this._uri = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
330 
331  // remove all bookmarks for the bookmark's url, this also removes
332  // the tags for the url
333  var itemIds = PlacesUtils.getBookmarksForURI(this._uri);
334  for (var i=0; i < itemIds.length; i++) {
335  var txn = PlacesUIUtils.ptm.removeItem(itemIds[i]);
336  PlacesUIUtils.ptm.doTransaction(txn);
337  }
338 
339 #ifdef ADVANCED_STARRING_UI
340  // hidePopup resets our itemId, thus we call it only after removing
341  // the bookmark
342  if (!this._batching)
343 #endif
344  this.panel.hidePopup();
345  },
346 
347  undoRemoveBookmarkCommand: function SU_undoRemoveBookmarkCommand() {
348  // restore the bookmark by undoing the last transaction and go back
349  // to the edit state
350  this.endBatch();
351  PlacesUIUtils.ptm.undoTransaction();
352  this._itemId = PlacesUtils.getMostRecentBookmarkForURI(this._uri);
353  this.showEditBookmarkPopup();
354  },
355 
356  beginBatch: function SU_beginBatch() {
357  if (!this._batching) {
358  PlacesUIUtils.ptm.beginBatch();
359  this._batching = true;
360  }
361  },
362 
363  endBatch: function SU_endBatch() {
364  if (this._batching) {
365  PlacesUIUtils.ptm.endBatch();
366  this._batching = false;
367  }
368  }
369 }
370 
371 var PlacesCommandHook = {
383  bookmarkPage: function PCH_bookmarkPage(aBrowser, aParent, aShowEditUI) {
384  var uri = aBrowser.currentURI;
385  var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
386  if (itemId == -1) {
387  // Copied over from addBookmarkForBrowser:
388  // Bug 52536: We obtain the URL and title from the nsIWebNavigation
389  // associated with a <browser/> rather than from a DOMWindow.
390  // This is because when a full page plugin is loaded, there is
391  // no DOMWindow (?) but information about the loaded document
392  // may still be obtained from the webNavigation.
393  var webNav = aBrowser.webNavigation;
394  var url = webNav.currentURI;
395  var title;
396  var description;
397  var charset;
398  try {
399  title = webNav.document.title || url.spec;
400  description = PlacesUIUtils.getDescriptionFromDocument(webNav.document);
401  charset = webNav.document.characterSet;
402  }
403  catch (e) { }
404 
405  if (aShowEditUI) {
406  // If we bookmark the page here (i.e. page was not "starred" already)
407  // but open right into the "edit" state, start batching here, so
408  // "Cancel" in that state removes the bookmark.
409  StarUI.beginBatch();
410  }
411 
412  var parent = aParent != undefined ?
413  aParent : PlacesUtils.unfiledBookmarksFolderId;
414  var descAnno = { name: DESCRIPTION_ANNO, value: description };
415  var txn = PlacesUIUtils.ptm.createItem(uri, parent, -1,
416  title, null, [descAnno]);
417  PlacesUIUtils.ptm.doTransaction(txn);
418  // Set the character-set
419  if (charset)
420  PlacesUtils.history.setCharsetForURI(uri, charset);
421  itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
422  }
423 
424  // Revert the contents of the location bar
425  if (gURLBar)
426  gURLBar.handleRevert();
427 
428  // dock the panel to the star icon when possible, otherwise dock
429  // it to the content area
430  if (aBrowser.contentWindow == window.content) {
431  var starIcon = aBrowser.ownerDocument.getElementById("star-button");
432  if (starIcon && isElementVisible(starIcon)) {
433  // Make sure the bookmark properties dialog hangs toward the middle of
434  // the location bar in RTL builds
435  var position = (getComputedStyle(gNavToolbox, "").direction == "rtl") ? 'after_start' : 'after_end';
436  if (aShowEditUI)
437  StarUI.showEditBookmarkPopup(itemId, starIcon, position);
438 #ifdef ADVANCED_STARRING_UI
439  else
440  StarUI.showPageBookmarkedNotification(itemId, starIcon, position);
441 #endif
442  return;
443  }
444  }
445 
446  StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap");
447  },
448 
452  bookmarkCurrentPage: function PCH_bookmarkCurrentPage(aShowEditUI, aParent) {
453  this.bookmarkPage(getBrowser().selectedBrowser, aParent, aShowEditUI);
454  },
455 
466  bookmarkLink: function PCH_bookmarkLink(aParent, aURL, aTitle) {
467  var linkURI = makeURI(aURL);
468  var itemId = PlacesUtils.getMostRecentBookmarkForURI(linkURI);
469  if (itemId == -1)
470  PlacesUIUtils.showMinimalAddBookmarkUI(linkURI, aTitle);
471  else {
472  PlacesUIUtils.showItemProperties(itemId,
473  PlacesUtils.bookmarks.TYPE_BOOKMARK);
474  }
475  },
476 
485  _getUniqueTabInfo: function BATC__getUniqueTabInfo() {
486  var tabList = [];
487  var seenURIs = [];
488 
489  var browsers = getBrowser().browsers;
490  for (var i = 0; i < browsers.length; ++i) {
491  var webNav = browsers[i].webNavigation;
492  var uri = webNav.currentURI;
493 
494  // skip redundant entries
495  if (uri.spec in seenURIs)
496  continue;
497 
498  // add to the set of seen URIs
499  seenURIs[uri.spec] = true;
500  tabList.push(uri);
501  }
502  return tabList;
503  },
504 
509  bookmarkCurrentPages: function PCH_bookmarkCurrentPages() {
510  var tabURIs = this._getUniqueTabInfo();
511  PlacesUIUtils.showMinimalAddMultiBookmarkUI(tabURIs);
512  },
513 
514 
524  addLiveBookmark: function PCH_addLiveBookmark(url, feedTitle, feedSubtitle) {
525  var feedURI = makeURI(url);
526 
527  var doc = gBrowser.contentDocument;
528  var title = (arguments.length > 1) ? feedTitle : doc.title;
529 
530  var description;
531  if (arguments.length > 2)
532  description = feedSubtitle;
533  else
534  description = PlacesUIUtils.getDescriptionFromDocument(doc);
535 
536  var toolbarIP =
537  new InsertionPoint(PlacesUtils.bookmarks.toolbarFolder, -1);
538  PlacesUIUtils.showMinimalAddLivemarkUI(feedURI, gBrowser.currentURI,
539  title, description, toolbarIP, true);
540  },
541 
549  showPlacesOrganizer: function PCH_showPlacesOrganizer(aLeftPaneRoot) {
550  var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
551  getService(Ci.nsIWindowMediator);
552  var organizer = wm.getMostRecentWindow("Places:Organizer");
553  if (!organizer) {
554  // No currently open places window, so open one with the specified mode.
555  openDialog("chrome://browser/content/places/places.xul",
556  "", "chrome,toolbar=yes,dialog=no,resizable", aLeftPaneRoot);
557  }
558  else {
559  organizer.PlacesOrganizer.selectLeftPaneQuery(aLeftPaneRoot);
560  organizer.focus();
561  }
562  },
563 
564  deleteButtonOnCommand: function PCH_deleteButtonCommand() {
565  PlacesUtils.bookmarks.removeItem(gEditItemOverlay.itemId);
566 
567  // remove all tags for the associated url
568  PlacesUtils.tagging.untagURI(gEditItemOverlay._uri, null);
569 
570  this.panel.hidePopup();
571  }
572 };
573 
574 // Helper object for the history menu.
575 var HistoryMenu = {
576  get _ss() {
577  delete this._ss;
578  return this._ss = Cc["@mozilla.org/browser/sessionstore;1"].
579  getService(Ci.nsISessionStore);
580  },
581 
582  toggleRecentlyClosedTabs: function PHM_toggleRecentlyClosedTabs() {
583  // enable/disable the Recently Closed Tabs sub menu
584  var undoPopup = document.getElementById("historyUndoPopup");
585 
586  // no restorable tabs, so disable menu
587  if (this._ss.getClosedTabCount(window) == 0)
588  undoPopup.parentNode.setAttribute("disabled", true);
589  else
590  undoPopup.parentNode.removeAttribute("disabled");
591  },
592 
599  _undoCloseMiddleClick: function PHM__undoCloseMiddleClick(aEvent) {
600  if (aEvent.button != 1)
601  return;
602 
603  undoCloseTab(aEvent.originalTarget.value);
604  gBrowser.moveTabToEnd();
605  },
606 
610  populateUndoSubmenu: function PHM_populateUndoSubmenu() {
611  var undoPopup = document.getElementById("historyUndoPopup");
612 
613  // remove existing menu items
614  while (undoPopup.hasChildNodes())
615  undoPopup.removeChild(undoPopup.firstChild);
616 
617  // no restorable tabs, so make sure menu is disabled, and return
618  if (this._ss.getClosedTabCount(window) == 0) {
619  undoPopup.parentNode.setAttribute("disabled", true);
620  return;
621  }
622 
623  // enable menu
624  undoPopup.parentNode.removeAttribute("disabled");
625 
626  // populate menu
627  var undoItems = eval("(" + this._ss.getClosedTabData(window) + ")");
628  for (var i = 0; i < undoItems.length; i++) {
629  var m = document.createElement("menuitem");
630  m.setAttribute("label", undoItems[i].title);
631  if (undoItems[i].image) {
632  let iconURL = undoItems[i].image;
633  // don't initiate a connection just to fetch a favicon (see bug 467828)
634  if (/^https?:/.test(iconURL))
635  iconURL = "moz-anno:favicon:" + iconURL;
636  m.setAttribute("image", iconURL);
637  }
638  m.setAttribute("class", "menuitem-iconic bookmark-item");
639  m.setAttribute("value", i);
640  m.setAttribute("oncommand", "undoCloseTab(" + i + ");");
641  m.addEventListener("click", this._undoCloseMiddleClick, false);
642  if (i == 0)
643  m.setAttribute("key", "key_undoCloseTab");
644  undoPopup.appendChild(m);
645  }
646 
647  // "Restore All Tabs"
649  undoPopup.appendChild(document.createElement("menuseparator"));
650  m = undoPopup.appendChild(document.createElement("menuitem"));
651  m.id = "menu_restoreAllTabs";
652  m.setAttribute("label", strings.getString("menuRestoreAllTabs.label"));
653  m.setAttribute("accesskey", strings.getString("menuRestoreAllTabs.accesskey"));
654  m.addEventListener("command", function() {
655  for (var i = 0; i < undoItems.length; i++)
656  undoCloseTab();
657  }, false);
658  },
659 
660  toggleRecentlyClosedWindows: function PHM_toggleRecentlyClosedWindows() {
661  // enable/disable the Recently Closed Windows sub menu
662  let undoPopup = document.getElementById("historyUndoWindowPopup");
663 
664  // no restorable windows, so disable menu
665  if (this._ss.getClosedWindowCount() == 0)
666  undoPopup.parentNode.setAttribute("disabled", true);
667  else
668  undoPopup.parentNode.removeAttribute("disabled");
669  },
670 
674  populateUndoWindowSubmenu: function PHM_populateUndoWindowSubmenu() {
675  let undoPopup = document.getElementById("historyUndoWindowPopup");
676  let menuLabelString = gNavigatorBundle.getString("menuUndoCloseWindowLabel");
677  let menuLabelStringSingleTab =
678  gNavigatorBundle.getString("menuUndoCloseWindowSingleTabLabel");
679 
680  // remove existing menu items
681  while (undoPopup.hasChildNodes())
682  undoPopup.removeChild(undoPopup.firstChild);
683 
684  // no restorable windows, so make sure menu is disabled, and return
685  if (this._ss.getClosedWindowCount() == 0) {
686  undoPopup.parentNode.setAttribute("disabled", true);
687  return;
688  }
689 
690  // enable menu
691  undoPopup.parentNode.removeAttribute("disabled");
692 
693  // populate menu
694  let undoItems = JSON.parse(this._ss.getClosedWindowData());
695  for (let i = 0; i < undoItems.length; i++) {
696  let undoItem = undoItems[i];
697  let otherTabsCount = undoItem.tabs.length - 1;
698  let label = (otherTabsCount == 0) ? menuLabelStringSingleTab
699  : PluralForm.get(otherTabsCount, menuLabelString);
700  let menuLabel = label.replace("#1", undoItem.title)
701  .replace("#2", otherTabsCount);
702  let m = document.createElement("menuitem");
703  m.setAttribute("label", menuLabel);
704  let selectedTab = undoItem.tabs[undoItem.selected - 1];
705  if (selectedTab.attributes.image) {
706  let iconURL = selectedTab.attributes.image;
707  // don't initiate a connection just to fetch a favicon (see bug 467828)
708  if (/^https?:/.test(iconURL))
709  iconURL = "moz-anno:favicon:" + iconURL;
710  m.setAttribute("image", iconURL);
711  }
712  m.setAttribute("class", "menuitem-iconic bookmark-item");
713  m.setAttribute("oncommand", "undoCloseWindow(" + i + ");");
714  if (i == 0)
715  m.setAttribute("key", "key_undoCloseWindow");
716  undoPopup.appendChild(m);
717  }
718 
719  // "Open All in Windows"
720  undoPopup.appendChild(document.createElement("menuseparator"));
721  let m = undoPopup.appendChild(document.createElement("menuitem"));
722  m.id = "menu_restoreAllWindows";
723  m.setAttribute("label", gNavigatorBundle.getString("menuRestoreAllWindows.label"));
724  m.setAttribute("accesskey", gNavigatorBundle.getString("menuRestoreAllWindows.accesskey"));
725  m.setAttribute("oncommand",
726  "for (var i = 0; i < " + undoItems.length + "; i++) undoCloseWindow();");
727  },
728 
734  onPopupShowing: function PHM_onPopupShowing(aEvent) {
735  // Don't handle events for submenus.
736  if (aEvent.target != aEvent.currentTarget)
737  return;
738 
739  var menuPopup = aEvent.target;
740  var resultNode = menuPopup.getResultNode();
741  resultNode.containerOpen = true;
742  document.getElementById("endHistorySeparator").hidden =
743  resultNode.childCount == 0;
744 
745  this.toggleRecentlyClosedTabs();
746  this.toggleRecentlyClosedWindows();
747  },
748 
754  onPopupHidden: function PHM_onPopupHidden(aEvent) {
755  // Don't handle events for submenus.
756  if (aEvent.target != aEvent.currentTarget)
757  return;
758 
759  var menuPopup = aEvent.target;
760  var resultNode = menuPopup.getResultNode();
761  if (resultNode.containerOpen)
762  resultNode.containerOpen = false;
763  }
764 };
765 
779  onClick: function BT_onClick(aEvent) {
780  // Only handle middle-click or left-click with modifiers.
781 #ifdef XP_MACOSX
782  var modifKey = aEvent.metaKey || aEvent.shiftKey;
783 #else
784  var modifKey = aEvent.ctrlKey || aEvent.shiftKey;
785 #endif
786  if (aEvent.button == 2 || (aEvent.button == 0 && !modifKey))
787  return;
788 
789  var target = aEvent.originalTarget;
790  // If this event bubbled up from a menu or menuitem, close the menus.
791  // Do this before opening tabs, to avoid hiding the open tabs confirm-dialog.
792  if (target.localName == "menu" || target.localName == "menuitem") {
793  for (node = target.parentNode; node; node = node.parentNode) {
794  if (node.localName == "menupopup")
795  node.hidePopup();
796  else if (node.localName != "menu")
797  break;
798  }
799  }
800 
801  if (target.node && PlacesUtils.nodeIsContainer(target.node)) {
802  // Don't open the root folder in tabs when the empty area on the toolbar
803  // is middle-clicked or when a non-bookmark item except for Open in Tabs)
804  // in a bookmarks menupopup is middle-clicked.
805  if (target.localName == "menu" || target.localName == "toolbarbutton")
806  PlacesUIUtils.openContainerNodeInTabs(target.node, aEvent);
807  }
808  else if (aEvent.button == 1) {
809  // left-clicks with modifier are already served by onCommand
810  this.onCommand(aEvent);
811  }
812  },
813 
821  onCommand: function BM_onCommand(aEvent) {
822  var target = aEvent.originalTarget;
823  if (target.node)
824  PlacesUIUtils.openNodeWithEvent(target.node, aEvent);
825  },
826 
834  onPopupShowing: function BM_onPopupShowing(event) {
835  var target = event.originalTarget;
836  if (!target.hasAttribute("placespopup"))
837  return;
838 
839  // Check if the popup contains at least 2 menuitems with places nodes
840  var numNodes = 0;
841  var hasMultipleURIs = false;
842  var currentChild = target.firstChild;
843  while (currentChild) {
844  if (currentChild.localName == "menuitem" && currentChild.node) {
845  if (++numNodes == 2) {
846  hasMultipleURIs = true;
847  break;
848  }
849  }
850  currentChild = currentChild.nextSibling;
851  }
852 
853  var itemId = target._resultNode.itemId;
854  var siteURIString = "";
855  if (itemId != -1 && PlacesUtils.itemIsLivemark(itemId)) {
856  var siteURI = PlacesUtils.livemarks.getSiteURI(itemId);
857  if (siteURI)
858  siteURIString = siteURI.spec;
859  }
860 
861  if (!siteURIString && target._endOptOpenSiteURI) {
862  target.removeChild(target._endOptOpenSiteURI);
863  target._endOptOpenSiteURI = null;
864  }
865 
866  if (!hasMultipleURIs && target._endOptOpenAllInTabs) {
867  target.removeChild(target._endOptOpenAllInTabs);
868  target._endOptOpenAllInTabs = null;
869  }
870 
871  if (!(hasMultipleURIs || siteURIString)) {
872  // we don't have to show any option
873  if (target._endOptSeparator) {
874  target.removeChild(target._endOptSeparator);
875  target._endOptSeparator = null;
876  target._endMarker = -1;
877  }
878  return;
879  }
880 
881  if (!target._endOptSeparator) {
882  // create a separator before options
883  target._endOptSeparator = document.createElement("menuseparator");
884  target._endOptSeparator.className = "bookmarks-actions-menuseparator";
885  target._endMarker = target.childNodes.length;
886  target.appendChild(target._endOptSeparator);
887  }
888 
889  if (siteURIString && !target._endOptOpenSiteURI) {
890  // Add "Open (Feed Name)" menuitem if it's a livemark with a siteURI
891  target._endOptOpenSiteURI = document.createElement("menuitem");
892  target._endOptOpenSiteURI.className = "openlivemarksite-menuitem";
893  target._endOptOpenSiteURI.setAttribute("siteURI", siteURIString);
894  target._endOptOpenSiteURI.setAttribute("oncommand",
895  "openUILink(this.getAttribute('siteURI'), event);");
896  // If a user middle-clicks this item we serve the oncommand event
897  // We are using checkForMiddleClick because of Bug 246720
898  // Note: stopPropagation is needed to avoid serving middle-click
899  // with BT_onClick that would open all items in tabs
900  target._endOptOpenSiteURI.setAttribute("onclick",
901  "checkForMiddleClick(this, event); event.stopPropagation();");
902  target._endOptOpenSiteURI.setAttribute("label",
903  PlacesUIUtils.getFormattedString("menuOpenLivemarkOrigin.label",
904  [target.parentNode.getAttribute("label")]));
905  target.appendChild(target._endOptOpenSiteURI);
906  }
907 
908  if (hasMultipleURIs && !target._endOptOpenAllInTabs) {
909  // Add the "Open All in Tabs" menuitem if there are
910  // at least two menuitems with places result nodes.
911  target._endOptOpenAllInTabs = document.createElement("menuitem");
912  target._endOptOpenAllInTabs.className = "openintabs-menuitem";
913  target._endOptOpenAllInTabs.setAttribute("oncommand",
914  "PlacesUIUtils.openContainerNodeInTabs(this.parentNode._resultNode, event);");
915  target._endOptOpenAllInTabs.setAttribute("onclick",
916  "checkForMiddleClick(this, event); event.stopPropagation();");
917  target._endOptOpenAllInTabs.setAttribute("label",
918  gNavigatorBundle.getString("menuOpenAllInTabs.label"));
919  target.appendChild(target._endOptOpenAllInTabs);
920  }
921  },
922 
923  fillInBHTooltip: function(aDocument, aEvent) {
924  var node;
925  var cropped = false;
926 
927  if (aDocument.tooltipNode.localName == "treechildren") {
928  var tree = aDocument.tooltipNode.parentNode;
929  var row = {}, column = {};
930  var tbo = tree.treeBoxObject;
931  tbo.getCellAt(aEvent.clientX, aEvent.clientY, row, column, {});
932  if (row.value == -1)
933  return false;
934  node = tree.view.nodeForTreeIndex(row.value);
935  cropped = tbo.isCellCropped(row.value, column.value);
936  }
937  else
938  node = aDocument.tooltipNode.node;
939 
940  if (!node)
941  return false;
942 
943  var title = node.title;
944  var url;
945 
946  // Show URL only for URI-type nodes.
947  if (PlacesUtils.nodeIsURI(node))
948  url = node.uri;
949 
950  // Show tooltip for containers only if their title is cropped.
951  if (!cropped && !url)
952  return false;
953 
954  var tooltipTitle = aDocument.getElementById("bhtTitleText");
955  tooltipTitle.hidden = (!title || (title == url));
956  if (!tooltipTitle.hidden)
957  tooltipTitle.textContent = title;
958 
959  var tooltipUrl = aDocument.getElementById("bhtUrlText");
960  tooltipUrl.hidden = !url;
961  if (!tooltipUrl.hidden)
962  tooltipUrl.value = url;
963 
964  // Show tooltip.
965  return true;
966  }
967 };
968 
979  onDragOver: function BMDH_onDragOver(event, flavor, session) {
980  if (!this.canDrop(event, session))
981  event.dataTransfer.effectAllowed = "none";
982  },
983 
989  getSupportedFlavours: function BMDH_getSupportedFlavours() {
990  var view = document.getElementById("bookmarksMenuPopup");
991  return view.getSupportedFlavours();
992  },
993 
1003  canDrop: function BMDH_canDrop(event, session) {
1004  PlacesControllerDragHelper.currentDataTransfer = event.dataTransfer;
1005 
1006  var ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId, -1);
1007  return ip && PlacesControllerDragHelper.canDrop(ip);
1008  },
1009 
1019  onDrop: function BMDH_onDrop(event, data, session) {
1020  PlacesControllerDragHelper.currentDataTransfer = event.dataTransfer;
1021 
1022  // Put the item at the end of bookmark menu
1023  var ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId, -1,
1024  Ci.nsITreeView.DROP_ON);
1025  PlacesControllerDragHelper.onDrop(ip);
1026  },
1027 
1033  onDragExit: function BMDH_onDragExit(event, session) {
1034  PlacesControllerDragHelper.currentDataTransfer = null;
1035  }
1036 };
1037 
1043  _springLoadDelay: 350, // milliseconds
1044 
1048  _timers: { },
1049 
1055  onBookmarksMenuDragEnter: function PMDC_onDragEnter(event) {
1056  if ("loadTime" in this._timers)
1057  return;
1058 
1059  this._setDragTimer("loadTime", this._openBookmarksMenu,
1060  this._springLoadDelay, [event]);
1061  },
1062 
1074  _setDragTimer: function PMDC__setDragTimer(id, callback, delay, args) {
1075  if (!this._dragSupported)
1076  return;
1077 
1078  // Cancel this timer if it's already running.
1079  if (id in this._timers)
1080  this._timers[id].cancel();
1081 
1086  function Callback(object, method, args) {
1087  this._method = method;
1088  this._args = args;
1089  this._object = object;
1090  }
1091  Callback.prototype = {
1092  notify: function C_notify(timer) {
1093  this._method.apply(this._object, this._args);
1094  }
1095  };
1096 
1097  var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
1098  timer.initWithCallback(new Callback(this, callback, args), delay,
1099  timer.TYPE_ONE_SHOT);
1100  this._timers[id] = timer;
1101  },
1102 
1108  _isContainer: function PMDC__isContainer(node) {
1109  return node.localName == "menu" ||
1110  (node.localName == "toolbarbutton" &&
1111  node.getAttribute("type") == "menu");
1112  },
1113 
1121  _openBookmarksMenu: function PMDC__openBookmarksMenu(event) {
1122  if ("loadTime" in this._timers)
1123  delete this._timers.loadTime;
1124  if (event.target.id == "bookmarksMenu") {
1125  // If this is the bookmarks menu, tell its menupopup child to show.
1126  event.target.lastChild.setAttribute("autoopened", "true");
1127  event.target.lastChild.showPopup(event.target.lastChild);
1128  }
1129  },
1130 
1131  // Whether or not drag and drop to menus is supported on this platform
1132  // Dragging in menus is disabled on OS X due to various repainting issues.
1133 #ifdef XP_MACOSX
1134  _dragSupported: false
1135 #else
1136  _dragSupported: true
1137 #endif
1138 };
1139 
1141  init: function PSB_init() {
1142  try {
1143  PlacesUtils.bookmarks.addObserver(this, false);
1144  } catch(ex) {
1145  Components.utils.reportError("PlacesStarButton.init(): error adding bookmark observer: " + ex);
1146  }
1147  },
1148 
1149  uninit: function PSB_uninit() {
1150  PlacesUtils.bookmarks.removeObserver(this);
1151  },
1152 
1153  QueryInterface: function PSB_QueryInterface(aIID) {
1154  if (aIID.equals(Ci.nsINavBookmarkObserver) ||
1155  aIID.equals(Ci.nsISupports))
1156  return this;
1157 
1158  throw Cr.NS_NOINTERFACE;
1159  },
1160 
1161  _starred: false,
1162  _batching: false,
1163 
1164  updateState: function PSB_updateState() {
1165  var starIcon = document.getElementById("star-button");
1166  if (!starIcon)
1167  return;
1168 
1169  var uri = getBrowser().currentURI;
1170  this._starred = uri && (PlacesUtils.getMostRecentBookmarkForURI(uri) != -1 ||
1171  PlacesUtils.getMostRecentFolderForFeedURI(uri) != -1);
1172  if (this._starred) {
1173  starIcon.setAttribute("starred", "true");
1174  starIcon.setAttribute("tooltiptext", gNavigatorBundle.getString("starButtonOn.tooltip"));
1175  }
1176  else {
1177  starIcon.removeAttribute("starred");
1178  starIcon.setAttribute("tooltiptext", gNavigatorBundle.getString("starButtonOff.tooltip"));
1179  }
1180  },
1181 
1182  onClick: function PSB_onClick(aEvent) {
1183  if (aEvent.button == 0)
1184  PlacesCommandHook.bookmarkCurrentPage(this._starred);
1185 
1186  // don't bubble to the textbox so that the address won't be selected
1187  aEvent.stopPropagation();
1188  },
1189 
1190  // nsINavBookmarkObserver
1191  onBeginUpdateBatch: function PSB_onBeginUpdateBatch() {
1192  this._batching = true;
1193  },
1194 
1195  onEndUpdateBatch: function PSB_onEndUpdateBatch() {
1196  this.updateState();
1197  this._batching = false;
1198  },
1199 
1200  onItemAdded: function PSB_onItemAdded(aItemId, aFolder, aIndex, aItemType) {
1201  if (!this._batching && !this._starred)
1202  this.updateState();
1203  },
1204 
1205  onBeforeItemRemoved: function() {},
1206 
1207  onItemRemoved: function PSB_onItemRemoved(aItemId, aFolder, aIndex,
1208  aItemType) {
1209  if (!this._batching)
1210  this.updateState();
1211  },
1212 
1213  onItemChanged: function PSB_onItemChanged(aItemId, aProperty,
1214  aIsAnnotationProperty, aNewValue,
1215  aLastModified, aItemType) {
1216  if (!this._batching && aProperty == "uri")
1217  this.updateState();
1218  },
1219 
1220  onItemVisited: function() { },
1221  onItemMoved: function() { }
1222 };
var PlacesUIUtils
Definition: utils.js:85
var PlacesMenuDNDController
var BookmarksEventHandler
const Cc
var args
Definition: alert.js:8
var gEditItemOverlay
#define DESCRIPTION_ANNO
var BookmarksMenuDropHandler
menuItem id
Definition: FeedWriter.js:971
var PlacesStarButton
onPageChanged onEndUpdateBatch
Definition: FeedWriter.js:1395
function doc() browser.contentDocument
dndDefaultHandler_module onDragOver
var event
sbOSDControlService prototype QueryInterface
var header
Definition: FeedWriter.js:953
function getBrowser() gBrowser
getService(Ci.sbIFaceplateManager)
let window
var HistoryMenu
var feedTitle
Definition: FeedWriter.js:1313
var strings
Definition: Info.js:46
TimerLoop prototype notify
_window init
Definition: FeedWriter.js:1144
this _contentSandbox label
Definition: FeedWriter.js:814
_collectFormDataForFrame aDocument
function makeURI(aURLSpec, aCharset)
Definition: FeedWriter.js:71
function isElementVisible(aElement)
document _overlayLoading
Definition: mainOverlay.js:88
var feedSubtitle
Definition: FeedWriter.js:1314
grep callback
onPageChanged onBeginUpdateBatch
Definition: FeedWriter.js:1395
return null
Definition: FeedWriter.js:1143
let node
return!aWindow arguments!aWindow arguments[0]
var gPrefService
Definition: overlay.js:34
function url(spec)
var uri
Definition: FeedWriter.js:1135
_updateTextAndScrollDataForTab aBrowser
countRef value
Definition: FeedWriter.js:1423
oState session
const Cr
var PlacesControllerDragHelper
Definition: controller.js:1333
if(DEBUG_DATAREMOTES)
const Ci
var JSON
var gNavigatorBundle
var hidden
observe data
Definition: FeedWriter.js:1329
function InsertionPoint(aItemId, aIndex, aOrientation, aIsTag, aDropNearItemId)
Definition: controller.js:84
function uninit(aEvent)
Definition: aboutDialog.js:89
_getSelectedPageStyle s i
var brandBundle
Definition: xpInstallHat.js:37
_updateTextAndScrollDataForFrame aData
sbDeviceFirmwareAutoCheckForUpdate prototype observe
var StarUI
converter charset
function undoCloseTab(aIndex)
Definition: browser.js:6350