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

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/metro/base/content/startui/TopSitesView.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,320 @@
     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 +Cu.import("resource://gre/modules/PageThumbs.jsm");
    1.11 +
    1.12 +function TopSitesView(aGrid) {
    1.13 +  View.call(this, aGrid);
    1.14 +  // View monitors this for maximum tile display counts
    1.15 +  this.tilePrefName = "browser.display.startUI.topsites.maxresults";
    1.16 +  this.showing = this.maxTiles > 0 && !this.isFirstRun();
    1.17 +
    1.18 +  // clean up state when the appbar closes
    1.19 +  StartUI.chromeWin.addEventListener('MozAppbarDismissing', this, false);
    1.20 +  let history = Cc["@mozilla.org/browser/nav-history-service;1"].
    1.21 +                getService(Ci.nsINavHistoryService);
    1.22 +  history.addObserver(this, false);
    1.23 +
    1.24 +  Services.obs.addObserver(this, "Metro:RefreshTopsiteThumbnail", false);
    1.25 +
    1.26 +  NewTabUtils.allPages.register(this);
    1.27 +  TopSites.prepareCache().then(function(){
    1.28 +    this.populateGrid();
    1.29 +  }.bind(this));
    1.30 +}
    1.31 +
    1.32 +TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
    1.33 +  _set:null,
    1.34 +  // _lastSelectedSites used to temporarily store blocked/removed sites for undo/restore-ing
    1.35 +  _lastSelectedSites: null,
    1.36 +  // isUpdating used only for testing currently
    1.37 +  isUpdating: false,
    1.38 +
    1.39 +  // For View's showing property
    1.40 +  get vbox() {
    1.41 +    return document.getElementById("start-topsites");
    1.42 +  },
    1.43 +
    1.44 +  destruct: function destruct() {
    1.45 +    Services.obs.removeObserver(this, "Metro:RefreshTopsiteThumbnail");
    1.46 +    NewTabUtils.allPages.unregister(this);
    1.47 +    if (StartUI.chromeWin) {
    1.48 +      StartUI.chromeWin.removeEventListener('MozAppbarDismissing', this, false);
    1.49 +    }
    1.50 +    View.prototype.destruct.call(this);
    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 +  doActionOnSelectedTiles: function(aActionName, aEvent) {
    1.59 +    let tileGroup = this._set;
    1.60 +    let selectedTiles = tileGroup.selectedItems;
    1.61 +    let sites = Array.map(selectedTiles, TopSites._linkFromNode);
    1.62 +    let nextContextActions = new Set();
    1.63 +
    1.64 +    switch (aActionName){
    1.65 +      case "delete":
    1.66 +        for (let aNode of selectedTiles) {
    1.67 +          // add some class to transition element before deletion?
    1.68 +          aNode.contextActions.delete('delete');
    1.69 +          // we need new context buttons to show (the tile node will go away though)
    1.70 +        }
    1.71 +        this._lastSelectedSites = (this._lastSelectedSites || []).concat(sites);
    1.72 +        // stop the appbar from dismissing
    1.73 +        aEvent.preventDefault();
    1.74 +        nextContextActions.add('restore');
    1.75 +        TopSites.hideSites(sites);
    1.76 +        break;
    1.77 +      case "restore":
    1.78 +        // usually restore is an undo action, so we have to recreate the tiles and grid selection
    1.79 +        if (this._lastSelectedSites) {
    1.80 +          let selectedUrls = this._lastSelectedSites.map((site) => site.url);
    1.81 +          // re-select the tiles once the tileGroup is done populating and arranging
    1.82 +          tileGroup.addEventListener("arranged", function _onArranged(aEvent){
    1.83 +            for (let url of selectedUrls) {
    1.84 +              let tileNode = tileGroup.querySelector("richgriditem[value='"+url+"']");
    1.85 +              if (tileNode) {
    1.86 +                tileNode.setAttribute("selected", true);
    1.87 +              }
    1.88 +            }
    1.89 +            tileGroup.removeEventListener("arranged", _onArranged, false);
    1.90 +            // <sfoster> we can't just call selectItem n times on tileGroup as selecting means trigger the default action
    1.91 +            // for seltype="single" grids.
    1.92 +            // so we toggle the attributes and raise the selectionchange "manually"
    1.93 +            let event = tileGroup.ownerDocument.createEvent("Events");
    1.94 +            event.initEvent("selectionchange", true, true);
    1.95 +            tileGroup.dispatchEvent(event);
    1.96 +          }, false);
    1.97 +
    1.98 +          TopSites.restoreSites(this._lastSelectedSites);
    1.99 +          // stop the appbar from dismissing,
   1.100 +          // the selectionchange event will trigger re-population of the context appbar
   1.101 +          aEvent.preventDefault();
   1.102 +        }
   1.103 +        break;
   1.104 +      case "pin":
   1.105 +        let pinIndices = [];
   1.106 +        Array.forEach(selectedTiles, function(aNode) {
   1.107 +          pinIndices.push( Array.indexOf(aNode.control.items, aNode) );
   1.108 +          aNode.contextActions.delete('pin');
   1.109 +          aNode.contextActions.add('unpin');
   1.110 +        });
   1.111 +        TopSites.pinSites(sites, pinIndices);
   1.112 +        break;
   1.113 +      case "unpin":
   1.114 +        Array.forEach(selectedTiles, function(aNode) {
   1.115 +          aNode.contextActions.delete('unpin');
   1.116 +          aNode.contextActions.add('pin');
   1.117 +        });
   1.118 +        TopSites.unpinSites(sites);
   1.119 +        break;
   1.120 +      // default: no action
   1.121 +    }
   1.122 +    if (nextContextActions.size) {
   1.123 +      // at next tick, re-populate the context appbar
   1.124 +      setTimeout(function(){
   1.125 +        // fire a MozContextActionsChange event to update the context appbar
   1.126 +        let event = document.createEvent("Events");
   1.127 +        event.actions = [...nextContextActions];
   1.128 +        event.initEvent("MozContextActionsChange", true, false);
   1.129 +        tileGroup.dispatchEvent(event);
   1.130 +      },0);
   1.131 +    }
   1.132 +  },
   1.133 +
   1.134 +  handleEvent: function(aEvent) {
   1.135 +    switch (aEvent.type){
   1.136 +      case "MozAppbarDismissing":
   1.137 +        // clean up when the context appbar is dismissed - we don't remember selections
   1.138 +        this._lastSelectedSites = null;
   1.139 +        break;
   1.140 +    }
   1.141 +  },
   1.142 +
   1.143 +  update: function() {
   1.144 +    // called by the NewTabUtils.allPages.update, notifying us of data-change in topsites
   1.145 +    let grid = this._set,
   1.146 +        dirtySites = TopSites.dirty();
   1.147 +
   1.148 +    if (dirtySites.size) {
   1.149 +      // we can just do a partial update and refresh the node representing each dirty tile
   1.150 +      for (let site of dirtySites) {
   1.151 +        let tileNode = grid.querySelector("[value='"+site.url+"']");
   1.152 +        if (tileNode) {
   1.153 +          this.updateTile(tileNode, new Site(site));
   1.154 +        }
   1.155 +      }
   1.156 +    } else {
   1.157 +        // flush, recreate all
   1.158 +      this.isUpdating = true;
   1.159 +      // destroy and recreate all item nodes, skip calling arrangeItems
   1.160 +      this.populateGrid();
   1.161 +    }
   1.162 +  },
   1.163 +
   1.164 +  updateTile: function(aTileNode, aSite, aArrangeGrid) {
   1.165 +    if (!(aSite && aSite.url)) {
   1.166 +      throw new Error("Invalid Site object passed to TopSitesView updateTile");
   1.167 +    }
   1.168 +    this._updateFavicon(aTileNode, Util.makeURI(aSite.url));
   1.169 +
   1.170 +    Task.spawn(function() {
   1.171 +      let filepath = PageThumbsStorage.getFilePathForURL(aSite.url);
   1.172 +      if (yield OS.File.exists(filepath)) {
   1.173 +        aSite.backgroundImage = 'url("'+PageThumbs.getThumbnailURL(aSite.url)+'")';
   1.174 +        // use the setter when available to update the backgroundImage value
   1.175 +        if ('backgroundImage' in aTileNode &&
   1.176 +            aTileNode.backgroundImage != aSite.backgroundImage) {
   1.177 +          aTileNode.backgroundImage = aSite.backgroundImage;
   1.178 +        } else {
   1.179 +          // just update the attribute for when the node gets the binding applied
   1.180 +          aTileNode.setAttribute("customImage", aSite.backgroundImage);
   1.181 +        }
   1.182 +      }
   1.183 +    });
   1.184 +
   1.185 +    aSite.applyToTileNode(aTileNode);
   1.186 +    if (aTileNode.refresh) {
   1.187 +      aTileNode.refresh();
   1.188 +    }
   1.189 +    if (aArrangeGrid) {
   1.190 +      this._set.arrangeItems();
   1.191 +    }
   1.192 +  },
   1.193 +
   1.194 +  populateGrid: function populateGrid() {
   1.195 +    this.isUpdating = true;
   1.196 +
   1.197 +    let sites = TopSites.getSites();
   1.198 +
   1.199 +    let tileset = this._set;
   1.200 +    tileset.clearAll(true);
   1.201 +
   1.202 +    if (!this.maxTiles) {
   1.203 +      this.isUpdating = false;
   1.204 +      return;
   1.205 +    } else {
   1.206 +      sites = sites.slice(0, this.maxTiles);
   1.207 +    }
   1.208 +
   1.209 +    for (let site of sites) {
   1.210 +      let slot = tileset.nextSlot();
   1.211 +      this.updateTile(slot, site);
   1.212 +    }
   1.213 +    tileset.arrangeItems();
   1.214 +    this.isUpdating = false;
   1.215 +  },
   1.216 +
   1.217 +  forceReloadOfThumbnail: function forceReloadOfThumbnail(url) {
   1.218 +    let nodes = this._set.querySelectorAll('richgriditem[value="'+url+'"]');
   1.219 +    for (let item of nodes) {
   1.220 +      if ("isBound" in item && item.isBound) {
   1.221 +        item.refreshBackgroundImage();
   1.222 +      }
   1.223 +    }
   1.224 +  },
   1.225 +
   1.226 +  isFirstRun: function isFirstRun() {
   1.227 +    return Services.prefs.getBoolPref("browser.firstrun.show.localepicker");
   1.228 +  },
   1.229 +
   1.230 +  _adjustDOMforViewState: function _adjustDOMforViewState(aState) {
   1.231 +    if (!this._set)
   1.232 +      return;
   1.233 +    if (!aState)
   1.234 +      aState = this._set.getAttribute("viewstate");
   1.235 +
   1.236 +    View.prototype._adjustDOMforViewState.call(this, aState);
   1.237 +
   1.238 +    // Don't show thumbnails in snapped view.
   1.239 +    if (aState == "snapped") {
   1.240 +      document.getElementById("start-topsites-grid").removeAttribute("tiletype");
   1.241 +    } else {
   1.242 +      document.getElementById("start-topsites-grid").setAttribute("tiletype", "thumbnail");
   1.243 +    }
   1.244 +
   1.245 +    // propogate tiletype changes down to tile children
   1.246 +    let tileType = this._set.getAttribute("tiletype");
   1.247 +    for (let item of this._set.children) {
   1.248 +      if (tileType) {
   1.249 +        item.setAttribute("tiletype", tileType);
   1.250 +      } else {
   1.251 +        item.removeAttribute("tiletype");
   1.252 +      }
   1.253 +    }
   1.254 +  },
   1.255 +
   1.256 +  refreshView: function () {
   1.257 +    this.populateGrid();
   1.258 +  },
   1.259 +
   1.260 +  // nsIObservers
   1.261 +  observe: function (aSubject, aTopic, aState) {
   1.262 +    switch (aTopic) {
   1.263 +      case "Metro:RefreshTopsiteThumbnail":
   1.264 +        this.forceReloadOfThumbnail(aState);
   1.265 +        break;
   1.266 +    }
   1.267 +    View.prototype.observe.call(this, aSubject, aTopic, aState);
   1.268 +    this.showing = this.maxTiles > 0 && !this.isFirstRun();
   1.269 +  },
   1.270 +
   1.271 +  // nsINavHistoryObserver
   1.272 +  onBeginUpdateBatch: function() {
   1.273 +  },
   1.274 +
   1.275 +  onEndUpdateBatch: function() {
   1.276 +  },
   1.277 +
   1.278 +  onVisit: function(aURI, aVisitID, aTime, aSessionID,
   1.279 +                    aReferringID, aTransitionType) {
   1.280 +  },
   1.281 +
   1.282 +  onTitleChanged: function(aURI, aPageTitle) {
   1.283 +  },
   1.284 +
   1.285 +  onDeleteURI: function(aURI) {
   1.286 +  },
   1.287 +
   1.288 +  onClearHistory: function() {
   1.289 +    if ('clearAll' in this._set)
   1.290 +      this._set.clearAll();
   1.291 +  },
   1.292 +
   1.293 +  onPageChanged: function(aURI, aWhat, aValue) {
   1.294 +  },
   1.295 +
   1.296 +  onDeleteVisits: function (aURI, aVisitTime, aGUID, aReason, aTransitionType) {
   1.297 +  },
   1.298 +
   1.299 +  QueryInterface: function(iid) {
   1.300 +    if (iid.equals(Components.interfaces.nsINavHistoryObserver) ||
   1.301 +        iid.equals(Components.interfaces.nsISupports)) {
   1.302 +      return this;
   1.303 +    }
   1.304 +    throw Cr.NS_ERROR_NO_INTERFACE;
   1.305 +  }
   1.306 +
   1.307 +});
   1.308 +
   1.309 +let TopSitesStartView = {
   1.310 +  _view: null,
   1.311 +  get _grid() { return document.getElementById("start-topsites-grid"); },
   1.312 +
   1.313 +  init: function init() {
   1.314 +    this._view = new TopSitesView(this._grid);
   1.315 +    this._grid.removeAttribute("fade");
   1.316 +  },
   1.317 +
   1.318 +  uninit: function uninit() {
   1.319 +    if (this._view) {
   1.320 +      this._view.destruct();
   1.321 +    }
   1.322 +  },
   1.323 +};

mercurial