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

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

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

mercurial