Bug 794028 part 2/2: Use the Firefox Start Top Sites tile group. r=mbrubeck
authorAlly Naaktgeboren <ally@mozilla.com>
Thu, 07 Mar 2013 15:38:03 -0800
changeset 124172 a1586c3b0da81c55267c86c59b33d4a0dbd3ef4f
parent 124171 08096dc62f37d048a44d70b0462aa857f8b6c329
child 124173 a8088accbcbe8bcd1437dd02f7cd26568d41f483
push id24408
push userryanvm@gmail.com
push dateFri, 08 Mar 2013 04:58:11 +0000
treeherdermozilla-central@cb432984d5ce [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmbrubeck
bugs794028
milestone22.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 794028 part 2/2: Use the Firefox Start Top Sites tile group. r=mbrubeck
browser/metro/base/content/TopSites.js
browser/metro/base/content/bindings/grid.xml
browser/metro/base/content/browser-ui.js
browser/metro/theme/platform.css
--- a/browser/metro/base/content/TopSites.js
+++ b/browser/metro/base/content/TopSites.js
@@ -1,17 +1,18 @@
 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
 'use strict';
  let prefs = Components.classes["@mozilla.org/preferences-service;1"].
       getService(Components.interfaces.nsIPrefBranch);
 
+Cu.import("resource://gre/modules/PageThumbs.jsm");
+
 // singleton to provide data-level functionality to the views
 let TopSites = {
   pinSite: function(aId, aSlotIndex) {
     Util.dumpLn("TopSites.pinSite: " + aId + ", (TODO)");
     // FIXME: implementation needed
     return true; // operation was successful
   },
   unpinSite: function(aId) {
@@ -26,27 +27,34 @@ let TopSites = {
   },
   restoreSite: function(aId) {
     Util.dumpLn("TopSites.restoreSite: " + aId + ", (TODO)");
     // FIXME: implementation needed
     return true; // operation was successful
   }
 };
 
-function TopSitesView(aGrid, maxSites) {
+// The value of useThumbs should not be changed over the lifetime of
+//   the object.
+function TopSitesView(aGrid, aMaxSites, aUseThumbnails) {
   this._set = aGrid;
   this._set.controller = this;
-  this._topSitesMax = maxSites;
+  this._topSitesMax = aMaxSites;
+  this._useThumbs = aUseThumbnails;
 
   // handle selectionchange DOM events from the grid/tile group
   this._set.addEventListener("context-action", this, false);
 
   let history = Cc["@mozilla.org/browser/nav-history-service;1"].
                 getService(Ci.nsINavHistoryService);
   history.addObserver(this, false);
+  if (this._useThumbs) {
+    PageThumbs.addExpirationFilter(this);
+    Services.obs.addObserver(this, "Metro:RefreshTopsiteThumbnail", false);
+  }
 }
 
 TopSitesView.prototype = {
   _set:null,
   _topSitesMax: null,
 
   handleItemClick: function tabview_handleItemClick(aItem) {
     let url = aItem.getAttribute("value");
@@ -142,28 +150,55 @@ TopSitesView.prototype = {
       } else {
         supportedActions.push('pin');
       }
       let item = this._set.appendItem(title, uri);
       item.setAttribute("iconURI", node.icon);
       item.setAttribute("data-itemid", node[identifier]);
       // here is where we could add verbs based on pinned etc. state
       item.setAttribute("data-contextactions", supportedActions.join(','));
+
+      if (this._useThumbs) {
+        let thumbnail = PageThumbs.getThumbnailURL(uri);
+        let cssthumbnail = 'url("'+thumbnail+'")';
+        item.backgroundImage = cssthumbnail;
+      }
     }
     rootNode.containerOpen = false;
   },
 
+  forceReloadOfThumbnail: function forceReloadOfThumbnail(url) {
+      let nodes = this._set.querySelectorAll('richgriditem[value="'+url+'"]');
+      for (let item of nodes) {
+        item.refreshBackgroundImage();
+      }
+  },
+  filterForThumbnailExpiration: function filterForThumbnailExpiration(aCallback) {
+    aCallback([item.getAttribute("value") for (item of this._set.children)]);
+  },
+
   isFirstRun: function isFirstRun() {
     return prefs.getBoolPref("browser.firstrun.show.localepicker");
   },
 
   destruct: function destruct() {
-    // remove the observers here
+    if (this._useThumbs) {
+      Services.obs.removeObserver(this, "Metro:RefreshTopsiteThumbnail");
+      PageThumbs.removeExpirationFilter(this);
+    }
   },
 
+  // nsIObservers
+  observe: function (aSubject, aTopic, aState) {
+    switch(aTopic) {
+      case "Metro:RefreshTopsiteThumbnail":
+        this.forceReloadOfThumbnail(aState);
+        break;
+    }
+  },
   // nsINavHistoryObserver
 
   onBeginUpdateBatch: function() {
   },
 
   onEndUpdateBatch: function() {
   },
 
@@ -197,17 +232,17 @@ TopSitesView.prototype = {
 
 };
 
 let TopSitesStartView = {
   _view: null,
   get _grid() { return document.getElementById("start-topsites-grid"); },
 
   init: function init() {
-    this._view = new TopSitesView(this._grid, 9);
+    this._view = new TopSitesView(this._grid, 9, true);
     if (this._view.isFirstRun()) {
       let topsitesVbox = document.getElementById("start-topsites");
       topsitesVbox.setAttribute("hidden", "true");
     }
     this._view.populateGrid();
   },
 
   uninit: function uninit() {
--- a/browser/metro/base/content/bindings/grid.xml
+++ b/browser/metro/base/content/bindings/grid.xml
@@ -471,38 +471,42 @@
         </body>
       </method>
 
     </implementation>
   </binding>
 
   <binding id="richgrid-item">
     <content>
-      <xul:vbox anonid="anon-richgrid-item" class="richgrid-item-content">
-        <xul:hbox class="richgrid-icon-container">
+      <xul:vbox anonid="anon-richgrid-item" class="richgrid-item-content" xbl:inherits="customImagePresent">
+        <xul:hbox class="richgrid-icon-container" xbl:inherits="customImagePresent">
           <xul:box class="richgrid-icon-box"><xul:image xbl:inherits="src=iconURI"/></xul:box>
           <xul:box flex="1" />
         </xul:hbox>
-        <xul:description xbl:inherits="value=label" crop="end"/>
+        <xul:description class="richgrid-item-desc" xbl:inherits="value=label" crop="end"/>
       </xul:vbox>
     </content>
 
     <implementation>
       <property name="_box" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'anon-richgrid-item');"/>
       <property name="color" onset="this._color = val; this.setColor();" onget="return this._color;"/>
+      <property name="backgroundimage"
+                onset="this._backgroundimage = val; this.setBackgroundImage();"
+                onget="return this._backgroundimage;" />
       <property name="selected"
                 onget="return this.getAttribute('selected') == 'true';"
                 onset="this.setAttribute('selected', val);"/>
 
       <constructor>
         <![CDATA[
           // Bindings don't get bound until the item is displayed,
-          // so we have to reset the background color when we get
+          // so we have to reset the background color/image when we get
           // created.
           this.setColor();
+          this.setBackgroundImage();
         ]]>
       </constructor>
 
       <property name="control">
         <getter><![CDATA[
           var parent = this.parentNode;
           while (parent) {
             if (parent instanceof Components.interfaces.nsIDOMXULSelectControlElement)
@@ -521,16 +525,38 @@
               this._box.style.backgroundColor = this.color;
             } else {
               this._box.parentNode.removeAttribute("customColorPresent");
             }
           ]]>
         </body>
       </method>
 
+      <method name="setBackgroundImage">
+        <body>
+          <![CDATA[
+            if (this.backgroundImage != undefined) {
+              this._box.parentNode.setAttribute("customImagePresent", "true");
+              this._box.style.backgroundImage = this.backgroundImage;
+            } else {
+              this._box.parentNode.removeAttribute("customImagePresent");
+              this._box.style.removeProperty("background-image");
+            }
+          ]]>
+        </body>
+      </method>
+      <method name="refreshBackgroundImage">
+        <body><![CDATA[
+          if (this.backgroundImage) {
+            this._box.style.removeProperty("background-image");
+            this._box.style.setProperty("background-image", this.backgroundImage);
+          }
+        ]]></body>
+      </method>
+
       <field name="_contextActions">null</field>
       <property name="contextActions">
         <getter>
           <![CDATA[
             if(!this._contextActions) {
               this._contextActions = new Set();
               let actionSet = this._contextActions;
               let actions = this.getAttribute("data-contextactions");
--- a/browser/metro/base/content/browser-ui.js
+++ b/browser/metro/base/content/browser-ui.js
@@ -1,13 +1,15 @@
 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+Cu.import("resource://gre/modules/PageThumbs.jsm");
+
 /**
  * Constants
  */
 
 // BrowserUI.update(state) constants. Currently passed in
 // but update doesn't pay attention to them. Can we remove?
 const TOOLBARSTATE_LOADING  = 1;
 const TOOLBARSTATE_LOADED   = 2;
@@ -78,16 +80,17 @@ var BrowserUI = {
   init: function() {
     // listen content messages
     messageManager.addMessageListener("DOMTitleChanged", this);
     messageManager.addMessageListener("DOMWillOpenModalDialog", this);
     messageManager.addMessageListener("DOMWindowClose", this);
 
     messageManager.addMessageListener("Browser:OpenURI", this);
     messageManager.addMessageListener("Browser:SaveAs:Return", this);
+    messageManager.addMessageListener("Content:StateChange", this);
 
     // listening escape to dismiss dialog on VK_ESCAPE
     window.addEventListener("keypress", this, true);
 
     window.addEventListener("MozPrecisePointer", this, true);
     window.addEventListener("MozImprecisePointer", this, true);
 
     Services.prefs.addObserver("browser.tabs.tabsOnly", this, false);
@@ -97,16 +100,17 @@ var BrowserUI = {
     ContextUI.init();
     StartUI.init();
     PanelUI.init();
     IdentityUI.init();
     if (Browser.getHomePage() === "about:start") {
       StartUI.show();
     }
     FlyoutPanelsUI.init();
+    PageThumbs.init();
 
     // show the right toolbars, awesomescreen, etc for the os viewstate
     BrowserUI._adjustDOMforViewState();
 
     // We can delay some initialization until after startup.  We wait until
     // the first page is shown, then dispatch a UIReadyDelayed event.
     messageManager.addMessageListener("pageshow", function() {
       if (getBrowser().currentURI.spec == "about:blank")
@@ -190,16 +194,18 @@ var BrowserUI = {
 
   uninit: function() {
     messageManager.removeMessageListener("Browser:MozApplicationManifest", OfflineApps);
 
     PanelUI.uninit();
     StartUI.uninit();
     Downloads.uninit();
     SettingsCharm.uninit();
+    messageManager.removeMessageListener("Content:StateChange", this);
+    PageThumbs.uninit();
   },
 
 
   /*********************************
    * Content visibility
    */
 
   get isContentShowing() {
@@ -800,21 +806,99 @@ var BrowserUI = {
       // XXX this and content's sender are a little warped
       case "Browser:OpenURI":
         let referrerURI = null;
         if (json.referrer)
           referrerURI = Services.io.newURI(json.referrer, null, null);
         //Browser.addTab(json.uri, json.bringFront, Browser.selectedTab, { referrerURI: referrerURI });
         this.goToURI(json.uri);
         break;
+      case "Content:StateChange":
+        let currBrowser = Browser.selectedBrowser;
+        if (this.shouldCaptureThumbnails(currBrowser)) {
+          PageThumbs.captureAndStore(currBrowser);
+          let currPage = currBrowser.currentURI.spec;
+          Services.obs.notifyObservers(null, "Metro:RefreshTopsiteThumbnail", currPage);
+        }
+        break;
     }
 
     return {};
   },
 
+  // Private Browsing is not supported on metro at this time, when it is added
+  //  this function must be updated to skip capturing those pages
+  shouldCaptureThumbnails: function shouldCaptureThumbnails(aBrowser) {
+    // Capture only if it's the currently selected tab.
+    if (aBrowser != Browser.selectedBrowser) {
+      return false;
+    }
+    // FIXME Bug 720575 - Don't capture thumbnails for SVG or XML documents as
+    //       that currently regresses Talos SVG tests.
+    let doc = aBrowser.contentDocument;
+    if (doc instanceof SVGDocument || doc instanceof XMLDocument) {
+      return false;
+    }
+
+    // There's no point in taking screenshot of loading pages.
+    if (aBrowser.docShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE) {
+      return false;
+    }
+
+    // Don't take screenshots of about: pages.
+    if (aBrowser.currentURI.schemeIs("about")) {
+      return false;
+    }
+
+    // No valid document channel. We shouldn't take a screenshot.
+    let channel = aBrowser.docShell.currentDocumentChannel;
+    if (!channel) {
+      return false;
+    }
+
+    // Don't take screenshots of internally redirecting about: pages.
+    // This includes error pages.
+    let uri = channel.originalURI;
+    if (uri.schemeIs("about")) {
+      return false;
+    }
+
+    // http checks
+    let httpChannel;
+    try {
+      httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
+    } catch (e) { /* Not an HTTP channel. */ }
+
+    if (httpChannel) {
+      // Continue only if we have a 2xx status code.
+      try {
+        if (Math.floor(httpChannel.responseStatus / 100) != 2) {
+          return false;
+        }
+      } catch (e) {
+        // Can't get response information from the httpChannel
+        // because mResponseHead is not available.
+        return false;
+      }
+
+      // Cache-Control: no-store.
+      if (httpChannel.isNoStoreResponse()) {
+        return false;
+      }
+
+      // Desktop has a pref that allows users to override this. We do not
+      //   support that pref currently
+      if (uri.schemeIs("https")) {
+        return false;
+      }
+    }
+
+    return true;
+  },
+
   supportsCommand : function(cmd) {
     var isSupported = false;
     switch (cmd) {
       case "cmd_back":
       case "cmd_forward":
       case "cmd_reload":
       case "cmd_forceReload":
       case "cmd_stop":
--- a/browser/metro/theme/platform.css
+++ b/browser/metro/theme/platform.css
@@ -451,17 +451,17 @@ richgriditem {
 
 richgriditem .richgrid-item-content {
   border: @metro_border_thin@ solid @tile_border_color@;
   box-shadow: 0 0 @metro_spacing_snormal@ rgba(0, 0, 0, 0.1);
   -moz-box-sizing: border-box;
   padding: 10px 8px 6px 8px;
 }
 
-richgriditem:not([customColorPresent]) .richgrid-item-content {
+.richgrid-item-content {
   background: #fff;
 }
 
 richgriditem[selected="true"] .richgrid-item-content {
   border: @metro_border_xthick@ solid @selected_color@;
   padding: @metro_spacing_xxsmall@;
 }
 
@@ -470,32 +470,59 @@ richgriditem .richgrid-icon-container {
 }
 
 richgriditem .richgrid-icon-box {
   padding: 4px;
   background: #fff;
   opacity: 1.0;
 }
 
-richgriditem[customColorPresent="true"] description {
+
+richgriditem[customColorPresent="true"] {
   color: #f1f1f1;
 }
+richgriditem[customImagePresent] {
+  color: #1a1a1a;
+}
+
 
 richgriditem[customColorPresent="true"] .richgrid-icon-box {
   opacity: 0.8;
   background-color: #fff;
 }
 
-richgriditem description {
+.richgrid-item-content[customImagePresent] {
+  height: 120px;
+  width: 200px;
+  background-size: cover;
+  background-position: center;
+  background-repeat: no-repeat;
+  -moz-box-pack: end;
+  padding: 0px;
+}
+
+/* hide icon if there is an image background */
+.richgrid-icon-container[customImagePresent] {
+  visibility: collapse;
+}
+
+.richgrid-item-desc {
   width: @tile_width@;
   font-size: @metro_font_normal@;
   margin-left: 0px !important;
   padding-left: 0px !important;
 }
 
+.richgrid-item-content[customImagePresent] > .richgrid-item-desc {
+  background: hsla(0,2%,98%,.95);
+  /*margin-bottom: 0px;
+  margin-right: 0px;*/
+  margin: 0px;
+}
+
 richgriditem image {
   width: 24px;
   height: 24px;
   list-style-image: url("chrome://browser/skin/images/identity-icons-generic.png");
 }
 
 /* Dialogs ----------------------------------------------------------------- */