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

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:eaec934394bf
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/. */
4
5 'use strict';
6
7 function HistoryView(aSet, aFilterUnpinned) {
8 View.call(this, aSet);
9
10 this._inBatch = 0;
11
12 // View monitors this for maximum tile display counts
13 this.tilePrefName = "browser.display.startUI.history.maxresults";
14 this.showing = this.maxTiles > 0;
15
16 this._filterUnpinned = aFilterUnpinned;
17 this._historyService = PlacesUtils.history;
18 this._navHistoryService = gHistSvc;
19
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 }
26
27 HistoryView.prototype = Util.extend(Object.create(View.prototype), {
28 _set: null,
29 _toRemove: null,
30
31 // For View's showing property
32 get vbox() {
33 return document.getElementById("start-history");
34 },
35
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 },
44
45 refreshView: function () {
46 this.onClearHistory();
47 this.populateGrid();
48 },
49
50 handleItemClick: function tabview_handleItemClick(aItem) {
51 let url = aItem.getAttribute("value");
52 StartUI.goToURI(url);
53 },
54
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;
63
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;
69
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;
74
75 // If item is marked for deletion, skip it.
76 if (this._toRemove && this._toRemove.indexOf(uri) !== -1)
77 continue;
78
79 let items = this._set.getItemsByUrl(uri);
80
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);
85
86 continue;
87 }
88
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 }
98
99 addedCount++;
100 }
101
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 }
108
109 rootNode.containerOpen = false;
110 this._set.arrangeItems();
111 if (this._inBatch > 0)
112 this._inBatch--;
113 },
114
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 },
120
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 },
126
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 },
133
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 },
141
142 doActionOnSelectedTiles: function bv_doActionOnSelectedTiles(aActionName, aEvent) {
143 let tileGroup = this._set;
144 let selectedTiles = tileGroup.selectedItems;
145
146 // just arrange the grid once at the end of any action handling
147 this._inBatch = true;
148
149 switch (aActionName){
150 case "delete":
151 Array.forEach(selectedTiles, function(aNode) {
152 if (!this._toRemove) {
153 this._toRemove = [];
154 }
155
156 let uri = aNode.getAttribute("value");
157
158 this._toRemove.push(uri);
159 this.removeHistory(uri);
160 }, this);
161
162 // stop the appbar from dismissing
163 aEvent.preventDefault();
164
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;
175
176 case "restore":
177 // clear toRemove and let _sendNeedsRefresh update the items.
178 this._toRemove = null;
179 break;
180
181 case "unpin":
182 Array.forEach(selectedTiles, function(aNode) {
183 let uri = aNode.getAttribute("value");
184
185 if (this._filterUnpinned)
186 this.removeHistory(uri);
187
188 this._pinHelper.setUnpinned(uri);
189 }, this);
190 break;
191
192 case "pin":
193 Array.forEach(selectedTiles, function(aNode) {
194 let uri = aNode.getAttribute("value");
195
196 this._pinHelper.setPinned(uri);
197 }, this);
198 break;
199
200 default:
201 this._inBatch = false;
202 return;
203 }
204
205 this._inBatch = false;
206 // Send refresh event so all view are in sync.
207 this._sendNeedsRefresh();
208 },
209
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 }
218
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);
224
225 this._toRemove = null;
226 }
227 break;
228
229 case "HistoryNeedsRefresh":
230 this.populateGrid(true);
231 break;
232
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 },
240
241 // nsINavHistoryObserver & helpers
242
243 onBeginUpdateBatch: function() {
244 // Avoid heavy grid redraws while a batch is in process
245 this._inBatch++;
246 },
247
248 onEndUpdateBatch: function() {
249 this.populateGrid(true);
250 if (this._inBatch > 0) {
251 this._inBatch--;
252 this._set.arrangeItems();
253 }
254 },
255
256 onVisit: function(aURI, aVisitID, aTime, aSessionID,
257 aReferringID, aTransitionType) {
258 if (!this._inBatch) {
259 this.populateGrid(true);
260 }
261 },
262
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 },
269
270 onDeleteURI: function(aURI) {
271 this.removeHistory(aURI.spec);
272 },
273
274 onClearHistory: function() {
275 if ('clearAll' in this._set)
276 this._set.clearAll();
277 },
278
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 },
292
293 onDeleteVisits: function (aURI, aVisitTime, aGUID, aReason, aTransitionType) {
294 if ((aReason == Ci.nsINavHistoryObserver.REASON_DELETED) && !this._inBatch) {
295 this.populateGrid(true);
296 }
297 },
298
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 });
307
308 let HistoryStartView = {
309 _view: null,
310 get _grid() { return document.getElementById("start-history-grid"); },
311
312 init: function init() {
313 this._view = new HistoryView(this._grid, true);
314 this._view.populateGrid();
315 this._grid.removeAttribute("fade");
316 },
317
318 uninit: function uninit() {
319 if (this._view) {
320 this._view.destruct();
321 }
322 }
323 };

mercurial