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