browser/metro/base/content/startui/HistoryView.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 function HistoryView(aSet, aFilterUnpinned) {
     8   View.call(this, aSet);
    10   this._inBatch = 0;
    12   // View monitors this for maximum tile display counts
    13   this.tilePrefName = "browser.display.startUI.history.maxresults";
    14   this.showing = this.maxTiles > 0;
    16   this._filterUnpinned = aFilterUnpinned;
    17   this._historyService = PlacesUtils.history;
    18   this._navHistoryService = gHistSvc;
    20   this._pinHelper = new ItemPinHelper("metro.history.unpinned");
    21   this._historyService.addObserver(this, false);
    22   StartUI.chromeWin.addEventListener('MozAppbarDismissing', this, false);
    23   StartUI.chromeWin.addEventListener('HistoryNeedsRefresh', this, false);
    24   window.addEventListener("TabClose", this, true);
    25 }
    27 HistoryView.prototype = Util.extend(Object.create(View.prototype), {
    28   _set: null,
    29   _toRemove: null,
    31   // For View's showing property
    32   get vbox() {
    33     return document.getElementById("start-history");
    34   },
    36   destruct: function destruct() {
    37     this._historyService.removeObserver(this);
    38     if (StartUI.chromeWin) {
    39       StartUI.chromeWin.removeEventListener('MozAppbarDismissing', this, false);
    40       StartUI.chromeWin.removeEventListener('HistoryNeedsRefresh', this, false);
    41     }
    42     View.prototype.destruct.call(this);
    43   },
    45   refreshView: function () {
    46     this.onClearHistory();
    47     this.populateGrid();
    48   },
    50   handleItemClick: function tabview_handleItemClick(aItem) {
    51     let url = aItem.getAttribute("value");
    52     StartUI.goToURI(url);
    53   },
    55   populateGrid: function populateGrid(aRefresh) {
    56     this._inBatch++; // always batch up grid updates to avoid redundant arrangeItems calls
    57     let query = this._navHistoryService.getNewQuery();
    58     let options = this._navHistoryService.getNewQueryOptions();
    59     options.excludeQueries = true;
    60     options.queryType = options.QUERY_TYPE_HISTORY;
    61     options.resultType = options.RESULTS_AS_URI;
    62     options.sortingMode = options.SORT_BY_DATE_DESCENDING;
    64     let limit = this.maxTiles;
    65     let result = this._navHistoryService.executeQuery(query, options);
    66     let rootNode = result.root;
    67     rootNode.containerOpen = true;
    68     let childCount = rootNode.childCount;
    70     for (let i = 0, addedCount = 0; i < childCount && addedCount < limit; i++) {
    71       let node = rootNode.getChild(i);
    72       let uri = node.uri;
    73       let title = (node.title && node.title.length) ? node.title : uri;
    75       // If item is marked for deletion, skip it.
    76       if (this._toRemove && this._toRemove.indexOf(uri) !== -1)
    77         continue;
    79       let items = this._set.getItemsByUrl(uri);
    81       // Item has been unpinned, skip if filterUnpinned set.
    82       if (this._filterUnpinned && !this._pinHelper.isPinned(uri)) {
    83         if (items.length > 0)
    84           this.removeHistory(uri);
    86         continue;
    87       }
    89       if (!aRefresh || items.length === 0) {
    90         // If we're not refreshing or the item is not in the grid, add it.
    91         this.addItemToSet(uri, title, node.icon, addedCount);
    92       } else if (aRefresh && items.length > 0) {
    93         // Update context action in case it changed in another view.
    94         for (let item of items) {
    95           this._setContextActions(item);
    96         }
    97       }
    99       addedCount++;
   100     }
   102     // Remove extra items in case a refresh added more than the limit.
   103     // This can happen when undoing a delete.
   104     if (aRefresh) {
   105       while (this._set.itemCount > limit)
   106         this._set.removeItemAt(this._set.itemCount - 1);
   107     }
   109     rootNode.containerOpen = false;
   110     this._set.arrangeItems();
   111     if (this._inBatch > 0)
   112       this._inBatch--;
   113   },
   115   addItemToSet: function addItemToSet(aURI, aTitle, aIcon, aPos) {
   116     let item = this._set.insertItemAt(aPos || 0, aTitle, aURI, this._inBatch);
   117     this._setContextActions(item);
   118     this._updateFavicon(item, aURI);
   119   },
   121   _setContextActions: function bv__setContextActions(aItem) {
   122     let uri = aItem.getAttribute("value");
   123     aItem.setAttribute("data-contextactions", "delete," + (this._pinHelper.isPinned(uri) ? "hide" : "pin"));
   124     if ("refresh" in aItem) aItem.refresh();
   125   },
   127   _sendNeedsRefresh: function bv__sendNeedsRefresh(){
   128     // Event sent when all views need to refresh.
   129     let event = document.createEvent("Events");
   130     event.initEvent("HistoryNeedsRefresh", true, false);
   131     window.dispatchEvent(event);
   132   },
   134   removeHistory: function (aUri) {
   135     let items = this._set.getItemsByUrl(aUri);
   136     for (let item of items)
   137       this._set.removeItem(item, true);
   138     if (!this._inBatch)
   139       this._set.arrangeItems();
   140   },
   142   doActionOnSelectedTiles: function bv_doActionOnSelectedTiles(aActionName, aEvent) {
   143     let tileGroup = this._set;
   144     let selectedTiles = tileGroup.selectedItems;
   146     // just arrange the grid once at the end of any action handling
   147     this._inBatch = true;
   149     switch (aActionName){
   150       case "delete":
   151         Array.forEach(selectedTiles, function(aNode) {
   152           if (!this._toRemove) {
   153             this._toRemove = [];
   154           }
   156           let uri = aNode.getAttribute("value");
   158           this._toRemove.push(uri);
   159           this.removeHistory(uri);
   160         }, this);
   162         // stop the appbar from dismissing
   163         aEvent.preventDefault();
   165         // at next tick, re-populate the context appbar.
   166         setTimeout(function(){
   167           // fire a MozContextActionsChange event to update the context appbar
   168           let event = document.createEvent("Events");
   169           // we need the restore button to show (the tile node will go away though)
   170           event.actions = ["restore"];
   171           event.initEvent("MozContextActionsChange", true, false);
   172           tileGroup.dispatchEvent(event);
   173         }, 0);
   174         break;
   176       case "restore":
   177         // clear toRemove and let _sendNeedsRefresh update the items.
   178         this._toRemove = null;
   179         break;
   181       case "unpin":
   182         Array.forEach(selectedTiles, function(aNode) {
   183           let uri = aNode.getAttribute("value");
   185           if (this._filterUnpinned)
   186             this.removeHistory(uri);
   188           this._pinHelper.setUnpinned(uri);
   189         }, this);
   190         break;
   192       case "pin":
   193         Array.forEach(selectedTiles, function(aNode) {
   194           let uri = aNode.getAttribute("value");
   196           this._pinHelper.setPinned(uri);
   197         }, this);
   198         break;
   200       default:
   201         this._inBatch = false;
   202         return;
   203     }
   205     this._inBatch = false;
   206     // Send refresh event so all view are in sync.
   207     this._sendNeedsRefresh();
   208   },
   210   handleEvent: function bv_handleEvent(aEvent) {
   211     switch (aEvent.type){
   212       case "MozAppbarDismissing":
   213         // If undo wasn't pressed, time to do definitive actions.
   214         if (this._toRemove) {
   215           for (let uri of this._toRemove) {
   216             this._historyService.removePage(NetUtil.newURI(uri));
   217           }
   219           // Clear context app bar
   220           let event = document.createEvent("Events");
   221           event.actions = [];
   222           event.initEvent("MozContextActionsChange", true, false);
   223           this._set.dispatchEvent(event);
   225           this._toRemove = null;
   226         }
   227         break;
   229       case "HistoryNeedsRefresh":
   230         this.populateGrid(true);
   231         break;
   233       case "TabClose":
   234         // Flush any pending actions - appbar will call us back
   235         // before this returns with 'MozAppbarDismissing' above.
   236         StartUI.chromeWin.ContextUI.dismissContextAppbar();
   237       break;
   238     }
   239   },
   241   // nsINavHistoryObserver & helpers
   243   onBeginUpdateBatch: function() {
   244     // Avoid heavy grid redraws while a batch is in process
   245     this._inBatch++;
   246   },
   248   onEndUpdateBatch: function() {
   249     this.populateGrid(true);
   250     if (this._inBatch > 0) {
   251       this._inBatch--;
   252       this._set.arrangeItems();
   253     }
   254   },
   256   onVisit: function(aURI, aVisitID, aTime, aSessionID,
   257                     aReferringID, aTransitionType) {
   258     if (!this._inBatch) {
   259       this.populateGrid(true);
   260     }
   261   },
   263   onTitleChanged: function(aURI, aPageTitle) {
   264     let changedItems = this._set.getItemsByUrl(aURI.spec);
   265     for (let item of changedItems) {
   266       item.setAttribute("label", aPageTitle);
   267     }
   268   },
   270   onDeleteURI: function(aURI) {
   271     this.removeHistory(aURI.spec);
   272   },
   274   onClearHistory: function() {
   275     if ('clearAll' in this._set)
   276       this._set.clearAll();
   277   },
   279   onPageChanged: function(aURI, aWhat, aValue) {
   280     if (aWhat ==  Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
   281       let changedItems = this._set.getItemsByUrl(aURI.spec);
   282       for (let item of changedItems) {
   283         let currIcon = item.getAttribute("iconURI");
   284         if (currIcon != aValue) {
   285           item.setAttribute("iconURI", aValue);
   286           if ("refresh" in item)
   287             item.refresh();
   288         }
   289       }
   290     }
   291   },
   293   onDeleteVisits: function (aURI, aVisitTime, aGUID, aReason, aTransitionType) {
   294     if ((aReason ==  Ci.nsINavHistoryObserver.REASON_DELETED) && !this._inBatch) {
   295       this.populateGrid(true);
   296     }
   297   },
   299   QueryInterface: function(iid) {
   300     if (iid.equals(Components.interfaces.nsINavHistoryObserver) ||
   301         iid.equals(Components.interfaces.nsISupports)) {
   302       return this;
   303     }
   304     throw Cr.NS_ERROR_NO_INTERFACE;
   305   }
   306 });
   308 let HistoryStartView = {
   309   _view: null,
   310   get _grid() { return document.getElementById("start-history-grid"); },
   312   init: function init() {
   313     this._view = new HistoryView(this._grid, true);
   314     this._view.populateGrid();
   315     this._grid.removeAttribute("fade");
   316   },
   318   uninit: function uninit() {
   319     if (this._view) {
   320       this._view.destruct();
   321     }
   322   }
   323 };

mercurial