browser/metro/base/content/startui/BookmarksView.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 /**
     8  * Wraps a list/grid control implementing nsIDOMXULSelectControlElement and
     9  * fills it with the user's bookmarks.
    10  *
    11  * @param           aSet    Control implementing nsIDOMXULSelectControlElement.
    12  * @param           aRoot   Bookmark root to show in the view.
    13  */
    14 function BookmarksView(aSet, aRoot, aFilterUnpinned) {
    15   View.call(this, aSet);
    17   this._inBatch = false; // batch up grid updates to avoid redundant arrangeItems calls
    19   // View monitors this for maximum tile display counts
    20   this.tilePrefName = "browser.display.startUI.bookmarks.maxresults";
    21   this.showing = this.maxTiles > 0;
    23   this._filterUnpinned = aFilterUnpinned;
    24   this._bookmarkService = PlacesUtils.bookmarks;
    25   this._navHistoryService = gHistSvc;
    27   this._changes = new BookmarkChangeListener(this);
    28   this._pinHelper = new ItemPinHelper("metro.bookmarks.unpinned");
    29   this._bookmarkService.addObserver(this._changes, false);
    30   StartUI.chromeWin.addEventListener('MozAppbarDismissing', this, false);
    31   StartUI.chromeWin.addEventListener('BookmarksNeedsRefresh', this, false);
    32   window.addEventListener("TabClose", this, true);
    34   this.root = aRoot;
    35 }
    37 BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
    38   _set: null,
    39   _changes: null,
    40   _root: null,
    41   _sort: 0, // Natural bookmark order.
    42   _toRemove: null,
    44   // For View's showing property
    45   get vbox() {
    46     return document.getElementById("start-bookmarks");
    47   },
    49   get sort() {
    50     return this._sort;
    51   },
    53   set sort(aSort) {
    54     this._sort = aSort;
    55     this.clearBookmarks();
    56     this.getBookmarks();
    57   },
    59   get root() {
    60     return this._root;
    61   },
    63   set root(aRoot) {
    64     this._root = aRoot;
    65   },
    67   destruct: function bv_destruct() {
    68     this._bookmarkService.removeObserver(this._changes);
    69     if (StartUI.chromeWin) {
    70       StartUI.chromeWin.removeEventListener('MozAppbarDismissing', this, false);
    71       StartUI.chromeWin.removeEventListener('BookmarksNeedsRefresh', this, false);
    72     }
    73     View.prototype.destruct.call(this);
    74   },
    76   refreshView: function () {
    77     this.clearBookmarks();
    78     this.getBookmarks();
    79   },
    81   handleItemClick: function bv_handleItemClick(aItem) {
    82     let url = aItem.getAttribute("value");
    83     StartUI.goToURI(url);
    84   },
    86   _getItemForBookmarkId: function bv__getItemForBookmark(aBookmarkId) {
    87     return this._set.querySelector("richgriditem[anonid='" + aBookmarkId + "']");
    88   },
    90   _getBookmarkIdForItem: function bv__getBookmarkForItem(aItem) {
    91     return +aItem.getAttribute("anonid");
    92   },
    94   _updateItemWithAttrs: function dv__updateItemWithAttrs(anItem, aAttrs) {
    95     for (let name in aAttrs)
    96       anItem.setAttribute(name, aAttrs[name]);
    97   },
    99   getBookmarks: function bv_getBookmarks(aRefresh) {
   100     let options = this._navHistoryService.getNewQueryOptions();
   101     options.queryType = options.QUERY_TYPE_BOOKMARKS;
   102     options.excludeQueries = true; // Don't include "smart folders"
   103     options.sortingMode = this._sort;
   105     let limit = this.maxTiles;
   107     let query = this._navHistoryService.getNewQuery();
   108     query.setFolders([Bookmarks.metroRoot], 1);
   110     let result = this._navHistoryService.executeQuery(query, options);
   111     let rootNode = result.root;
   112     rootNode.containerOpen = true;
   113     let childCount = rootNode.childCount;
   115     this._inBatch = true; // batch up grid updates to avoid redundant arrangeItems calls
   117     for (let i = 0, addedCount = 0; i < childCount && addedCount < limit; i++) {
   118       let node = rootNode.getChild(i);
   120       // Ignore folders, separators, undefined item types, etc.
   121       if (node.type != node.RESULT_TYPE_URI)
   122         continue;
   124       // If item is marked for deletion, skip it.
   125       if (this._toRemove && this._toRemove.indexOf(node.itemId) !== -1)
   126         continue;
   128       let item = this._getItemForBookmarkId(node.itemId);
   130       // Item has been unpinned.
   131       if (this._filterUnpinned && !this._pinHelper.isPinned(node.itemId)) {
   132         if (item)
   133           this.removeBookmark(node.itemId);
   135         continue;
   136       }
   138       if (!aRefresh || !item) {
   139         // If we're not refreshing or the item is not in the grid, add it.
   140         this.addBookmark(node.itemId, addedCount);
   141       } else if (aRefresh && item) {
   142         // Update context action in case it changed in another view.
   143         this._setContextActions(item);
   144       }
   146       addedCount++;
   147     }
   149     // Remove extra items in case a refresh added more than the limit.
   150     // This can happen when undoing a delete.
   151     if (aRefresh) {
   152       while (this._set.itemCount > limit)
   153         this._set.removeItemAt(this._set.itemCount - 1, true);
   154     }
   155     this._set.arrangeItems();
   156     this._inBatch = false;
   157     rootNode.containerOpen = false;
   158   },
   160   inCurrentView: function bv_inCurrentView(aParentId, aItemId) {
   161     if (this._root && aParentId != this._root)
   162       return false;
   164     return !!this._getItemForBookmarkId(aItemId);
   165   },
   167   clearBookmarks: function bv_clearBookmarks() {
   168     if ('clearAll' in this._set)
   169       this._set.clearAll();
   170   },
   172   addBookmark: function bv_addBookmark(aBookmarkId, aPos) {
   173     let index = this._bookmarkService.getItemIndex(aBookmarkId);
   174     let uri = this._bookmarkService.getBookmarkURI(aBookmarkId);
   175     let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec;
   176     let item = this._set.insertItemAt(aPos || index, title, uri.spec, this._inBatch);
   177     item.setAttribute("anonid", aBookmarkId);
   178     this._setContextActions(item);
   179     this._updateFavicon(item, uri);
   180   },
   182   _setContextActions: function bv__setContextActions(aItem) {
   183     let itemId = this._getBookmarkIdForItem(aItem);
   184     aItem.setAttribute("data-contextactions", "delete," + (this._pinHelper.isPinned(itemId) ? "hide" : "pin"));
   185     if (aItem.refresh) aItem.refresh();
   186   },
   188   _sendNeedsRefresh: function bv__sendNeedsRefresh(){
   189     // Event sent when all view instances need to refresh.
   190     let event = document.createEvent("Events");
   191     event.initEvent("BookmarksNeedsRefresh", true, false);
   192     window.dispatchEvent(event);
   193   },
   195   updateBookmark: function bv_updateBookmark(aBookmarkId) {
   196     let item = this._getItemForBookmarkId(aBookmarkId);
   198     if (!item)
   199       return;
   201     let oldIndex = this._set.getIndexOfItem(item);
   202     let index = this._bookmarkService.getItemIndex(aBookmarkId);
   204     if (oldIndex != index) {
   205       this.removeBookmark(aBookmarkId);
   206       this.addBookmark(aBookmarkId);
   207       return;
   208     }
   210     let uri = this._bookmarkService.getBookmarkURI(aBookmarkId);
   211     let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec;
   213     item.setAttribute("anonid", aBookmarkId);
   214     item.setAttribute("value", uri.spec);
   215     item.setAttribute("label", title);
   217     this._updateFavicon(item, uri);
   218   },
   220   removeBookmark: function bv_removeBookmark(aBookmarkId) {
   221     let item = this._getItemForBookmarkId(aBookmarkId);
   222     let index = this._set.getIndexOfItem(item);
   223     this._set.removeItemAt(index, this._inBatch);
   224   },
   226   doActionOnSelectedTiles: function bv_doActionOnSelectedTiles(aActionName, aEvent) {
   227     let tileGroup = this._set;
   228     let selectedTiles = tileGroup.selectedItems;
   230     switch (aActionName){
   231       case "delete":
   232         Array.forEach(selectedTiles, function(aNode) {
   233           if (!this._toRemove) {
   234             this._toRemove = [];
   235           }
   237           let itemId = this._getBookmarkIdForItem(aNode);
   239           this._toRemove.push(itemId);
   240           this.removeBookmark(itemId);
   241         }, this);
   243         // stop the appbar from dismissing
   244         aEvent.preventDefault();
   246         // at next tick, re-populate the context appbar.
   247         setTimeout(function(){
   248           // fire a MozContextActionsChange event to update the context appbar
   249           let event = document.createEvent("Events");
   250           // we need the restore button to show (the tile node will go away though)
   251           event.actions = ["restore"];
   252           event.initEvent("MozContextActionsChange", true, false);
   253           tileGroup.dispatchEvent(event);
   254         }, 0);
   255         break;
   257       case "restore":
   258         // clear toRemove and let _sendNeedsRefresh update the items.
   259         this._toRemove = null;
   260         break;
   262       case "unpin":
   263         Array.forEach(selectedTiles, function(aNode) {
   264           let itemId = this._getBookmarkIdForItem(aNode);
   266           if (this._filterUnpinned)
   267             this.removeBookmark(itemId);
   269           this._pinHelper.setUnpinned(itemId);
   270         }, this);
   271         break;
   273       case "pin":
   274         Array.forEach(selectedTiles, function(aNode) {
   275           let itemId = this._getBookmarkIdForItem(aNode);
   277           this._pinHelper.setPinned(itemId);
   278         }, this);
   279         break;
   281       default:
   282         return;
   283     }
   285     // Send refresh event so all view are in sync.
   286     this._sendNeedsRefresh();
   287   },
   289   handleEvent: function bv_handleEvent(aEvent) {
   290     switch (aEvent.type){
   291       case "MozAppbarDismissing":
   292         // If undo wasn't pressed, time to do definitive actions.
   293         if (this._toRemove) {
   294           for (let bookmarkId of this._toRemove) {
   295             this._bookmarkService.removeItem(bookmarkId);
   296           }
   297           this._toRemove = null;
   298         }
   299         break;
   301       case "BookmarksNeedsRefresh":
   302         this.getBookmarks(true);
   303         break;
   305       case "TabClose":
   306         // Flush any pending actions - appbar will call us back
   307         // before this returns with 'MozAppbarDismissing' above.
   308         StartUI.chromeWin.ContextUI.dismissContextAppbar();
   309       break;
   310     }
   311   }
   312 });
   314 let BookmarksStartView = {
   315   _view: null,
   316   get _grid() { return document.getElementById("start-bookmarks-grid"); },
   318   init: function init() {
   319     this._view = new BookmarksView(this._grid, Bookmarks.metroRoot, true);
   320     this._view.getBookmarks();
   321     this._grid.removeAttribute("fade");
   322   },
   324   uninit: function uninit() {
   325     if (this._view) {
   326       this._view.destruct();
   327     }
   328   },
   329 };
   331 /**
   332  * Observes bookmark changes and keeps a linked BookmarksView updated.
   333  *
   334  * @param aView An instance of BookmarksView.
   335  */
   336 function BookmarkChangeListener(aView) {
   337   this._view = aView;
   338 }
   340 BookmarkChangeListener.prototype = {
   341   //////////////////////////////////////////////////////////////////////////////
   342   //// nsINavBookmarkObserver
   343   onBeginUpdateBatch: function () { },
   344   onEndUpdateBatch: function () { },
   346   onItemAdded: function bCL_onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded, aGUID, aParentGUID) {
   347     this._view.getBookmarks(true);
   348   },
   350   onItemChanged: function bCL_onItemChanged(aItemId, aProperty, aIsAnnotationProperty, aNewValue, aLastModified, aItemType, aParentId, aGUID, aParentGUID) {
   351     let itemIndex = PlacesUtils.bookmarks.getItemIndex(aItemId);
   352     if (!this._view.inCurrentView(aParentId, aItemId))
   353       return;
   355     this._view.updateBookmark(aItemId);
   356   },
   358   onItemMoved: function bCL_onItemMoved(aItemId, aOldParentId, aOldIndex, aNewParentId, aNewIndex, aItemType, aGUID, aOldParentGUID, aNewParentGUID) {
   359     let wasInView = this._view.inCurrentView(aOldParentId, aItemId);
   360     let nowInView = this._view.inCurrentView(aNewParentId, aItemId);
   362     if (!wasInView && nowInView)
   363       this._view.addBookmark(aItemId);
   365     if (wasInView && !nowInView)
   366       this._view.removeBookmark(aItemId);
   368     this._view.getBookmarks(true);
   369   },
   371   onBeforeItemRemoved: function (aItemId, aItemType, aParentId, aGUID, aParentGUID) { },
   372   onItemRemoved: function bCL_onItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID, aParentGUID) {
   373     if (!this._view.inCurrentView(aParentId, aItemId))
   374       return;
   376     this._view.removeBookmark(aItemId);
   377     this._view.getBookmarks(true);
   378   },
   380   onItemVisited: function(aItemId, aVisitId, aTime, aTransitionType, aURI, aParentId, aGUID, aParentGUID) { },
   382   //////////////////////////////////////////////////////////////////////////////
   383   //// nsISupports
   384   QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
   385 };

mercurial