1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/metro/base/content/startui/HistoryView.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,323 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +'use strict'; 1.9 + 1.10 +function HistoryView(aSet, aFilterUnpinned) { 1.11 + View.call(this, aSet); 1.12 + 1.13 + this._inBatch = 0; 1.14 + 1.15 + // View monitors this for maximum tile display counts 1.16 + this.tilePrefName = "browser.display.startUI.history.maxresults"; 1.17 + this.showing = this.maxTiles > 0; 1.18 + 1.19 + this._filterUnpinned = aFilterUnpinned; 1.20 + this._historyService = PlacesUtils.history; 1.21 + this._navHistoryService = gHistSvc; 1.22 + 1.23 + this._pinHelper = new ItemPinHelper("metro.history.unpinned"); 1.24 + this._historyService.addObserver(this, false); 1.25 + StartUI.chromeWin.addEventListener('MozAppbarDismissing', this, false); 1.26 + StartUI.chromeWin.addEventListener('HistoryNeedsRefresh', this, false); 1.27 + window.addEventListener("TabClose", this, true); 1.28 +} 1.29 + 1.30 +HistoryView.prototype = Util.extend(Object.create(View.prototype), { 1.31 + _set: null, 1.32 + _toRemove: null, 1.33 + 1.34 + // For View's showing property 1.35 + get vbox() { 1.36 + return document.getElementById("start-history"); 1.37 + }, 1.38 + 1.39 + destruct: function destruct() { 1.40 + this._historyService.removeObserver(this); 1.41 + if (StartUI.chromeWin) { 1.42 + StartUI.chromeWin.removeEventListener('MozAppbarDismissing', this, false); 1.43 + StartUI.chromeWin.removeEventListener('HistoryNeedsRefresh', this, false); 1.44 + } 1.45 + View.prototype.destruct.call(this); 1.46 + }, 1.47 + 1.48 + refreshView: function () { 1.49 + this.onClearHistory(); 1.50 + this.populateGrid(); 1.51 + }, 1.52 + 1.53 + handleItemClick: function tabview_handleItemClick(aItem) { 1.54 + let url = aItem.getAttribute("value"); 1.55 + StartUI.goToURI(url); 1.56 + }, 1.57 + 1.58 + populateGrid: function populateGrid(aRefresh) { 1.59 + this._inBatch++; // always batch up grid updates to avoid redundant arrangeItems calls 1.60 + let query = this._navHistoryService.getNewQuery(); 1.61 + let options = this._navHistoryService.getNewQueryOptions(); 1.62 + options.excludeQueries = true; 1.63 + options.queryType = options.QUERY_TYPE_HISTORY; 1.64 + options.resultType = options.RESULTS_AS_URI; 1.65 + options.sortingMode = options.SORT_BY_DATE_DESCENDING; 1.66 + 1.67 + let limit = this.maxTiles; 1.68 + let result = this._navHistoryService.executeQuery(query, options); 1.69 + let rootNode = result.root; 1.70 + rootNode.containerOpen = true; 1.71 + let childCount = rootNode.childCount; 1.72 + 1.73 + for (let i = 0, addedCount = 0; i < childCount && addedCount < limit; i++) { 1.74 + let node = rootNode.getChild(i); 1.75 + let uri = node.uri; 1.76 + let title = (node.title && node.title.length) ? node.title : uri; 1.77 + 1.78 + // If item is marked for deletion, skip it. 1.79 + if (this._toRemove && this._toRemove.indexOf(uri) !== -1) 1.80 + continue; 1.81 + 1.82 + let items = this._set.getItemsByUrl(uri); 1.83 + 1.84 + // Item has been unpinned, skip if filterUnpinned set. 1.85 + if (this._filterUnpinned && !this._pinHelper.isPinned(uri)) { 1.86 + if (items.length > 0) 1.87 + this.removeHistory(uri); 1.88 + 1.89 + continue; 1.90 + } 1.91 + 1.92 + if (!aRefresh || items.length === 0) { 1.93 + // If we're not refreshing or the item is not in the grid, add it. 1.94 + this.addItemToSet(uri, title, node.icon, addedCount); 1.95 + } else if (aRefresh && items.length > 0) { 1.96 + // Update context action in case it changed in another view. 1.97 + for (let item of items) { 1.98 + this._setContextActions(item); 1.99 + } 1.100 + } 1.101 + 1.102 + addedCount++; 1.103 + } 1.104 + 1.105 + // Remove extra items in case a refresh added more than the limit. 1.106 + // This can happen when undoing a delete. 1.107 + if (aRefresh) { 1.108 + while (this._set.itemCount > limit) 1.109 + this._set.removeItemAt(this._set.itemCount - 1); 1.110 + } 1.111 + 1.112 + rootNode.containerOpen = false; 1.113 + this._set.arrangeItems(); 1.114 + if (this._inBatch > 0) 1.115 + this._inBatch--; 1.116 + }, 1.117 + 1.118 + addItemToSet: function addItemToSet(aURI, aTitle, aIcon, aPos) { 1.119 + let item = this._set.insertItemAt(aPos || 0, aTitle, aURI, this._inBatch); 1.120 + this._setContextActions(item); 1.121 + this._updateFavicon(item, aURI); 1.122 + }, 1.123 + 1.124 + _setContextActions: function bv__setContextActions(aItem) { 1.125 + let uri = aItem.getAttribute("value"); 1.126 + aItem.setAttribute("data-contextactions", "delete," + (this._pinHelper.isPinned(uri) ? "hide" : "pin")); 1.127 + if ("refresh" in aItem) aItem.refresh(); 1.128 + }, 1.129 + 1.130 + _sendNeedsRefresh: function bv__sendNeedsRefresh(){ 1.131 + // Event sent when all views need to refresh. 1.132 + let event = document.createEvent("Events"); 1.133 + event.initEvent("HistoryNeedsRefresh", true, false); 1.134 + window.dispatchEvent(event); 1.135 + }, 1.136 + 1.137 + removeHistory: function (aUri) { 1.138 + let items = this._set.getItemsByUrl(aUri); 1.139 + for (let item of items) 1.140 + this._set.removeItem(item, true); 1.141 + if (!this._inBatch) 1.142 + this._set.arrangeItems(); 1.143 + }, 1.144 + 1.145 + doActionOnSelectedTiles: function bv_doActionOnSelectedTiles(aActionName, aEvent) { 1.146 + let tileGroup = this._set; 1.147 + let selectedTiles = tileGroup.selectedItems; 1.148 + 1.149 + // just arrange the grid once at the end of any action handling 1.150 + this._inBatch = true; 1.151 + 1.152 + switch (aActionName){ 1.153 + case "delete": 1.154 + Array.forEach(selectedTiles, function(aNode) { 1.155 + if (!this._toRemove) { 1.156 + this._toRemove = []; 1.157 + } 1.158 + 1.159 + let uri = aNode.getAttribute("value"); 1.160 + 1.161 + this._toRemove.push(uri); 1.162 + this.removeHistory(uri); 1.163 + }, this); 1.164 + 1.165 + // stop the appbar from dismissing 1.166 + aEvent.preventDefault(); 1.167 + 1.168 + // at next tick, re-populate the context appbar. 1.169 + setTimeout(function(){ 1.170 + // fire a MozContextActionsChange event to update the context appbar 1.171 + let event = document.createEvent("Events"); 1.172 + // we need the restore button to show (the tile node will go away though) 1.173 + event.actions = ["restore"]; 1.174 + event.initEvent("MozContextActionsChange", true, false); 1.175 + tileGroup.dispatchEvent(event); 1.176 + }, 0); 1.177 + break; 1.178 + 1.179 + case "restore": 1.180 + // clear toRemove and let _sendNeedsRefresh update the items. 1.181 + this._toRemove = null; 1.182 + break; 1.183 + 1.184 + case "unpin": 1.185 + Array.forEach(selectedTiles, function(aNode) { 1.186 + let uri = aNode.getAttribute("value"); 1.187 + 1.188 + if (this._filterUnpinned) 1.189 + this.removeHistory(uri); 1.190 + 1.191 + this._pinHelper.setUnpinned(uri); 1.192 + }, this); 1.193 + break; 1.194 + 1.195 + case "pin": 1.196 + Array.forEach(selectedTiles, function(aNode) { 1.197 + let uri = aNode.getAttribute("value"); 1.198 + 1.199 + this._pinHelper.setPinned(uri); 1.200 + }, this); 1.201 + break; 1.202 + 1.203 + default: 1.204 + this._inBatch = false; 1.205 + return; 1.206 + } 1.207 + 1.208 + this._inBatch = false; 1.209 + // Send refresh event so all view are in sync. 1.210 + this._sendNeedsRefresh(); 1.211 + }, 1.212 + 1.213 + handleEvent: function bv_handleEvent(aEvent) { 1.214 + switch (aEvent.type){ 1.215 + case "MozAppbarDismissing": 1.216 + // If undo wasn't pressed, time to do definitive actions. 1.217 + if (this._toRemove) { 1.218 + for (let uri of this._toRemove) { 1.219 + this._historyService.removePage(NetUtil.newURI(uri)); 1.220 + } 1.221 + 1.222 + // Clear context app bar 1.223 + let event = document.createEvent("Events"); 1.224 + event.actions = []; 1.225 + event.initEvent("MozContextActionsChange", true, false); 1.226 + this._set.dispatchEvent(event); 1.227 + 1.228 + this._toRemove = null; 1.229 + } 1.230 + break; 1.231 + 1.232 + case "HistoryNeedsRefresh": 1.233 + this.populateGrid(true); 1.234 + break; 1.235 + 1.236 + case "TabClose": 1.237 + // Flush any pending actions - appbar will call us back 1.238 + // before this returns with 'MozAppbarDismissing' above. 1.239 + StartUI.chromeWin.ContextUI.dismissContextAppbar(); 1.240 + break; 1.241 + } 1.242 + }, 1.243 + 1.244 + // nsINavHistoryObserver & helpers 1.245 + 1.246 + onBeginUpdateBatch: function() { 1.247 + // Avoid heavy grid redraws while a batch is in process 1.248 + this._inBatch++; 1.249 + }, 1.250 + 1.251 + onEndUpdateBatch: function() { 1.252 + this.populateGrid(true); 1.253 + if (this._inBatch > 0) { 1.254 + this._inBatch--; 1.255 + this._set.arrangeItems(); 1.256 + } 1.257 + }, 1.258 + 1.259 + onVisit: function(aURI, aVisitID, aTime, aSessionID, 1.260 + aReferringID, aTransitionType) { 1.261 + if (!this._inBatch) { 1.262 + this.populateGrid(true); 1.263 + } 1.264 + }, 1.265 + 1.266 + onTitleChanged: function(aURI, aPageTitle) { 1.267 + let changedItems = this._set.getItemsByUrl(aURI.spec); 1.268 + for (let item of changedItems) { 1.269 + item.setAttribute("label", aPageTitle); 1.270 + } 1.271 + }, 1.272 + 1.273 + onDeleteURI: function(aURI) { 1.274 + this.removeHistory(aURI.spec); 1.275 + }, 1.276 + 1.277 + onClearHistory: function() { 1.278 + if ('clearAll' in this._set) 1.279 + this._set.clearAll(); 1.280 + }, 1.281 + 1.282 + onPageChanged: function(aURI, aWhat, aValue) { 1.283 + if (aWhat == Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) { 1.284 + let changedItems = this._set.getItemsByUrl(aURI.spec); 1.285 + for (let item of changedItems) { 1.286 + let currIcon = item.getAttribute("iconURI"); 1.287 + if (currIcon != aValue) { 1.288 + item.setAttribute("iconURI", aValue); 1.289 + if ("refresh" in item) 1.290 + item.refresh(); 1.291 + } 1.292 + } 1.293 + } 1.294 + }, 1.295 + 1.296 + onDeleteVisits: function (aURI, aVisitTime, aGUID, aReason, aTransitionType) { 1.297 + if ((aReason == Ci.nsINavHistoryObserver.REASON_DELETED) && !this._inBatch) { 1.298 + this.populateGrid(true); 1.299 + } 1.300 + }, 1.301 + 1.302 + QueryInterface: function(iid) { 1.303 + if (iid.equals(Components.interfaces.nsINavHistoryObserver) || 1.304 + iid.equals(Components.interfaces.nsISupports)) { 1.305 + return this; 1.306 + } 1.307 + throw Cr.NS_ERROR_NO_INTERFACE; 1.308 + } 1.309 +}); 1.310 + 1.311 +let HistoryStartView = { 1.312 + _view: null, 1.313 + get _grid() { return document.getElementById("start-history-grid"); }, 1.314 + 1.315 + init: function init() { 1.316 + this._view = new HistoryView(this._grid, true); 1.317 + this._view.populateGrid(); 1.318 + this._grid.removeAttribute("fade"); 1.319 + }, 1.320 + 1.321 + uninit: function uninit() { 1.322 + if (this._view) { 1.323 + this._view.destruct(); 1.324 + } 1.325 + } 1.326 +};