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

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:cccdf51dcc79
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 Cu.import("resource://gre/modules/PageThumbs.jsm");
8
9 function TopSitesView(aGrid) {
10 View.call(this, aGrid);
11 // View monitors this for maximum tile display counts
12 this.tilePrefName = "browser.display.startUI.topsites.maxresults";
13 this.showing = this.maxTiles > 0 && !this.isFirstRun();
14
15 // clean up state when the appbar closes
16 StartUI.chromeWin.addEventListener('MozAppbarDismissing', this, false);
17 let history = Cc["@mozilla.org/browser/nav-history-service;1"].
18 getService(Ci.nsINavHistoryService);
19 history.addObserver(this, false);
20
21 Services.obs.addObserver(this, "Metro:RefreshTopsiteThumbnail", false);
22
23 NewTabUtils.allPages.register(this);
24 TopSites.prepareCache().then(function(){
25 this.populateGrid();
26 }.bind(this));
27 }
28
29 TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
30 _set:null,
31 // _lastSelectedSites used to temporarily store blocked/removed sites for undo/restore-ing
32 _lastSelectedSites: null,
33 // isUpdating used only for testing currently
34 isUpdating: false,
35
36 // For View's showing property
37 get vbox() {
38 return document.getElementById("start-topsites");
39 },
40
41 destruct: function destruct() {
42 Services.obs.removeObserver(this, "Metro:RefreshTopsiteThumbnail");
43 NewTabUtils.allPages.unregister(this);
44 if (StartUI.chromeWin) {
45 StartUI.chromeWin.removeEventListener('MozAppbarDismissing', this, false);
46 }
47 View.prototype.destruct.call(this);
48 },
49
50 handleItemClick: function tabview_handleItemClick(aItem) {
51 let url = aItem.getAttribute("value");
52 StartUI.goToURI(url);
53 },
54
55 doActionOnSelectedTiles: function(aActionName, aEvent) {
56 let tileGroup = this._set;
57 let selectedTiles = tileGroup.selectedItems;
58 let sites = Array.map(selectedTiles, TopSites._linkFromNode);
59 let nextContextActions = new Set();
60
61 switch (aActionName){
62 case "delete":
63 for (let aNode of selectedTiles) {
64 // add some class to transition element before deletion?
65 aNode.contextActions.delete('delete');
66 // we need new context buttons to show (the tile node will go away though)
67 }
68 this._lastSelectedSites = (this._lastSelectedSites || []).concat(sites);
69 // stop the appbar from dismissing
70 aEvent.preventDefault();
71 nextContextActions.add('restore');
72 TopSites.hideSites(sites);
73 break;
74 case "restore":
75 // usually restore is an undo action, so we have to recreate the tiles and grid selection
76 if (this._lastSelectedSites) {
77 let selectedUrls = this._lastSelectedSites.map((site) => site.url);
78 // re-select the tiles once the tileGroup is done populating and arranging
79 tileGroup.addEventListener("arranged", function _onArranged(aEvent){
80 for (let url of selectedUrls) {
81 let tileNode = tileGroup.querySelector("richgriditem[value='"+url+"']");
82 if (tileNode) {
83 tileNode.setAttribute("selected", true);
84 }
85 }
86 tileGroup.removeEventListener("arranged", _onArranged, false);
87 // <sfoster> we can't just call selectItem n times on tileGroup as selecting means trigger the default action
88 // for seltype="single" grids.
89 // so we toggle the attributes and raise the selectionchange "manually"
90 let event = tileGroup.ownerDocument.createEvent("Events");
91 event.initEvent("selectionchange", true, true);
92 tileGroup.dispatchEvent(event);
93 }, false);
94
95 TopSites.restoreSites(this._lastSelectedSites);
96 // stop the appbar from dismissing,
97 // the selectionchange event will trigger re-population of the context appbar
98 aEvent.preventDefault();
99 }
100 break;
101 case "pin":
102 let pinIndices = [];
103 Array.forEach(selectedTiles, function(aNode) {
104 pinIndices.push( Array.indexOf(aNode.control.items, aNode) );
105 aNode.contextActions.delete('pin');
106 aNode.contextActions.add('unpin');
107 });
108 TopSites.pinSites(sites, pinIndices);
109 break;
110 case "unpin":
111 Array.forEach(selectedTiles, function(aNode) {
112 aNode.contextActions.delete('unpin');
113 aNode.contextActions.add('pin');
114 });
115 TopSites.unpinSites(sites);
116 break;
117 // default: no action
118 }
119 if (nextContextActions.size) {
120 // at next tick, re-populate the context appbar
121 setTimeout(function(){
122 // fire a MozContextActionsChange event to update the context appbar
123 let event = document.createEvent("Events");
124 event.actions = [...nextContextActions];
125 event.initEvent("MozContextActionsChange", true, false);
126 tileGroup.dispatchEvent(event);
127 },0);
128 }
129 },
130
131 handleEvent: function(aEvent) {
132 switch (aEvent.type){
133 case "MozAppbarDismissing":
134 // clean up when the context appbar is dismissed - we don't remember selections
135 this._lastSelectedSites = null;
136 break;
137 }
138 },
139
140 update: function() {
141 // called by the NewTabUtils.allPages.update, notifying us of data-change in topsites
142 let grid = this._set,
143 dirtySites = TopSites.dirty();
144
145 if (dirtySites.size) {
146 // we can just do a partial update and refresh the node representing each dirty tile
147 for (let site of dirtySites) {
148 let tileNode = grid.querySelector("[value='"+site.url+"']");
149 if (tileNode) {
150 this.updateTile(tileNode, new Site(site));
151 }
152 }
153 } else {
154 // flush, recreate all
155 this.isUpdating = true;
156 // destroy and recreate all item nodes, skip calling arrangeItems
157 this.populateGrid();
158 }
159 },
160
161 updateTile: function(aTileNode, aSite, aArrangeGrid) {
162 if (!(aSite && aSite.url)) {
163 throw new Error("Invalid Site object passed to TopSitesView updateTile");
164 }
165 this._updateFavicon(aTileNode, Util.makeURI(aSite.url));
166
167 Task.spawn(function() {
168 let filepath = PageThumbsStorage.getFilePathForURL(aSite.url);
169 if (yield OS.File.exists(filepath)) {
170 aSite.backgroundImage = 'url("'+PageThumbs.getThumbnailURL(aSite.url)+'")';
171 // use the setter when available to update the backgroundImage value
172 if ('backgroundImage' in aTileNode &&
173 aTileNode.backgroundImage != aSite.backgroundImage) {
174 aTileNode.backgroundImage = aSite.backgroundImage;
175 } else {
176 // just update the attribute for when the node gets the binding applied
177 aTileNode.setAttribute("customImage", aSite.backgroundImage);
178 }
179 }
180 });
181
182 aSite.applyToTileNode(aTileNode);
183 if (aTileNode.refresh) {
184 aTileNode.refresh();
185 }
186 if (aArrangeGrid) {
187 this._set.arrangeItems();
188 }
189 },
190
191 populateGrid: function populateGrid() {
192 this.isUpdating = true;
193
194 let sites = TopSites.getSites();
195
196 let tileset = this._set;
197 tileset.clearAll(true);
198
199 if (!this.maxTiles) {
200 this.isUpdating = false;
201 return;
202 } else {
203 sites = sites.slice(0, this.maxTiles);
204 }
205
206 for (let site of sites) {
207 let slot = tileset.nextSlot();
208 this.updateTile(slot, site);
209 }
210 tileset.arrangeItems();
211 this.isUpdating = false;
212 },
213
214 forceReloadOfThumbnail: function forceReloadOfThumbnail(url) {
215 let nodes = this._set.querySelectorAll('richgriditem[value="'+url+'"]');
216 for (let item of nodes) {
217 if ("isBound" in item && item.isBound) {
218 item.refreshBackgroundImage();
219 }
220 }
221 },
222
223 isFirstRun: function isFirstRun() {
224 return Services.prefs.getBoolPref("browser.firstrun.show.localepicker");
225 },
226
227 _adjustDOMforViewState: function _adjustDOMforViewState(aState) {
228 if (!this._set)
229 return;
230 if (!aState)
231 aState = this._set.getAttribute("viewstate");
232
233 View.prototype._adjustDOMforViewState.call(this, aState);
234
235 // Don't show thumbnails in snapped view.
236 if (aState == "snapped") {
237 document.getElementById("start-topsites-grid").removeAttribute("tiletype");
238 } else {
239 document.getElementById("start-topsites-grid").setAttribute("tiletype", "thumbnail");
240 }
241
242 // propogate tiletype changes down to tile children
243 let tileType = this._set.getAttribute("tiletype");
244 for (let item of this._set.children) {
245 if (tileType) {
246 item.setAttribute("tiletype", tileType);
247 } else {
248 item.removeAttribute("tiletype");
249 }
250 }
251 },
252
253 refreshView: function () {
254 this.populateGrid();
255 },
256
257 // nsIObservers
258 observe: function (aSubject, aTopic, aState) {
259 switch (aTopic) {
260 case "Metro:RefreshTopsiteThumbnail":
261 this.forceReloadOfThumbnail(aState);
262 break;
263 }
264 View.prototype.observe.call(this, aSubject, aTopic, aState);
265 this.showing = this.maxTiles > 0 && !this.isFirstRun();
266 },
267
268 // nsINavHistoryObserver
269 onBeginUpdateBatch: function() {
270 },
271
272 onEndUpdateBatch: function() {
273 },
274
275 onVisit: function(aURI, aVisitID, aTime, aSessionID,
276 aReferringID, aTransitionType) {
277 },
278
279 onTitleChanged: function(aURI, aPageTitle) {
280 },
281
282 onDeleteURI: function(aURI) {
283 },
284
285 onClearHistory: function() {
286 if ('clearAll' in this._set)
287 this._set.clearAll();
288 },
289
290 onPageChanged: function(aURI, aWhat, aValue) {
291 },
292
293 onDeleteVisits: function (aURI, aVisitTime, aGUID, aReason, aTransitionType) {
294 },
295
296 QueryInterface: function(iid) {
297 if (iid.equals(Components.interfaces.nsINavHistoryObserver) ||
298 iid.equals(Components.interfaces.nsISupports)) {
299 return this;
300 }
301 throw Cr.NS_ERROR_NO_INTERFACE;
302 }
303
304 });
305
306 let TopSitesStartView = {
307 _view: null,
308 get _grid() { return document.getElementById("start-topsites-grid"); },
309
310 init: function init() {
311 this._view = new TopSitesView(this._grid);
312 this._grid.removeAttribute("fade");
313 },
314
315 uninit: function uninit() {
316 if (this._view) {
317 this._view.destruct();
318 }
319 },
320 };

mercurial