Bug 1126188: Show suggested tile explanation text under a suggested tile. r=adw, a=sylvestre
authorMarina Samuel <msamuel@mozilla.com>
Fri, 20 Mar 2015 16:39:09 -0400
changeset 258191 e9021ea8d7ca
parent 258190 1a007e477655
child 258192 db2b58500934
push id4619
push useredilee@gmail.com
push date2015-04-02 05:50 +0000
treeherdermozilla-beta@daf8a9291a9b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersadw, sylvestre
bugs1126188
milestone38.0
Bug 1126188: Show suggested tile explanation text under a suggested tile. r=adw, a=sylvestre
browser/base/content/newtab/grid.js
browser/base/content/newtab/newTab.css
browser/base/content/newtab/sites.js
browser/base/content/test/newtab/browser_newtab_drag_drop.js
browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js
browser/modules/DirectoryLinksProvider.jsm
browser/themes/shared/newtab/newTab.inc.css
--- a/browser/base/content/newtab/grid.js
+++ b/browser/base/content/newtab/grid.js
@@ -163,17 +163,18 @@ let gGrid = {
       '  <span class="newtab-thumbnail"/>' +
       '  <span class="newtab-thumbnail enhanced-content"/>' +
       '  <span class="newtab-title"/>' +
       '</a>' +
       '<input type="button" title="' + newTabString("pin") + '"' +
       '       class="newtab-control newtab-control-pin"/>' +
       '<input type="button" title="' + newTabString("block") + '"' +
       '       class="newtab-control newtab-control-block"/>' +
-      '<span class="newtab-sponsored">' + newTabString("sponsored.button") + '</span>';
+      '<span class="newtab-sponsored">' + newTabString("sponsored.button") + '</span>' +
+      '<span class="newtab-suggested"/>';
 
     this._siteFragment = document.createDocumentFragment();
     this._siteFragment.appendChild(site);
   },
 
   /**
    * Make sure the correct number of rows and columns are visible
    */
@@ -184,17 +185,18 @@ let gGrid = {
     // Same goes for the grid if that's not ready yet.
     if (!this.isDocumentLoaded || !this._ready) {
       return;
     }
 
     // Save the cell's computed height/width including margin and border
     if (this._cellMargin === undefined) {
       let refCell = document.querySelector(".newtab-cell");
-      this._cellMargin = parseFloat(getComputedStyle(refCell).marginTop) * 2;
+      this._cellMargin = parseFloat(getComputedStyle(refCell).marginTop) +
+        parseFloat(getComputedStyle(refCell).marginBottom);
       this._cellHeight = refCell.offsetHeight + this._cellMargin;
       this._cellWidth = refCell.offsetWidth + this._cellMargin;
     }
 
     let availSpace = document.documentElement.clientHeight - this._cellMargin -
                      document.querySelector("#newtab-search-container").offsetHeight;
     let visibleRows = Math.floor(availSpace / this._cellHeight);
     this._node.style.height = this._computeHeight() + "px";
--- a/browser/base/content/newtab/newTab.css
+++ b/browser/base/content/newtab/newTab.css
@@ -154,17 +154,17 @@ input[type=button] {
 #newtab-grid[page-disabled] {
   pointer-events: none;
 }
 
 /* CELLS */
 .newtab-cell {
   display: -moz-box;
   height: 180px;
-  margin: 20px 10px;
+  margin: 20px 10px 85px;
   width: 290px;
 }
 
 /* SITES */
 .newtab-site {
   position: relative;
   -moz-box-flex: 1;
   transition: 100ms ease-out;
@@ -188,30 +188,55 @@ input[type=button] {
   left: 0;
   top: 0;
   right: 0;
   bottom: 0;
 }
 
 /* TITLES */
 .newtab-sponsored,
-.newtab-title {
-  bottom: -26px;
+.newtab-title,
+.newtab-suggested {
   overflow: hidden;
   position: absolute;
   right: 0;
   text-align: center;
+}
+
+.newtab-sponsored,
+.newtab-title {
+  bottom: -26px;
   white-space: nowrap;
+  text-overflow: ellipsis;
+  font-size: 13px;
+}
+
+.newtab-suggested {
+  border: 1px solid #dcdcdc;
+  border-radius: 2px;
+  cursor: pointer;
+  font-size: 12px;
+  height: 17px;
+  line-height: 17px;
+  margin-bottom: -1px;
+  padding: 2px 8px;
+  display: none;
+  margin-left: auto;
+  margin-right: auto;
+  left: 0;
+  top: 215px;
+}
+
+.newtab-suggested-bounds {
+  max-height: 51px; /* 51 / 17 = 3 lines maximum */
 }
 
 .newtab-title {
-  font-size: 13px;
   left: 0;
   padding-top: 14px;
-  text-overflow: ellipsis;
 }
 
 .newtab-sponsored {
   border: 1px solid #dcdcdc;
   border-radius: 2px;
   cursor: pointer;
   display: none;
   font-family: Arial;
@@ -232,16 +257,20 @@ input[type=button] {
   left: 0;
   right: auto;
 }
 
 .newtab-site:-moz-any([type=enhanced], [type=sponsored]) .newtab-sponsored {
   display: block;
 }
 
+.newtab-site[type=related] .newtab-suggested {
+  display: table;
+}
+
 .sponsored-explain,
 .sponsored-explain a {
   color: white;
 }
 
 .sponsored-explain {
   background-color: rgba(51, 51, 51, 0.95);
   border-bottom-left-radius: 6px;
--- a/browser/base/content/newtab/sites.js
+++ b/browser/base/content/newtab/sites.js
@@ -126,16 +126,22 @@ Site.prototype = {
     let tooltip = (title == url ? title : title + "\n" + url);
 
     let link = this._querySelector(".newtab-link");
     link.setAttribute("title", tooltip);
     link.setAttribute("href", url);
     this._querySelector(".newtab-title").textContent = title;
     this.node.setAttribute("type", this.link.type);
 
+    if (this.link.targetedSite) {
+      let targetedSite = `<strong> ${this.link.targetedSite} </strong>`;
+      this._querySelector(".newtab-suggested").innerHTML =
+        `<div class='newtab-suggested-bounds'>Suggested for ${targetedSite} visitors</div>`;
+    }
+
     if (this.isPinned())
       this._updateAttributes(true);
     // Capture the page if the thumbnail is missing, which will cause page.js
     // to be notified and call our refreshThumbnail() method.
     this.captureIfMissing();
     // but still display whatever thumbnail might be available now.
     this.refreshThumbnail();
   },
@@ -172,33 +178,40 @@ Site.prototype = {
 
       if (this.link.type != link.type) {
         this.node.setAttribute("type", "enhanced");
         this.enhancedId = link.directoryId;
       }
     }
   },
 
+  _ignoreHoverEvents: function(element) {
+    element.addEventListener("mouseover", () => {
+      this.cell.node.setAttribute("ignorehover", "true");
+    });
+    element.addEventListener("mouseout", () => {
+      this.cell.node.removeAttribute("ignorehover");
+    });
+  },
+
   /**
    * Adds event handlers for the site and its buttons.
    */
   _addEventHandlers: function Site_addEventHandlers() {
     // Register drag-and-drop event handlers.
     this._node.addEventListener("dragstart", this, false);
     this._node.addEventListener("dragend", this, false);
     this._node.addEventListener("mouseover", this, false);
 
-    // Specially treat the sponsored icon to prevent regular hover effects
+    // Specially treat the sponsored icon & suggested explanation
+    // text to prevent regular hover effects
     let sponsored = this._querySelector(".newtab-sponsored");
-    sponsored.addEventListener("mouseover", () => {
-      this.cell.node.setAttribute("ignorehover", "true");
-    });
-    sponsored.addEventListener("mouseout", () => {
-      this.cell.node.removeAttribute("ignorehover");
-    });
+    let suggested = this._querySelector(".newtab-suggested");
+    this._ignoreHoverEvents(sponsored);
+    this._ignoreHoverEvents(suggested);
   },
 
   /**
    * Speculatively opens a connection to the current site.
    */
   _speculativeConnect: function Site_speculativeConnect() {
     let sc = Services.io.QueryInterface(Ci.nsISpeculativeConnect);
     let uri = Services.io.newURI(this.url, null, null);
@@ -263,16 +276,22 @@ Site.prototype = {
       }
     }
     // Handle sponsored explanation link click
     else if (target.parentElement.classList.contains("sponsored-explain")) {
       action = "sponsored_link";
     }
     // Only handle primary clicks for the remaining targets
     else if (button == 0) {
+      if (target.parentElement.classList.contains("newtab-suggested") ||
+          target.classList.contains("newtab-suggested")) {
+        // Suggested explanation text should do nothing when clicked and
+        // the link in the suggested explanation should act as default.
+        return;
+      }
       aEvent.preventDefault();
       if (target.classList.contains("newtab-control-block")) {
         this.block();
         action = "block";
       }
       else if (target.classList.contains("sponsored-explain") ||
                target.classList.contains("newtab-sponsored")) {
         this._toggleSponsored();
--- a/browser/base/content/test/newtab/browser_newtab_drag_drop.js
+++ b/browser/base/content/test/newtab/browser_newtab_drag_drop.js
@@ -55,18 +55,18 @@ function runTests() {
   // pinned sites should not be pushed out of the grid (unless there are only
   // pinned ones left on the grid)
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",,,,,,,7,8");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7p,8p");
 
-  yield simulateDrop(2, 8);
-  checkGrid("0,1,3,4,5,6,7p,8p,2p");
+  yield simulateDrop(2, 5);
+  checkGrid("0,1,3,4,5,2p,6,7p,8p");
 
   // make sure that pinned sites are re-positioned correctly
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("0,1,2,,,5");
 
   yield addNewTabPageTab();
   checkGrid("0p,1p,2p,3,4,5p,6,7,8");
 
--- a/browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js
+++ b/browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js
@@ -30,29 +30,29 @@ function runTests() {
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",,,,,,,7,8");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7p,8p");
 
   // force the grid to be small enough that a pinned cell could be pushed out
   Services.prefs.setIntPref(PREF_NEWTAB_COLUMNS, 3);
-  yield simulateExternalDrop(7);
-  checkGrid("0,1,2,3,4,5,7p,99p,8p");
+  yield simulateExternalDrop(5);
+  checkGrid("0,1,2,3,4,99p,5,7p,8p");
 
   // drag a new site beneath a pinned cell and make sure the pinned cell is
   // not moved
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",,,,,,,,8");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7,8p");
 
-  yield simulateExternalDrop(7);
-  checkGrid("0,1,2,3,4,5,6,99p,8p");
+  yield simulateExternalDrop(5);
+  checkGrid("0,1,2,3,4,99p,5,6,8p");
 
   // drag a new site onto a block of pinned sites and make sure they're shifted
   // around accordingly
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("0,1,2,,,,,,");
 
   yield addNewTabPageTab();
   checkGrid("0p,1p,2p");
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -558,34 +558,49 @@ let DirectoryLinksProvider = {
     }
 
     // Create a flat list of all possible links we can show as related.
     // Note that many top sites may map to the same related links, but we only
     // want to count each related link once (based on url), thus possibleLinks is a map
     // from url to relatedLink. Thus, each link has an equal chance of being chosen at
     // random from flattenedLinks if it appears only once.
     let possibleLinks = new Map();
+    let targetedSites = new Map();
     this._topSitesWithRelatedLinks.forEach(topSiteWithRelatedLink => {
       let relatedLinksMap = this._relatedLinks.get(topSiteWithRelatedLink);
       relatedLinksMap.forEach((relatedLink, url) => {
         possibleLinks.set(url, relatedLink);
+
+        // Keep a map of URL to targeted sites. We later use this to show the user
+        // what site they visited to trigger this suggestion.
+        if (!targetedSites.get(url)) {
+          targetedSites.set(url, []);
+        }
+        targetedSites.get(url).push(topSiteWithRelatedLink);
       })
     });
     let flattenedLinks = [...possibleLinks.values()];
 
     // Choose our related link at random
     let relatedIndex = Math.floor(Math.random() * flattenedLinks.length);
     let chosenRelatedLink = flattenedLinks[relatedIndex];
 
     // Show the new directory tile.
     this._callObservers("onLinkChanged", {
       url: chosenRelatedLink.url,
+      title: chosenRelatedLink.title,
       frecency: RELATED_FRECENCY,
       lastVisitDate: chosenRelatedLink.lastVisitDate,
       type: "related",
+
+      // Choose the first site a user has visited as the target. In the future,
+      // this should be the site with the highest frecency. However, we currently
+      // store frecency by URL not by site.
+      targetedSite: targetedSites.get(chosenRelatedLink.url).length ?
+        targetedSites.get(chosenRelatedLink.url)[0] : null
     });
     return chosenRelatedLink;
    },
 
   /**
    * Return the object to its pre-init state
    */
   reset: function DirectoryLinksProvider_reset() {
--- a/browser/themes/shared/newtab/newTab.inc.css
+++ b/browser/themes/shared/newtab/newTab.inc.css
@@ -117,35 +117,40 @@
   background-origin: padding-box;
   background-clip: padding-box;
   background-repeat: no-repeat;
   background-size: cover;
   border-radius: inherit;
   transition: opacity 100ms ease-out;
 }
 
-.newtab-site:hover .newtab-thumbnail.enhanced-content {
+.newtab-cell:not([ignorehover]) .newtab-site:hover .newtab-thumbnail.enhanced-content {
   opacity: 0;
 }
 
 .newtab-site[type=affiliate] .newtab-thumbnail,
 .newtab-site[type=enhanced] .newtab-thumbnail,
 .newtab-site[type=organic] .newtab-thumbnail,
 .newtab-site[type=sponsored] .newtab-thumbnail {
   background-position: center center;
   background-size: auto;
 }
 
 /* TITLES */
 #newtab-intro-what,
 .newtab-sponsored,
-.newtab-title {
+.newtab-title,
+.newtab-suggested  {
   color: #5c5c5c;
 }
 
+.newtab-suggested {
+  background-color: white;
+}
+
 .newtab-site:hover .newtab-title {
   color: #222;
 }
 
 .newtab-site[pinned] .newtab-title {
   padding: 0 15px;
 }