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

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

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

mercurial