Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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 | }; |