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

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     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/. */
     5 "use strict"
     7 Cu.import("resource://gre/modules/PageThumbs.jsm");
     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();
    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);
    21   Services.obs.addObserver(this, "Metro:RefreshTopsiteThumbnail", false);
    23   NewTabUtils.allPages.register(this);
    24   TopSites.prepareCache().then(function(){
    25     this.populateGrid();
    26   }.bind(this));
    27 }
    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,
    36   // For View's showing property
    37   get vbox() {
    38     return document.getElementById("start-topsites");
    39   },
    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   },
    50   handleItemClick: function tabview_handleItemClick(aItem) {
    51     let url = aItem.getAttribute("value");
    52     StartUI.goToURI(url);
    53   },
    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();
    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);
    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   },
   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   },
   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();
   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   },
   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));
   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     });
   182     aSite.applyToTileNode(aTileNode);
   183     if (aTileNode.refresh) {
   184       aTileNode.refresh();
   185     }
   186     if (aArrangeGrid) {
   187       this._set.arrangeItems();
   188     }
   189   },
   191   populateGrid: function populateGrid() {
   192     this.isUpdating = true;
   194     let sites = TopSites.getSites();
   196     let tileset = this._set;
   197     tileset.clearAll(true);
   199     if (!this.maxTiles) {
   200       this.isUpdating = false;
   201       return;
   202     } else {
   203       sites = sites.slice(0, this.maxTiles);
   204     }
   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   },
   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   },
   223   isFirstRun: function isFirstRun() {
   224     return Services.prefs.getBoolPref("browser.firstrun.show.localepicker");
   225   },
   227   _adjustDOMforViewState: function _adjustDOMforViewState(aState) {
   228     if (!this._set)
   229       return;
   230     if (!aState)
   231       aState = this._set.getAttribute("viewstate");
   233     View.prototype._adjustDOMforViewState.call(this, aState);
   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     }
   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   },
   253   refreshView: function () {
   254     this.populateGrid();
   255   },
   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   },
   268   // nsINavHistoryObserver
   269   onBeginUpdateBatch: function() {
   270   },
   272   onEndUpdateBatch: function() {
   273   },
   275   onVisit: function(aURI, aVisitID, aTime, aSessionID,
   276                     aReferringID, aTransitionType) {
   277   },
   279   onTitleChanged: function(aURI, aPageTitle) {
   280   },
   282   onDeleteURI: function(aURI) {
   283   },
   285   onClearHistory: function() {
   286     if ('clearAll' in this._set)
   287       this._set.clearAll();
   288   },
   290   onPageChanged: function(aURI, aWhat, aValue) {
   291   },
   293   onDeleteVisits: function (aURI, aVisitTime, aGUID, aReason, aTransitionType) {
   294   },
   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   }
   304 });
   306 let TopSitesStartView = {
   307   _view: null,
   308   get _grid() { return document.getElementById("start-topsites-grid"); },
   310   init: function init() {
   311     this._view = new TopSitesView(this._grid);
   312     this._grid.removeAttribute("fade");
   313   },
   315   uninit: function uninit() {
   316     if (this._view) {
   317       this._view.destruct();
   318     }
   319   },
   320 };

mercurial