Bug 1156921 - Backout Suggested Tiles (bug 1120311) from 38.0 [a=sylvestre]
authorEd Lee <edilee@mozilla.com>
Tue, 21 Apr 2015 12:08:41 -0700
changeset 260223 d7ca3b75c842
parent 260222 d27c9211ebb3
child 260224 1cd478c3e0b5
push id722
push useredilee@gmail.com
push date2015-04-22 07:00 +0000
treeherdermozilla-release@d7ca3b75c842 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssylvestre
bugs1156921, 1120311
milestone38.0
Bug 1156921 - Backout Suggested Tiles (bug 1120311) from 38.0 [a=sylvestre]
browser/app/profile/firefox.js
browser/base/content/newtab/grid.js
browser/base/content/newtab/newTab.css
browser/base/content/newtab/page.js
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/base/content/test/newtab/browser_newtab_drop_preview.js
browser/base/content/test/newtab/browser_newtab_enhanced.js
browser/base/content/test/newtab/browser_newtab_reportLinkAction.js
browser/base/content/test/newtab/head.js
browser/modules/DirectoryLinksProvider.jsm
browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
browser/themes/shared/newtab/newTab.inc.css
toolkit/modules/NewTabUtils.jsm
toolkit/modules/tests/xpcshell/test_NewTabUtils.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1636,20 +1636,20 @@ pref("browser.newtabpage.enabled", true)
 
 // number of rows of newtab grid
 pref("browser.newtabpage.rows", 3);
 
 // number of columns of newtab grid
 pref("browser.newtabpage.columns", 5);
 
 // directory tiles download URL
-pref("browser.newtabpage.directory.source", "https://tiles.services.mozilla.com/v3/links/fetch/%LOCALE%/%CHANNEL%");
+pref("browser.newtabpage.directory.source", "https://tiles.services.mozilla.com/v2/links/fetch/%LOCALE%");
 
 // endpoint to send newtab click and view pings
-pref("browser.newtabpage.directory.ping", "https://tiles.services.mozilla.com/v3/links/");
+pref("browser.newtabpage.directory.ping", "https://tiles.services.mozilla.com/v2/links/");
 
 // Enable the DOM fullscreen API.
 pref("full-screen-api.enabled", true);
 
 // True if the fullscreen API requires approval upon a domain entering fullscreen.
 // Domains that have already had fullscreen permission granted won't re-request
 // approval.
 pref("full-screen-api.approval-required", true);
--- a/browser/base/content/newtab/grid.js
+++ b/browser/base/content/newtab/grid.js
@@ -163,18 +163,17 @@ 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-suggested"/>';
+      '<span class="newtab-sponsored">' + newTabString("sponsored.button") + '</span>';
 
     this._siteFragment = document.createDocumentFragment();
     this._siteFragment.appendChild(site);
   },
 
   /**
    * Make sure the correct number of rows and columns are visible
    */
@@ -185,19 +184,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);
-      this._cellHeight = refCell.offsetHeight + this._cellMargin +
-        parseFloat(getComputedStyle(refCell).marginBottom);
+      this._cellMargin = parseFloat(getComputedStyle(refCell).marginTop) * 2;
+      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";
     this._node.style.maxHeight = this._computeHeight(visibleRows) + "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 68px;
+  margin: 20px 10px;
   width: 290px;
 }
 
 /* SITES */
 .newtab-site {
   position: relative;
   -moz-box-flex: 1;
   transition: 100ms ease-out;
@@ -188,55 +188,30 @@ input[type=button] {
   left: 0;
   top: 0;
   right: 0;
   bottom: 0;
 }
 
 /* TITLES */
 .newtab-sponsored,
-.newtab-title,
-.newtab-suggested {
+.newtab-title {
+  bottom: -26px;
   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: 34px; /* 34 / 17 = 2 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;
@@ -257,42 +232,34 @@ input[type=button] {
   left: 0;
   right: auto;
 }
 
 .newtab-site:-moz-any([type=enhanced], [type=sponsored]) .newtab-sponsored {
   display: block;
 }
 
-.newtab-site[suggested] .newtab-suggested {
-  display: table;
-}
-
 .sponsored-explain,
-.sponsored-explain a,
-.suggested-explain,
-.suggested-explain a {
+.sponsored-explain a {
   color: white;
 }
 
-.sponsored-explain,
-.suggested-explain {
+.sponsored-explain {
   background-color: rgba(51, 51, 51, 0.95);
   border-bottom-left-radius: 6px;
   border-bottom-right-radius: 6px;
   bottom: 0px;
   line-height: 20px;
   padding: 15px 10px;
   position: absolute;
   text-align: start;
 }
 
 #newtab-intro-panel input,
-.sponsored-explain input,
-.suggested-explain input {
+.sponsored-explain input {
   background-size: 18px;
   height: 18px;
   opacity: 1;
   pointer-events: none;
   position: static;
   width: 18px;
 }
 
@@ -490,17 +457,17 @@ input[type=button] {
 #newtab-customize-panel > .panel-arrowcontainer > .panel-arrowcontent,
 #newtab-search-panel > .panel-arrowcontainer > .panel-arrowcontent {
   padding: 0;
 }
 
 .newtab-customize-panel-item,
 .newtab-search-panel-engine,
 #newtab-search-manage {
-  padding: 10px 10px 10px 25px;
+  padding: 4px 24px;
 }
 
 .newtab-customize-panel-item:not(:last-child),
 .newtab-search-panel-engine {
   border-bottom: 1px solid threedshadow;
 }
 
 .newtab-search-panel-engine > image {
@@ -512,20 +479,16 @@ input[type=button] {
 
 .newtab-customize-panel-item > label,
 .newtab-search-panel-engine > label,
 #newtab-search-manage > label {
   padding: 0;
   margin: 0;
 }
 
-.newtab-customize-panel-item:not([selected]) {
-  color: #919191;
-}
-
 .newtab-customize-panel-item[selected],
 .newtab-search-panel-engine[selected] {
   background: url("chrome://global/skin/menu/shared-menu-check.png") center left 4px no-repeat transparent;
   background-size: 16px 16px;
 }
 
 @media (min-resolution: 2dppx) {
   .newtab-customize-panel-item[selected],
--- a/browser/base/content/newtab/page.js
+++ b/browser/base/content/newtab/page.js
@@ -36,24 +36,16 @@ let gPage = {
 
     // Initialize customize controls.
     gCustomize.init();
 
     // Initialize intro panel.
     gIntro.init();
   },
 
-  _updateCogMenuStringsForEnUS: function() {
-    if (DirectoryLinksProvider.locale == "en-US") {
-      document.querySelector("#newtab-customize-enhanced label").innerHTML = "Show suggested and your top sites";
-      document.querySelector("#newtab-customize-classic label").innerHTML = "Show your top sites";
-      document.querySelector("#newtab-customize-blank label").innerHTML = "Show blank page";
-    }
-  },
-
   /**
    * Listens for notifications specific to this page.
    */
   observe: function Page_observe(aSubject, aTopic, aData) {
     if (aTopic == "nsPref:changed") {
       gCustomize.updateSelected();
 
       let enabled = gAllPages.enabled;
@@ -147,31 +139,29 @@ let gPage = {
 #endif
   },
 
   /**
    * Updates the 'page-disabled' attributes of the respective DOM nodes.
    * @param aValue Whether the New Tab Page is enabled or not.
    */
   _updateAttributes: function Page_updateAttributes(aValue) {
-    this._updateCogMenuStringsForEnUS();
-
     // Set the nodes' states.
     let nodeSelector = "#newtab-scrollbox, #newtab-grid, #newtab-search-container";
     for (let node of document.querySelectorAll(nodeSelector)) {
       if (aValue)
         node.removeAttribute("page-disabled");
       else
         node.setAttribute("page-disabled", "true");
     }
 
     // Enables/disables the control and link elements.
     let inputSelector = ".newtab-control, .newtab-link";
     for (let input of document.querySelectorAll(inputSelector)) {
-      if (aValue)
+      if (aValue) 
         input.removeAttribute("tabindex");
       else
         input.setAttribute("tabindex", "-1");
     }
   },
 
   /**
    * Handles all page events.
--- a/browser/base/content/newtab/sites.js
+++ b/browser/base/content/newtab/sites.js
@@ -126,23 +126,16 @@ 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) {
-      this.node.setAttribute("suggested", true);
-      let targetedSite = `<strong> ${this.link.targetedName} </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();
   },
@@ -179,40 +172,33 @@ 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 & suggested explanation
-    // text to prevent regular hover effects
+    // Specially treat the sponsored icon to prevent regular hover effects
     let sponsored = this._querySelector(".newtab-sponsored");
-    let suggested = this._querySelector(".newtab-suggested");
-    this._ignoreHoverEvents(sponsored);
-    this._ignoreHoverEvents(suggested);
+    sponsored.addEventListener("mouseover", () => {
+      this.cell.node.setAttribute("ignorehover", "true");
+    });
+    sponsored.addEventListener("mouseout", () => {
+      this.cell.node.removeAttribute("ignorehover");
+    });
   },
 
   /**
    * 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);
@@ -229,40 +215,35 @@ Site.prototype = {
       // We only want to get indices for the default configuration, everything
       // else goes in the same bucket.
       aIndex = 9;
     }
     Services.telemetry.getHistogramById("NEWTAB_PAGE_SITE_CLICKED")
                       .add(aIndex);
   },
 
-  _toggleLegalText: function(buttonClass, explanationTextClass) {
-    let button = this._querySelector(buttonClass);
+  _toggleSponsored: function() {
+    let button = this._querySelector(".newtab-sponsored");
     if (button.hasAttribute("active")) {
-      let explain = this._querySelector(explanationTextClass);
+      let explain = this._querySelector(".sponsored-explain");
       explain.parentNode.removeChild(explain);
 
       button.removeAttribute("active");
     }
     else {
       let explain = document.createElementNS(HTML_NAMESPACE, "div");
-      explain.className = explanationTextClass.slice(1); // Slice off the first character, '.'
+      explain.className = "sponsored-explain";
       this.node.appendChild(explain);
 
       let link = '<a href="' + TILES_EXPLAIN_LINK + '">' +
                  newTabString("learn.link") + "</a>";
-      let type = this.node.getAttribute("suggested") ? "suggested" : this.node.getAttribute("type");
+      let type = this.node.getAttribute("type");
       let icon = '<input type="button" class="newtab-control newtab-' +
-                 (type == "enhanced" ? "customize" : "control-block") + '"/>';
-
-      if (type == "suggested") {
-        explain.innerHTML = `This site is suggested to you by Mozilla. You can remove it at any time by clicking the ${icon} button. ${link}`;
-      } else {
-        explain.innerHTML = newTabString(type + ".explain", [icon, link]);
-      }
+                 (type == "sponsored" ? "control-block" : "customize") + '"/>';
+      explain.innerHTML = newTabString(type + ".explain", [icon, link]);
 
       button.setAttribute("active", "true");
     }
   },
 
   /**
    * Handles site click events.
    */
@@ -280,38 +261,28 @@ Site.prototype = {
         this._recordSiteClicked(tileIndex);
         action = "click";
       }
     }
     // Handle sponsored explanation link click
     else if (target.parentElement.classList.contains("sponsored-explain")) {
       action = "sponsored_link";
     }
-    else if (target.parentElement.classList.contains("suggested-explain")) {
-      action = "suggested_link";
-    }
     // Only handle primary clicks for the remaining targets
     else if (button == 0) {
       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._toggleLegalText(".newtab-sponsored", ".sponsored-explain");
+        this._toggleSponsored();
         action = "sponsored";
       }
-      else if (target.classList.contains("suggested-explain") ||
-               target.classList.contains("newtab-suggested-bounds") ||
-               target.parentElement.classList.contains("newtab-suggested-bounds") ||
-               target.classList.contains("newtab-suggested")) {
-        this._toggleLegalText(".newtab-suggested", ".suggested-explain");
-        action = "suggested";
-      }
       else if (pinned) {
         this.unpin();
         action = "unpin";
       }
       else {
         this.pin();
         action = "pin";
       }
--- a/browser/base/content/test/newtab/browser_newtab_drag_drop.js
+++ b/browser/base/content/test/newtab/browser_newtab_drag_drop.js
@@ -4,17 +4,16 @@
 /*
  * These tests make sure that dragging and dropping sites works as expected.
  * Sites contained in the grid need to shift around to indicate the result
  * of the drag-and-drop operation. If the grid is full and we're dragging
  * a new site into it another one gets pushed out.
  */
 function runTests() {
   requestLongerTimeout(2);
-  yield addNewTabPageTab();
 
   // test a simple drag-and-drop scenario
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7,8");
 
@@ -55,18 +54,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, 5);
-  checkGrid("0,1,3,4,5,2p,6,7p,8p");
+  yield simulateDrop(2, 8);
+  checkGrid("0,1,3,4,5,6,7p,8p,2p");
 
   // 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
@@ -8,17 +8,16 @@ const PREF_NEWTAB_COLUMNS = "browser.new
  * Sites contained in the grid need to shift around to indicate the result
  * of the drag-and-drop operation. If the grid is full and we're dragging
  * a new site into it another one gets pushed out.
  * This is a continuation of browser_newtab_drag_drop.js
  * to decrease test run time, focusing on external sites.
  */
 function runTests() {
   registerCleanupFunction(_ => Services.prefs.clearUserPref(PREF_NEWTAB_COLUMNS));
-  yield addNewTabPageTab();
 
   // drag a new site onto the very first cell
   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");
 
@@ -30,29 +29,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(5);
-  checkGrid("0,1,2,3,4,99p,5,7p,8p");
+  yield simulateExternalDrop(7);
+  checkGrid("0,1,2,3,4,5,7p,99p,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(5);
-  checkGrid("0,1,2,3,4,99p,5,6,8p");
+  yield simulateExternalDrop(7);
+  checkGrid("0,1,2,3,4,5,6,99p,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/base/content/test/newtab/browser_newtab_drop_preview.js
+++ b/browser/base/content/test/newtab/browser_newtab_drop_preview.js
@@ -1,18 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /*
  * These tests ensure that the drop preview correctly arranges sites when
  * dragging them around.
  */
 function runTests() {
-  yield addNewTabPageTab();
-
   // the first three sites are pinned - make sure they're re-arranged 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");
 
   let cw = getContentWindow();
--- a/browser/base/content/test/newtab/browser_newtab_enhanced.js
+++ b/browser/base/content/test/newtab/browser_newtab_enhanced.js
@@ -1,41 +1,25 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const PRELOAD_PREF = "browser.newtab.preload";
 
 gDirectorySource = "data:application/json," + JSON.stringify({
-  "enhanced": [{
+  "en-US": [{
     url: "http://example.com/",
     enhancedImageURI: "data:image/png;base64,helloWORLD",
     title: "title",
-    type: "organic",
-  }],
-  "directory": [{
-    url: "http://example1.com/",
-    enhancedImageURI: "data:image/png;base64,helloWORLD2",
-    title: "title1",
     type: "organic"
-  }],
-  "suggested": [{
-    url: "http://example1.com/2",
-    imageURI: "data:image/png;base64,helloWORLD3",
-    title: "title2",
-    type: "affiliate",
-    frecent_sites: ["test.com"]
   }]
 });
 
 function runTests() {
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
   let origEnhanced = NewTabUtils.allPages.enhanced;
   registerCleanupFunction(() => {
-    DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
     Services.prefs.clearUserPref(PRELOAD_PREF);
     NewTabUtils.allPages.enhanced = origEnhanced;
   });
 
   Services.prefs.setBoolPref(PRELOAD_PREF, false);
 
   function getData(cellNum) {
     let cell = getCell(cellNum);
@@ -44,102 +28,54 @@ function runTests() {
     let siteNode = cell.site.node;
     return {
       type: siteNode.getAttribute("type"),
       enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
       title: siteNode.querySelector(".newtab-title").textContent,
     };
   }
 
-  // Make the page have a directory link, enhanced link, and history link
+  // Make the page have a directory link followed by a history link
   yield setLinks("-1");
 
   // Test with enhanced = false
   yield addNewTabPageTab();
   yield customizeNewTabPage("classic");
   let {type, enhanced, title} = getData(0);
-  isnot(type, "enhanced", "history link is not enhanced");
-  is(enhanced, "", "history link has no enhanced image");
-  is(title, "site#-1");
+  is(type, "organic", "directory link is organic");
+  isnot(enhanced, "", "directory link has enhanced image");
+  is(title, "title");
 
-  is(getData(1), null, "there is only one link and it's a history link");
+  is(getData(1), null, "history link pushed out by directory link");
 
   // Test with enhanced = true
   yield addNewTabPageTab();
   yield customizeNewTabPage("enhanced");
   ({type, enhanced, title} = getData(0));
-  is(type, "organic", "directory link is organic");
-  isnot(enhanced, "", "directory link has enhanced image");
-  is(title, "title1");
-
-  ({type, enhanced, title} = getData(1));
-  is(type, "enhanced", "history link is enhanced");
-  isnot(enhanced, "", "history link has enhanced image");
+  is(type, "organic", "directory link is still organic");
+  isnot(enhanced, "", "directory link still has enhanced image");
   is(title, "title");
 
-  is(getData(2), null, "there are only 2 links, directory and enhanced history");
+  is(getData(1), null, "history link still pushed out by directory link");
 
   // Test with a pinned link
   setPinnedLinks("-1");
   yield addNewTabPageTab();
   ({type, enhanced, title} = getData(0));
   is(type, "enhanced", "pinned history link is enhanced");
   isnot(enhanced, "", "pinned history link has enhanced image");
   is(title, "title");
 
-  ({type, enhanced, title} = getData(1));
-  is(type, "organic", "directory link is organic");
-  isnot(enhanced, "", "directory link has enhanced image");
-  is(title, "title1");
-
-  is(getData(2), null, "directory link pushed out by pinned history link");
+  is(getData(1), null, "directory link pushed out by pinned history link");
 
   // Test pinned link with enhanced = false
   yield addNewTabPageTab();
   yield customizeNewTabPage("classic");
   ({type, enhanced, title} = getData(0));
   isnot(type, "enhanced", "history link is not enhanced");
   is(enhanced, "", "history link has no enhanced image");
   is(title, "site#-1");
 
   is(getData(1), null, "directory link still pushed out by pinned history link");
 
   ok(getContentDocument().getElementById("newtab-intro-what"),
      "'What is this page?' link exists");
-
-  yield unpinCell(0);
-
-
-
-  // Test that a suggested tile is not enhanced by a directory tile
-  let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
-  NewTabUtils.isTopPlacesSite = () => true;
-  yield setLinks("-1");
-
-  // Test with enhanced = false
-  yield addNewTabPageTab();
-  yield customizeNewTabPage("classic");
-  ({type, enhanced, title} = getData(0));
-  isnot(type, "enhanced", "history link is not enhanced");
-  is(enhanced, "", "history link has no enhanced image");
-  is(title, "site#-1");
-
-  is(getData(1), null, "there is only one link and it's a history link");
-
-
-  // Test with enhanced = true
-  yield addNewTabPageTab();
-  yield customizeNewTabPage("enhanced");
-
-  // Suggested link was not enhanced by directory link with same domain
-  ({type, enhanced, title} = getData(0));
-  is(type, "affiliate", "suggested link is affiliate");
-  is(enhanced, "", "suggested link has no enhanced image");
-  is(title, "title2");
-
-  // Enhanced history link shows up second
-  ({type, enhanced, title} = getData(1));
-  is(type, "enhanced", "pinned history link is enhanced");
-  isnot(enhanced, "", "pinned history link has enhanced image");
-  is(title, "title");
-
-  is(getData(2), null, "there is only a suggested link followed by an enhanced history link");
 }
--- a/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js
+++ b/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const PRELOAD_PREF = "browser.newtab.preload";
 
 gDirectorySource = "data:application/json," + JSON.stringify({
-  "directory": [{
+  "en-US": [{
     url: "http://example.com/organic",
     type: "organic"
   }, {
     url: "http://localhost/sponsored",
     type: "sponsored"
   }]
 });
 
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -291,18 +291,17 @@ function fillHistory(aLinks, aCallback =
  */
 function setPinnedLinks(aLinks) {
   let links = aLinks;
 
   if (typeof links == "string") {
     links = aLinks.split(/\s*,\s*/).map(function (id) {
       if (id)
         return {url: "http://example" + (id != "-1" ? id : "") + ".com/",
-                title: "site#" + id,
-                type: "history"};
+                title: "site#" + id};
     });
   }
 
   let string = Cc["@mozilla.org/supports-string;1"]
                  .createInstance(Ci.nsISupportsString);
   string.data = JSON.stringify(links);
   Services.prefs.setComplexValue("browser.newtabpage.pinned",
                                  Ci.nsISupportsString, string);
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -10,28 +10,25 @@ const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 const XMLHttpRequest =
   Components.Constructor("@mozilla.org/xmlextras/xmlhttprequest;1", "nsIXMLHttpRequest");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/Timer.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
   "resource://gre/modules/NewTabUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
   "resource://gre/modules/osfile.jsm")
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
-  "resource://gre/modules/UpdateChannel.jsm");
 XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => {
   return new TextDecoder();
 });
 
 // The filename where directory links are stored locally
 const DIRECTORY_LINKS_FILE = "directoryLinks.json";
 const DIRECTORY_LINKS_TYPE = "application/json";
 
@@ -45,61 +42,25 @@ const PREF_SELECTED_LOCALE = "general.us
 const PREF_DIRECTORY_SOURCE = "browser.newtabpage.directory.source";
 
 // The preference that tells where to send click/view pings
 const PREF_DIRECTORY_PING = "browser.newtabpage.directory.ping";
 
 // The preference that tells if newtab is enhanced
 const PREF_NEWTAB_ENHANCED = "browser.newtabpage.enhanced";
 
-// Only allow explicitly approved frecent sites with display name
-const ALLOWED_FRECENT_SITES = new Map([
-  [ 'airdroid.com,android-developers.blogspot.com,android.com,androidandme.com,androidapplications.com,androidapps.com,androidauthority.com,androidcentral.com,androidcommunity.com,androidfilehost.com,androidforums.com,androidguys.com,androidheadlines.com,androidpit.com,androidpolice.com,androidspin.com,androidtapp.com,androinica.com,droid-life.com,droidforums.net,droidviews.com,droidxforums.com,forum.xda-developers.com,phandroid.com,play.google.com,shopandroid.com,talkandroid.com,theandroidsoul.com,thedroidguy.com,videodroid.org',
-    'Technology' ],
-  [ 'assurancewireless.com,att.com,attsavings.com,boostmobile.com,budgetmobile.com,consumercellular.com,credomobile.com,gosmartmobile.com,h2owirelessnow.com,lycamobile.com,lycamobile.us,metropcs.com,myfamilymobile.com,polarmobile.com,qlinkwireless.com,republicwireless.com,sprint.com,straighttalk.com,t-mobile.com,tracfonewireless.com,verizonwireless.com,virginmobile.com,virginmobile.com.au,virginmobileusa.com,vodafone.co.uk,vodafone.com,vzwshop.com',
-    'Mobile Phone' ],
-  [ 'addons.mozilla.org,air.mozilla.org,blog.mozilla.org,bugzilla.mozilla.org,developer.mozilla.org,etherpad.mozilla.org,forums.mozillazine.org,hacks.mozilla.org,hg.mozilla.org,mozilla.org,planet.mozilla.org,quality.mozilla.org,support.mozilla.org,treeherder.mozilla.org,wiki.mozilla.org',
-    'Mozilla' ],
-  [ '3dprint.com,4sysops.com,access-programmers.co.uk,accountingweb.com,addictivetips.com,adweek.com,afterdawn.com,akihabaranews.com,anandtech.com,appsrumors.com,arstechnica.com,belkin.com,besttechinfo.com,betanews.com,bgr.com,botcrawl.com,breakingmuscle.com,canonrumors.com,cheap-phones.com,chip.de,chip.eu,cio.com,citeworld.com,cleanpcremove.com,cnet.com,commentcamarche.net,computer.org,computerhope.com,computershopper.com,computerweekly.com,contextures.com,coolest-gadgets.com,crn.com,csoonline.com,daniweb.com,data.com,datacenterknowledge.com,ddj.com,devicemag.com,digitaltrends.com,dottech.org,dpreview.com,dslreports.com,edugeek.net,eetimes.com,engadget.com,epic.com,eurekalert.org,eweek.com,experts-exchange.com,extremetech.com,fosshub.com,freesoftwaremagazine.com,funkyspacemonkey.com,futuremark.com,gadgetreview.com,ghacks.net,gizmodo.co.uk,gizmodo.com,globalsecurity.org,greenbot.com,gunup.com,guru3d.com,head-fi.org,hexus.net,hothardware.com,howtoforge.com,idg.com.au,idigitaltimes.com,idownloadblog.com,ihackmyi.com,ilounge.com,infomine.com,informationweek.com,intellireview.com,intomobile.com,iphonehacks.com,ismashphone.com,isource.com,it168.com,itechpost.com,itpro.co.uk,itworld.com,jailbreaknation.com,kioskea.net,laptoping.com,laptopmag.com,lightreading.com,livescience.com,malwaretips.com,mediaroom.com,mobilemag.com,modmyi.com,modmymobile.com,mophie.com,mozillazine.org,neoseeker.com,neowin.net,newscientist.com,newsoxy.com,nextadvisor.com,notebookcheck.com,notebookreview.com,nvidia.com,nwc.com,orafaq.com,osdir.com,osxdaily.com,our-hometown.com,pcadvisor.co.uk,pchome.net,pcmag.com,pconline.com.cn,pcpop.com,pcpro.co.uk,pcreview.co.uk,pcrisk.com,pcwelt.de,phonerebel.com,phonescoop.com,physorg.com,pocket-lint.com,post-theory.com,prnewswire.co.uk,prnewswire.com,programming4.us,quickpwn.com,readwrite.com,redmondpie.com,redorbit.com,reviewed.com,safer-networking.org,sciencedaily.com,sciencenews.org,scientificamerican.com,scientificblogging.com,sciverse.com,servicerow.com,sinfuliphone.com,singularityhub.com,slashdot.org,slashgear.com,softonic.com,softonic.com.br,softonic.fr,sophos.com,space.com,sparkfun.com,speedguide.net,stuff.tv,techdailynews.net,techdirt.com,techeblog.com,techhive.com,techie-buzz.com,technewsworld.com,techniqueworld.com,technobuffalo.com,technologyreview.com,technologytell.com,techpowerup.com,techpp.com,techrepublic.com,techshout.com,techweb.com,techworld.com,techworld.com.au,techworldtweets.com,telecomfile.com,tgdaily.com,theinquirer.net,thenextweb.com,theregister.co.uk,thermofisher.com,theverge.com,thewindowsclub.com,tomsguide.com,tomshardware.com,tomsitpro.com,toptenreviews.com,trustedreviews.com,tuaw.com,tweaktown.com,ubergizmo.com,unwiredview.com,venturebeat.com,wccftech.com,webmonkey.com,webpronews.com,windows7codecs.com,windowscentral.com,windowsitpro.com,windowstechies.com,winsupersite.com,wired.co.uk,wired.com,wp-themes.com,xda-developers.com,xml.com,zdnet.com,zmescience.com,zol.com.cn',
-    'Technology' ],
-  [ '9to5mac.com,appadvice.com,apple.com,appleinsider.com,appleturns.com,appsafari.com,cultofmac.com,everymac.com,insanelymac.com,iphoneunlockspot.com,isource.com,itunes.apple.com,lowendmac.com,mac-forums.com,macdailynews.com,macenstein.com,macgasm.net,macintouch.com,maclife.com,macnews.com,macnn.com,macobserver.com,macosx.com,macpaw.com,macrumors.com,macsales.com,macstories.net,macupdate.com,macuser.co.uk,macworld.co.uk,macworld.com,maxiapple.com,spymac.com,theapplelounge.com',
-    'Technology' ],
-  [ 'alistapart.com,answers.microsoft.com,backpack.openbadges.org,blog.chromium.org,caniuse.com,codefirefox.com,codepen.io,css-tricks.com,css3generator.com,cssdeck.com,csswizardry.com,devdocs.io,docs.angularjs.org,ghacks.net,github.com,html5demos.com,html5rocks.com,html5test.com,iojs.org,khanacademy.org,l10n.mozilla.org,learn.jquery.com,marketplace.firefox.com,mozilla-hispano.org,mozillians.org,news.ycombinator.com,npmjs.com,packagecontrol.io,quirksmode.org,readwrite.com,reps.mozilla.org,smashingmagazine.com,stackoverflow.com,status.modern.ie,teamtreehouse.com,tutorialspoint.com,udacity.com,validator.w3.org,w3.org,w3cschool.cc,w3schools.com,whatcanidoformozilla.org',
-    'Web Development' ],
-  [ 'classroom.google.com,codecademy.com,elearning.ut.ac.id,khanacademy.org,learn.jquery.com,teamtreehouse.com,tutorialspoint.com,udacity.com,w3cschool.cc,w3schools.com',
-    'Web Education' ],
-  [ 'abebooks.co.uk,abebooks.com,alibris.com,allaboutcircuits.com,allyoucanbooks.com,answersingenesis.org,artnet.com,audiobooks.com,barnesandnoble.com,barnesandnobleinc.com,bartleby.com,betterworldbooks.com,biggerbooks.com,bncollege.com,bookbyte.com,bookdepository.com,bookfinder.com,bookrenter.com,booksamillion.com,booksite.com,boundless.com,brookstone.com,btol.com,calibre-ebook.com,campusbookrentals.com,casadellibro.com,cbomc.com,cengagebrain.com,chapters.indigo.ca,christianbook.com,ciscopress.com,coursesmart.com,cqpress.com,crafterschoice.com,crossings.com,cshlp.org,deseretbook.com,directtextbook.com,discountmags.com,doubledaybookclub.com,doubledaylargeprint.com,doverpublications.com,ebooks.com,ecampus.com,fellabooks.net,fictionwise.com,flatworldknowledge.com,grolier.com,harpercollins.com,hayhouse.com,historybookclub.com,hpb.com,hpbmarketplace.com,interweave.com,iseeme.com,katiekazoo.com,knetbooks.com,learnoutloud.com,librarything.com,literaryguild.com,lulu.com,lww.com,macmillan.com,magazines.com,mbsdirect.net,militarybookclub.com,mypearsonstore.com,mysteryguild.com,netplaces.com,noble.com,novelguide.com,onespirit.com,oxfordjournals.org,paperbackswap.com,papy.co.jp,peachpit.com,penguin.com,penguingroup.com,pimsleur.com,powells.com,qpb.com,quepublishing.com,reviews.com,rhapsodybookclub.com,rodalestore.com,royalsocietypublishing.org,sagepub.com,scrubsmag.com,sfbc.com,simonandschuster.com,simonandschuster.net,simpletruths.com,teach12.net,textbooks.com,textbookx.com,thegoodcook.com,thriftbooks.com,tlsbooks.com,toshibabookplace.com,tumblebooks.com,urbookdownload.com,valorebooks.com,valuemags.com,wwnorton.com,zoobooks.com',
-    'Literature' ],
-  [ 'aceshowbiz.com,aintitcoolnews.com,askkissy.com,askmen.com,atraf.co.il,audioboom.com,beamly.com,blippitt.com,bollywoodlife.com,bossip.com,buzzlamp.com,celebdirtylaundry.com,celebfocus.com,celebitchy.com,celebrity-gossip.net,celebrityabout.com,celebwild.com,chisms.net,concertboom.com,crushable.com,cultbox.co.uk,dailyentertainmentnews.com,dayscafe.com,deadline.com,deathandtaxesmag.com,diaryofahollywoodstreetking.com,digitalspy.com,egotastic.com,empirenews.net,enelbrasero.com,everydaycelebs.com,ew.com,extratv.com,facade.com,fanaru.com,fhm.com,geektyrant.com,glamourpage.com,heatworld.com,hlntv.com,hollyscoop.com,hollywoodreporter.com,hollywoodtuna.com,hypable.com,infotransfer.net,insideedition.com,interaksyon.com,jezebel.com,justjared.com,justjaredjr.com,komando.com,koreaboo.com,maxgo.com,maxim.com,maxviral.com,mediatakeout.com,mosthappy.com,moviestalk.com,my.ology.com,ngoisao.net,nofilmschool.com,nolocreo.com,octane.tv,ouchpress.com,people.com,peopleenespanol.com,perezhilton.com,pinkisthenewblog.com,platotv.tv,playbill.com,playbillvault.com,playgroundmag.net,popeater.com,popnhop.com,popsugar.co.uk,popsugar.com,purepeople.com,radaronline.com,rantchic.com,reshareworthy.com,rinkworks.com,ripbird.com,sara-freder.com,screenjunkies.com,soapcentral.com,soapoperadigest.com,sobadsogood.com,splitsider.com,starcasm.net,starpulse.com,straightfromthea.com,stupidcelebrities.net,stupiddope.com,tbn.org,theawesomedaily.com,theawl.com,thefrisky.com,thefw.com,theresacaputo.com,thezooom.com,tvnotas.com.mx,twanatells.com,vanswarpedtour.com,vietgiaitri.com,viral.buzz,vulture.com,wakavision.com,worthytales.net,wwtdd.com,younghollywood.com',
-    'Entertainment News' ],
-  [ '247wallst.com,4-traders.com,advfn.com,agweb.com,allbusiness.com,barchart.com,barrons.com,beckershospitalreview.com,benzinga.com,bizjournals.com,bizsugar.com,bloomberg.com,bloomberglaw.com,business-standard.com,businessinsider.com,businessinsider.com.au,businesspundit.com,businessweek.com,businesswire.com,cboe.com,cheatsheet.com,chicagobusiness.com,cjonline.com,cnbc.com,cnnmoney.com,cqrcengage.com,dailyfinance.com,dailyfx.com,dealbreaker.com,djindexes.com,dowjones.com,easierstreetdaily.com,economist.com,economyandmarkets.com,economywatch.com,edweek.org,eleconomista.es,entrepreneur.com,etfdailynews.com,etfdb.com,ewallstreeter.com,fastcolabs.com,fastcompany.com,financeformulas.net,financialpost.com,flife.de,forbes.com,forexpros.com,fortune.com,foxbusiness.com,ft.com,ftpress.com,fx-exchange.com,hbr.org,howdofinance.com,ibtimes.com,inc.com,investopedia.com,investors.com,investorwords.com,journalofaccountancy.com,kiplinger.com,lendingandcredit.net,lfb.org,mainstreet.com,markettraders.com,marketwatch.com,maxkeiser.com,minyanville.com,ml.com,moneycontrol.com,moneymappress.com,moneynews.com,moneysavingexpert.com,morningstar.com,mortgagenewsdaily.com,motleyfool.com,mt.co.kr,nber.org,nyse.com,oilprice.com,pewsocialtrends.org,principal.com,qz.com,rantfinance.com,realclearmarkets.com,recode.net,reuters.ca,reuters.co.in,reuters.co.uk,reuters.com,rttnews.com,seekingalpha.com,smallbiztrends.com,streetinsider.com,thecheapinvestor.com,theeconomiccollapseblog.com,themoneyconverter.com,thestreet.com,tickertech.com,tradingeconomics.com,updown.com,valuewalk.com,wikinvest.com,wsj.com,zacks.com',
-    'Financial News' ],
-  [ '10tv.com,8newsnow.com,9news.com,abc.net.au,abc7.com,abc7chicago.com,abcnews.go.com,aclu.org,activistpost.com,ajc.com,al.com,alan.com,alarab.net,aljazeera.com,americanthinker.com,app.com,aristeguinoticias.com,azcentral.com,baltimoresun.com,becomingminimalist.com,beforeitsnews.com,bigstory.ap.org,blackamericaweb.com,bloomberg.com,bloombergview.com,boston.com,bostonherald.com,breitbart.com,buffalonews.com,c-span.org,canada.com,cbs46.com,cbsnews.com,chicagotribune.com,chron.com,citizensvoice.com,citylab.com,cleveland.com,cnn.com,coed.com,countercurrentnews.com,courant.com,ctvnews.ca,dailyherald.com,dailynews.com,dallasnews.com,delawareonline.com,democratandchronicle.com,democraticunderground.com,democrats.org,denverpost.com,desmoinesregister.com,dispatch.com,elcomercio.pe,english.aljazeera.net,examiner.com,farsnews.com,firstcoastnews.com,firstpost.com,firsttoknow.com,foreignpolicy.com,foxnews.com,freebeacon.com,freep.com,fresnobee.com,gazette.com,global.nytimes.com,heraldtribune.com,hindustantimes.com,hngn.com,humanevents.com,huzlers.com,indiatimes.com,indystar.com,irishtimes.com,jacksonville.com,jpost.com,jsonline.com,kansascity.com,kctv5.com,kentucky.com,kickerdaily.com,king5.com,kmov.com,knoxnews.com,kpho.com,kvue.com,kwqc.com,kxan.com,lainformacion.com,latimes.com,ldnews.com,lex18.com,linternaute.com,livemint.com,lostateminor.com,m24.ru,macleans.ca,manchestereveningnews.co.uk,marinecorpstimes.com,masslive.com,mavikocaeli.com.tr,mcall.com,medium.com,mentalfloss.com,mercurynews.com,metro.us,miamiherald.com,militarytimes.com,mk.ru,mlive.com,mondotimes.com,montrealgazette.com,msnbc.com,msnewsnow.com,mynews13.com,mysanantonio.com,mysuncoast.com,nbclosangeles.com,nbcnewyork.com,nbcphiladelphia.com,ndtv.com,newindianexpress.com,news.cincinnati.com,news.google.com,news.msn.com,news.yahoo.com,news10.net,news8000.com,newsday.com,newsdaymarketing.net,newsen.com,newsmax.com,newsobserver.com,newsok.com,newsru.ua,newstatesman.com,newszoom.com,nj.com,nola.com,northjersey.com,nouvelobs.com,npr.org,nwfdailynews.com,nwitimes.com,nydailynews.com,nytimes.com,observer.com,ocregister.com,okcfox.com,omaha.com,onenewspage.com,ontheissues.org,oregonlive.com,orlandosentinel.com,palmbeachpost.com,pe.com,pennlive.com,philly.com,pilotonline.com,polar.com,post-gazette.com,postandcourier.com,presstelegram.com,presstv.ir,propublica.org,providencejournal.com,realclearpolitics.com,recorderonline.com,reporterdock.com,reporterherald.com,respublica.al,reuters.com,rg.ru,roanoke.com,sacbee.com,scmp.com,scnow.com,sdpnoticias.com,seattletimes.com,semana.com,sfgate.com,sharepowered.com,sinembargo.mx,slate.com,sltrib.com,sotomayortv.com,sourcewatch.org,spectator.co.uk,squaremirror.com,star-telegram.com,staradvertiser.com,startribune.com,statesman.com,stltoday.com,streetwise.co,stuff.co.nz,success.com,suffolknewsherald.com,sun-sentinel.com,sunnewsnetwork.ca,suntimes.com,supernewschannel.com,surenews.com,svoboda.org,syracuse.com,tampabay.com,tbd.com,telegram.com,telegraph.co.uk,tennessean.com,the-open-mind.com,theadvocate.com,theage.com.au,theatlantic.com,thebarefootwriter.com,theblaze.com,thecalifornian.com,thedailysheeple.com,thefix.com,theintelligencer.net,thelocal.com,thenational.ae,thenewstribune.com,theparisreview.org,thereporter.com,therepublic.com,thestar.com,thetelegram.com,thetimes.co.uk,theuspatriot.com,time.com,timescall.com,timesdispatch.com,timesleaderonline.com,timesofisrael.com,toledoblade.com,toprightnews.com,townhall.com,tpnn.com,trendolizer.com,triblive.com,tribune.com.pk,tricities.com,troymessenger.com,trueactivist.com,truthandaction.org,tsn.ua,tulsaworld.com,twincities.com,upi.com,usatoday.com,utsandiego.com,vagazette.com,viralwomen.com,vitalworldnews.com,voasomali.com,vox.com,washingtonexaminer.com,washingtonpost.com,watchdog.org,wave3.com,wavy.com,wbay.com,wbtw.com,wcpo.com,wctrib.com,wdtn.com,weeklystandard.com,westernjournalism.com,wfsb.com,wgrz.com,whas11.com,winonadailynews.com,wishtv.com,wistv.com,wkbn.com,wkow.com,wlfi.com,wmtw.com,wmur.com,wopular.com,world-top-news.com,worldnews.com,wplol.us,wpsdlocal6.com,wptz.com,wric.com,wsmv.com,wthitv.com,wthr.com,wtnh.com,wtol.com,wtsp.com,wvec.com,wwlp.com,wwltv.com,wyff4.com,yonhapnews.co.kr,yourbreakingnews.com',
-    'News' ],
-  [ '2k.com,360game.vn,4399.com,a10.com,activision.com,addictinggames.com,alawar.com,alienwarearena.com,anagrammer.com,andkon.com,aq.com,arcadeprehacks.com,arcadeyum.com,arcgames.com,archeagegame.com,armorgames.com,askmrrobot.com,battle.net,battlefieldheroes.com,bigfishgames.com,bigpoint.com,bioware.com,bluesnews.com,boardgamegeek.com,bollyheaven.com,bubblebox.com,bukkit.org,bungie.net,buycraft.net,callofduty.com,candystand.com,cda.pl,challonge.com,championselect.net,cheapassgamer.com,cheatcc.com,cheatengine.org,cheathappens.com,chess.com,civfanatics.com,clashofclans-tools.com,clashofclansbuilder.com,comdotgame.com,commonsensemedia.org,coolrom.com,crazygames.com,csgolounge.com,curse.com,d20pfsrd.com,destructoid.com,diablofans.com,diablowiki.net,didigames.com,dota2.com,dota2lounge.com,dressupgames.com,dulfy.net,ebog.com,elderscrollsonline.com,elitedangerous.com,elitepvpers.com,emuparadise.me,enjoydressup.com,escapegames24.com,escapistmagazine.com,eventhubs.com,eveonline.com,farming-simulator.com,feed-the-beast.com,flashgames247.com,flightrising.com,flipline.com,flonga.com,freegames.ws,freeonlinegames.com,fresh-hotel.org,friv.com,friv.today,fullypcgames.net,funny-games.biz,funtrivia.com,futhead.com,g2a.com,gamasutra.com,game-debate.com,game-oldies.com,game321.com,gamebaby.com,gamebaby.net,gamebanana.com,gamefaqs.com,gamefly.com,gamefront.com,gamegape.com,gamehouse.com,gameinformer.com,gamejolt.com,gamemazing.com,gamemeteor.com,gamerankings.com,gamersgate.com,games-msn.com,games-workshop.com,games.com,games2girls.com,gamesbox.com,gamesfreak.net,gametop.com,gametracker.com,gametrailers.com,gamezhero.com,gbatemp.net,geforce.com,gematsu.com,giantbomb.com,girl.me,girlsgames123.com,girlsplay.com,gog.com,gogames.me,gonintendo.com,goodgamestudios.com,gosugamers.net,greenmangaming.com,gtaforums.com,gtainside.com,guildwars2.com,hackedarcadegames.com,hearthpwn.com,hirezstudios.com,hitbox.tv,hltv.org,howrse.com,icy-veins.com,indiedb.com,jayisgames.com,jigzone.com,joystiq.com,juegosdechicas.com,kabam.com,kbhgames.com,kerbalspaceprogram.com,king.com,kixeye.com,kizi.com,kogama.com,kongregate.com,kotaku.com,lolcounter.com,lolking.net,lolnexus.com,lolpro.com,lolskill.net,lootcrate.com,lumosity.com,mafa.com,mangafox.me,mangapark.com,mariowiki.com,maxgames.com,megagames.com,metacritic.com,mindjolt.com,minecraft.net,minecraftforum.net,minecraftservers.org,minecraftskins.com,mineplex.com,miniclip.com,mmo-champion.com,mmobomb.com,mmohuts.com,mmorpg.com,mmosite.com,mobafire.com,moddb.com,modxvm.com,mojang.com,moshimonsters.com,mousebreaker.com,moviestarplanet.com,mtgsalvation.com,muchgames.com,myonlinearcade.com,myplaycity.com,myrealgames.com,mythicspoiler.com,n4g.com,newgrounds.com,nexon.net,nexusmods.com,ninjakiwi.com,nintendo.com,nintendoeverything.com,nintendolife.com,nitrome.com,nosteam.ro,notdoppler.com,noxxic.com,operationsports.com,origin.com,ownedcore.com,pacogames.com,pathofexile.com,pcgamer.com,pch.com,pcsx2.net,penny-arcade.com,planetminecraft.com,plarium.com,playdota.com,playpink.com,playsides.com,playstationlifestyle.net,playstationtrophies.org,pog.com,pokemon.com,polygon.com,popcap.com,primarygames.com,probuilds.net,ps3hax.net,psnprofiles.com,psu.com,qq.com,r2games.com,resourcepack.net,retrogamer.com,rewardtv.com,riotgames.com,robertsspaceindustries.com,roblox.com,robocraftgame.com,rockpapershotgun.com,rockstargames.com,roosterteeth.com,runescape.com,schoolofdragons.com,screwattack.com,scufgaming.com,segmentnext.com,shacknews.com,shockwave.com,shoryuken.com,siliconera.com,silvergames.com,skydaz.com,smashbros.com,solomid.net,starcitygames.com,starsue.net,steamcommunity.com,steamgifts.com,strategywiki.org,supercheats.com,surrenderat20.net,swtor.com,tankionline.com,tcgplayer.com,teamfortress.com,teamliquid.net,tetrisfriends.com,thesims3.com,thesimsresource.com,thetechgame.com,topg.org,totaljerkface.com,toucharcade.com,transformice.com,trueachievements.com,twcenter.net,twitch.tv,twoplayergames.org,unity3d.com,vg247.com,vgchartz.com,videogamesblogger.com,warframe.com,warlight.net,warthunder.com,watchcartoononline.com,websudoku.com,wildstar-online.com,wildtangent.com,wineverygame.com,wizards.com,worldofsolitaire.com,worldoftanks.com,wowhead.com,wowprogress.com,wowwiki.com,xbox.com,xbox360iso.com,xboxachievements.com,xfire.com,xtremetop100.com,y8.com,yoyogames.com,zybez.net,zynga.com',
-    'Video Game' ],
-  [ 'chat.com,fring.com,hello.firefox.com,skype.com,viber.com,vonage.com',
-    'Video Chat' ],
-]);
-
 // Only allow link urls that are http(s)
 const ALLOWED_LINK_SCHEMES = new Set(["http", "https"]);
 
 // Only allow link image urls that are https or data
 const ALLOWED_IMAGE_SCHEMES = new Set(["https", "data"]);
 
 // The frecency of a directory link
 const DIRECTORY_FRECENCY = 1000;
 
-// The frecency of a suggested link
-const SUGGESTED_FRECENCY = Infinity;
-
-// Default number of times to show a link
-const DEFAULT_FREQUENCY_CAP = 5;
-
 // Divide frecency by this amount for pings
 const PING_SCORE_DIVISOR = 10000;
 
 // Allowed ping actions remotely stored as columns: case-insensitive [a-z0-9_]
 const PING_ACTIONS = ["block", "click", "pin", "sponsored", "sponsored_link", "unpin", "view"];
 
 /**
  * Singleton that serves as the provider of directory links.
@@ -118,31 +79,16 @@ let DirectoryLinksProvider = {
   // download default interval is 24 hours in milliseconds
   _downloadIntervalMS: 86400000,
 
   /**
    * A mapping from eTLD+1 to an enhanced link objects
    */
   _enhancedLinks: new Map(),
 
-  /**
-   * A mapping from site to remaining number of views
-   */
-  _frequencyCaps: new Map(),
-
-  /**
-   * A mapping from site to a list of suggested link objects
-   */
-  _suggestedLinks: new Map(),
-
-  /**
-   * A set of top sites that we can provide suggested links for
-   */
-  _topSitesWithSuggestedLinks: new Set(),
-
   get _observedPrefs() Object.freeze({
     enhanced: PREF_NEWTAB_ENHANCED,
     linksURL: PREF_DIRECTORY_SOURCE,
     matchOSLocale: PREF_MATCH_OS_LOCALE,
     prefSelectedLocale: PREF_SELECTED_LOCALE,
   }),
 
   get _linksURL() {
@@ -236,32 +182,19 @@ let DirectoryLinksProvider = {
 
   _removePrefsObserver: function DirectoryLinksProvider_removeObserver() {
     for (let pref in this._observedPrefs) {
       let prefName = this._observedPrefs[pref];
       Services.prefs.removeObserver(prefName, this);
     }
   },
 
-  _cacheSuggestedLinks: function(link) {
-    if (!link.frecent_sites || "sponsored" == link.type) {
-      // Don't cache links that don't have the expected 'frecent_sites' or are sponsored.
-      return;
-    }
-    for (let suggestedSite of link.frecent_sites) {
-      let suggestedMap = this._suggestedLinks.get(suggestedSite) || new Map();
-      suggestedMap.set(link.url, link);
-      this._suggestedLinks.set(suggestedSite, suggestedMap);
-    }
-  },
-
   _fetchAndCacheLinks: function DirectoryLinksProvider_fetchAndCacheLinks(uri) {
     // Replace with the same display locale used for selecting links data
     uri = uri.replace("%LOCALE%", this.locale);
-    uri = uri.replace("%CHANNEL%", UpdateChannel.get());
 
     let deferred = Promise.defer();
     let xmlHttp = new XMLHttpRequest();
 
     let self = this;
     xmlHttp.onload = function(aResponse) {
       let json = this.responseText;
       if (this.status && this.status != 200) {
@@ -333,67 +266,46 @@ let DirectoryLinksProvider = {
     if ((Date.now() - this._lastDownloadMS) > this._downloadIntervalMS) {
       return true;
     }
     return false;
   },
 
   /**
    * Reads directory links file and parses its content
-   * @return a promise resolved to an object with keys 'directory' and 'suggested',
-   *         each containing a valid list of links,
-   *         or {'directory': [], 'suggested': []} if read or parse fails.
+   * @return a promise resolved to valid list of links or [] if read or parse fails
    */
   _readDirectoryLinksFile: function DirectoryLinksProvider_readDirectoryLinksFile() {
-    let emptyOutput = {directory: [], suggested: [], enhanced: []};
     return OS.File.read(this._directoryFilePath).then(binaryData => {
       let output;
       try {
+        let locale = this.locale;
         let json = gTextDecoder.decode(binaryData);
-        let linksObj = JSON.parse(json);
-        output = {directory: linksObj.directory || [],
-                  suggested: linksObj.suggested || [],
-                  enhanced:  linksObj.enhanced  || []};
+        let list = JSON.parse(json);
+        output = list[locale];
       }
       catch (e) {
         Cu.reportError(e);
       }
-      return output || emptyOutput;
+      return output || [];
     },
     error => {
       Cu.reportError(error);
-      return emptyOutput;
+      return [];
     });
   },
 
   /**
    * Report some action on a newtab page (view, click)
    * @param sites Array of sites shown on newtab page
    * @param action String of the behavior to report
    * @param triggeringSiteIndex optional Int index of the site triggering action
    * @return download promise
    */
   reportSitesAction: function DirectoryLinksProvider_reportSitesAction(sites, action, triggeringSiteIndex) {
-    // Check if the suggested tile was shown
-    if (action == "view") {
-      sites.slice(0, triggeringSiteIndex + 1).forEach(site => {
-        let {targetedSite, url} = site.link;
-        if (targetedSite) {
-          this._decreaseFrequencyCap(url, 1);
-        }
-      });
-    }
-    // Use up all views if the user clicked on a frequency capped tile
-    else if (action == "click") {
-      let {targetedSite, url} = sites[triggeringSiteIndex].link;
-      if (targetedSite) {
-        this._decreaseFrequencyCap(url, DEFAULT_FREQUENCY_CAP);
-      }
-    }
-
     let newtabEnhanced = false;
     let pingEndPoint = "";
     try {
       newtabEnhanced = Services.prefs.getBoolPref(PREF_NEWTAB_ENHANCED);
       pingEndPoint = Services.prefs.getCharPref(PREF_DIRECTORY_PING);
     }
     catch (ex) {}
 
@@ -444,29 +356,21 @@ let DirectoryLinksProvider = {
     return this._fetchAndCacheLinksIfNecessary();
   },
 
   /**
    * Get the enhanced link object for a link (whether history or directory)
    */
   getEnhancedLink: function DirectoryLinksProvider_getEnhancedLink(link) {
     // Use the provided link if it's already enhanced
-    return link.enhancedImageURI && link ? link :
+    return link.enhancedImageURI && link ||
            this._enhancedLinks.get(NewTabUtils.extractSite(link.url));
   },
 
   /**
-   * Get the display name of an allowed frecent sites. Returns undefined for a
-   * unallowed frecent sites.
-   */
-  getFrecentSitesName(sites) {
-    return ALLOWED_FRECENT_SITES.get(sites.join(","));
-  },
-
-  /**
    * Check if a url's scheme is in a Set of allowed schemes
    */
   isURLAllowed: function DirectoryLinksProvider_isURLAllowed(url, allowed) {
     // Assume no url is an allowed url
     if (!url) {
       return true;
     }
 
@@ -480,247 +384,58 @@ let DirectoryLinksProvider = {
   },
 
   /**
    * Gets the current set of directory links.
    * @param aCallback The function that the array of links is passed to.
    */
   getLinks: function DirectoryLinksProvider_getLinks(aCallback) {
     this._readDirectoryLinksFile().then(rawLinks => {
-      // Reset the cache of suggested tiles and enhanced images for this new set of links
+      // Reset the cache of enhanced images for this new set of links
       this._enhancedLinks.clear();
-      this._frequencyCaps.clear();
-      this._suggestedLinks.clear();
 
-      let validityFilter = function(link) {
+      return rawLinks.filter(link => {
         // Make sure the link url is allowed and images too if they exist
         return this.isURLAllowed(link.url, ALLOWED_LINK_SCHEMES) &&
                this.isURLAllowed(link.imageURI, ALLOWED_IMAGE_SCHEMES) &&
                this.isURLAllowed(link.enhancedImageURI, ALLOWED_IMAGE_SCHEMES);
-      }.bind(this);
-
-      rawLinks.suggested.filter(validityFilter).forEach((link, position) => {
-        // Only allow suggested links with approved frecent sites
-        let name = this.getFrecentSitesName(link.frecent_sites);
-        if (name == undefined) {
-          return;
-        }
-
-        link.targetedName = name;
-        link.lastVisitDate = rawLinks.suggested.length - position;
-
-        // We cache suggested tiles here but do not push any of them in the links list yet.
-        // The decision for which suggested tile to include will be made separately.
-        this._cacheSuggestedLinks(link);
-        this._frequencyCaps.set(link.url, DEFAULT_FREQUENCY_CAP);
-      });
-
-      rawLinks.enhanced.filter(validityFilter).forEach((link, position) => {
-        link.lastVisitDate = rawLinks.enhanced.length - position;
-
+      }).map((link, position) => {
         // Stash the enhanced image for the site
         if (link.enhancedImageURI) {
           this._enhancedLinks.set(NewTabUtils.extractSite(link.url), link);
         }
-      });
 
-      let links = rawLinks.directory.filter(validityFilter).map((link, position) => {
-        link.lastVisitDate = rawLinks.directory.length - position;
         link.frecency = DIRECTORY_FRECENCY;
+        link.lastVisitDate = rawLinks.length - position;
         return link;
       });
-
-      // Allow for one link suggestion on top of the default directory links
-      this.maxNumLinks = links.length + 1;
-
-      return links;
     }).catch(ex => {
       Cu.reportError(ex);
       return [];
-    }).then(links => {
-      aCallback(links);
-      this._populatePlacesLinks();
-    });
+    }).then(aCallback);
   },
 
   init: function DirectoryLinksProvider_init() {
     this._setDefaultEnhanced();
     this._addPrefsObserver();
     // setup directory file path and last download timestamp
     this._directoryFilePath = OS.Path.join(OS.Constants.Path.localProfileDir, DIRECTORY_LINKS_FILE);
     this._lastDownloadMS = 0;
-
-    NewTabUtils.placesProvider.addObserver(this);
-
     return Task.spawn(function() {
       // get the last modified time of the links file if it exists
       let doesFileExists = yield OS.File.exists(this._directoryFilePath);
       if (doesFileExists) {
         let fileInfo = yield OS.File.stat(this._directoryFilePath);
         this._lastDownloadMS = Date.parse(fileInfo.lastModificationDate);
       }
       // fetch directory on startup without force
       yield this._fetchAndCacheLinksIfNecessary();
     }.bind(this));
   },
 
-  _handleManyLinksChanged: function() {
-    this._topSitesWithSuggestedLinks.clear();
-    this._suggestedLinks.forEach((suggestedLinks, site) => {
-      if (NewTabUtils.isTopPlacesSite(site)) {
-        this._topSitesWithSuggestedLinks.add(site);
-      }
-    });
-    this._updateSuggestedTile();
-  },
-
-  /**
-   * Updates _topSitesWithSuggestedLinks based on the link that was changed.
-   *
-   * @return true if _topSitesWithSuggestedLinks was modified, false otherwise.
-   */
-  _handleLinkChanged: function(aLink) {
-    let changedLinkSite = NewTabUtils.extractSite(aLink.url);
-    let linkStored = this._topSitesWithSuggestedLinks.has(changedLinkSite);
-
-    if (!NewTabUtils.isTopPlacesSite(changedLinkSite) && linkStored) {
-      this._topSitesWithSuggestedLinks.delete(changedLinkSite);
-      return true;
-    }
-
-    if (this._suggestedLinks.has(changedLinkSite) &&
-        NewTabUtils.isTopPlacesSite(changedLinkSite) && !linkStored) {
-      this._topSitesWithSuggestedLinks.add(changedLinkSite);
-      return true;
-    }
-    return false;
-  },
-
-  _populatePlacesLinks: function () {
-    NewTabUtils.links.populateProviderCache(NewTabUtils.placesProvider, () => {
-      this._handleManyLinksChanged();
-    });
-  },
-
-  onLinkChanged: function (aProvider, aLink) {
-    // Make sure NewTabUtils.links handles the notification first.
-    setTimeout(() => {
-      if (this._handleLinkChanged(aLink)) {
-        this._updateSuggestedTile();
-      }
-    }, 0);
-  },
-
-  onManyLinksChanged: function () {
-    // Make sure NewTabUtils.links handles the notification first.
-    setTimeout(() => {
-      this._handleManyLinksChanged();
-    }, 0);
-  },
-
-  /**
-   * Record for a url that some number of views have been used
-   * @param url String url of the suggested link
-   * @param amount Number of equivalent views to decrease
-   */
-  _decreaseFrequencyCap(url, amount) {
-    let remainingViews = this._frequencyCaps.get(url) - amount;
-    this._frequencyCaps.set(url, remainingViews);
-
-    // Reached the number of views, so pick a new one.
-    if (remainingViews <= 0) {
-      this._updateSuggestedTile();
-    }
-  },
-
-  /**
-   * Chooses and returns a suggested tile based on a user's top sites
-   * that we have an available suggested tile for.
-   *
-   * @return the chosen suggested tile, or undefined if there isn't one
-   */
-  _updateSuggestedTile: function() {
-    let sortedLinks = NewTabUtils.getProviderLinks(this);
-
-    if (!sortedLinks) {
-      // If NewTabUtils.links.resetCache() is called before getting here,
-      // sortedLinks may be undefined.
-      return;
-    }
-
-    // Delete the current suggested tile, if one exists.
-    let initialLength = sortedLinks.length;
-    if (initialLength) {
-      let mostFrecentLink = sortedLinks[0];
-      if (mostFrecentLink.targetedSite) {
-        this._callObservers("onLinkChanged", {
-          url: mostFrecentLink.url,
-          frecency: SUGGESTED_FRECENCY,
-          lastVisitDate: mostFrecentLink.lastVisitDate,
-          type: mostFrecentLink.type,
-        }, 0, true);
-      }
-    }
-
-    if (this._topSitesWithSuggestedLinks.size == 0) {
-      // There are no potential suggested links we can show.
-      return;
-    }
-
-    // Create a flat list of all possible links we can show as suggested.
-    // Note that many top sites may map to the same suggested links, but we only
-    // want to count each suggested link once (based on url), thus possibleLinks is a map
-    // from url to suggestedLink. 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._topSitesWithSuggestedLinks.forEach(topSiteWithSuggestedLink => {
-      let suggestedLinksMap = this._suggestedLinks.get(topSiteWithSuggestedLink);
-      suggestedLinksMap.forEach((suggestedLink, url) => {
-        // Skip this link if we've shown it too many times already
-        if (this._frequencyCaps.get(url) <= 0) {
-          return;
-        }
-
-        possibleLinks.set(url, suggestedLink);
-
-        // 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(topSiteWithSuggestedLink);
-      })
-    });
-
-    // We might have run out of possible links to show
-    let numLinks = possibleLinks.size;
-    if (numLinks == 0) {
-      return;
-    }
-
-    let flattenedLinks = [...possibleLinks.values()];
-
-    // Choose our suggested link at random
-    let suggestedIndex = Math.floor(Math.random() * numLinks);
-    let chosenSuggestedLink = flattenedLinks[suggestedIndex];
-
-    // Add the suggested link to the front with some extra values
-    this._callObservers("onLinkChanged", Object.assign({
-      frecency: SUGGESTED_FRECENCY,
-
-      // 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(chosenSuggestedLink.url).length ?
-        targetedSites.get(chosenSuggestedLink.url)[0] : null
-    }, chosenSuggestedLink));
-    return chosenSuggestedLink;
-   },
-
   /**
    * Return the object to its pre-init state
    */
   reset: function DirectoryLinksProvider_reset() {
     delete this.__linksURL;
     this._removePrefsObserver();
     this._removeObservers();
   },
@@ -728,21 +443,21 @@ let DirectoryLinksProvider = {
   addObserver: function DirectoryLinksProvider_addObserver(aObserver) {
     this._observers.add(aObserver);
   },
 
   removeObserver: function DirectoryLinksProvider_removeObserver(aObserver) {
     this._observers.delete(aObserver);
   },
 
-  _callObservers(methodName, ...args) {
+  _callObservers: function DirectoryLinksProvider__callObservers(aMethodName, aArg) {
     for (let obs of this._observers) {
-      if (typeof(obs[methodName]) == "function") {
+      if (typeof(obs[aMethodName]) == "function") {
         try {
-          obs[methodName](this, ...args);
+          obs[aMethodName](this, aArg);
         } catch (err) {
           Cu.reportError(err);
         }
       }
     }
   },
 
   _removeObservers: function() {
--- a/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
+++ b/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
@@ -14,25 +14,22 @@ Cu.import("resource://gre/modules/Promis
 Cu.import("resource://gre/modules/Http.jsm");
 Cu.import("resource://testing-common/httpd.js");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
   "resource://gre/modules/NetUtil.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
-  "resource://gre/modules/NewTabUtils.jsm");
 
 do_get_profile();
 
 const DIRECTORY_LINKS_FILE = "directoryLinks.json";
 const DIRECTORY_FRECENCY = 1000;
-const SUGGESTED_FRECENCY = Infinity;
-const kURLData = {"directory": [{"url":"http://example.com","title":"LocalSource"}]};
+const kURLData = {"en-US": [{"url":"http://example.com","title":"LocalSource"}]};
 const kTestURL = 'data:application/json,' + JSON.stringify(kURLData);
 
 // DirectoryLinksProvider preferences
 const kLocalePref = DirectoryLinksProvider._observedPrefs.prefSelectedLocale;
 const kSourceUrlPref = DirectoryLinksProvider._observedPrefs.linksURL;
 const kPingUrlPref = "browser.newtabpage.directory.ping";
 const kNewtabEnhancedPref = "browser.newtabpage.enhanced";
 
@@ -49,67 +46,23 @@ const kPingUrl = kBaseUrl + kPingPath;
 
 // app/profile/firefox.js are not avaialble in xpcshell: hence, preset them
 Services.prefs.setCharPref(kLocalePref, "en-US");
 Services.prefs.setCharPref(kSourceUrlPref, kTestURL);
 Services.prefs.setCharPref(kPingUrlPref, kPingUrl);
 Services.prefs.setBoolPref(kNewtabEnhancedPref, true);
 
 const kHttpHandlerData = {};
-kHttpHandlerData[kExamplePath] = {"directory": [{"url":"http://example.com","title":"RemoteSource"}]};
+kHttpHandlerData[kExamplePath] = {"en-US": [{"url":"http://example.com","title":"RemoteSource"}]};
 
 const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
                               "nsIBinaryInputStream",
                               "setInputStream");
 
 let gLastRequestPath;
-
-let suggestedTile1 = {
-  url: "http://turbotax.com",
-  type: "affiliate",
-  lastVisitDate: 4,
-  frecent_sites: [
-    "taxact.com",
-    "hrblock.com",
-    "1040.com",
-    "taxslayer.com"
-  ]
-};
-let suggestedTile2 = {
-  url: "http://irs.gov",
-  type: "affiliate",
-  lastVisitDate: 3,
-  frecent_sites: [
-    "taxact.com",
-    "hrblock.com",
-    "freetaxusa.com",
-    "taxslayer.com"
-  ]
-};
-let suggestedTile3 = {
-  url: "http://hrblock.com",
-  type: "affiliate",
-  lastVisitDate: 2,
-  frecent_sites: [
-    "taxact.com",
-    "freetaxusa.com",
-    "1040.com",
-    "taxslayer.com"
-  ]
-};
-let suggestedTile4 = {
-  url: "http://sponsoredtile.com",
-  type: "sponsored",
-  lastVisitDate: 1,
-  frecent_sites: [
-    "sponsoredtarget.com"
-  ]
-}
-let someOtherSite = {url: "http://someothersite.com", title: "Not_A_Suggested_Site"};
-
 function getHttpHandler(path) {
   let code = 200;
   let body = JSON.stringify(kHttpHandlerData[path]);
   if (path == kFailPath) {
     code = 204;
   }
   return function(aRequest, aResponse) {
     gLastRequestPath = aRequest.path;
@@ -203,441 +156,30 @@ function promiseCleanDirectoryLinksProvi
 }
 
 function run_test() {
   // Set up a mock HTTP server to serve a directory page
   server = new HttpServer();
   server.registerPrefixHandler(kExamplePath, getHttpHandler(kExamplePath));
   server.registerPrefixHandler(kFailPath, getHttpHandler(kFailPath));
   server.start(kDefaultServerPort);
-  NewTabUtils.init();
 
   run_next_test();
 
   // Teardown.
   do_register_cleanup(function() {
     server.stop(function() { });
     DirectoryLinksProvider.reset();
     Services.prefs.clearUserPref(kLocalePref);
     Services.prefs.clearUserPref(kSourceUrlPref);
     Services.prefs.clearUserPref(kPingUrlPref);
     Services.prefs.clearUserPref(kNewtabEnhancedPref);
   });
 }
 
-add_task(function test_updateSuggestedTile() {
-  let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
-
-  // Initial setup
-  let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3], "directory": [someOtherSite]};
-  let dataURI = 'data:application/json,' + JSON.stringify(data);
-
-  let testObserver = new TestFirstRun();
-  DirectoryLinksProvider.addObserver(testObserver);
-
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
-
-  yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
-  let links = yield fetchData();
-
-  let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
-  NewTabUtils.isTopPlacesSite = function(site) {
-    return topSites.indexOf(site) >= 0;
-  }
-
-  let origGetProviderLinks = NewTabUtils.getProviderLinks;
-  NewTabUtils.getProviderLinks = function(provider) {
-    return links;
-  }
-
-  do_check_eq(DirectoryLinksProvider._updateSuggestedTile(), undefined);
-
-  function TestFirstRun() {
-    this.promise = new Promise(resolve => {
-      this.onLinkChanged = (directoryLinksProvider, link) => {
-        links.unshift(link);
-        let possibleLinks = [suggestedTile1.url, suggestedTile2.url, suggestedTile3.url];
-
-        isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], ["hrblock.com", "1040.com", "freetaxusa.com"]);
-        do_check_true(possibleLinks.indexOf(link.url) > -1);
-        do_check_eq(link.frecency, SUGGESTED_FRECENCY);
-        do_check_eq(link.type, "affiliate");
-        resolve();
-      };
-    });
-  }
-
-  function TestChangingSuggestedTile() {
-    this.count = 0;
-    this.promise = new Promise(resolve => {
-      this.onLinkChanged = (directoryLinksProvider, link) => {
-        this.count++;
-        let possibleLinks = [suggestedTile1.url, suggestedTile2.url, suggestedTile3.url];
-
-        do_check_true(possibleLinks.indexOf(link.url) > -1);
-        do_check_eq(link.type, "affiliate");
-        do_check_true(this.count <= 2);
-
-        if (this.count == 1) {
-          // The removed suggested link is the one we added initially.
-          do_check_eq(link.url, links.shift().url);
-          do_check_eq(link.frecency, SUGGESTED_FRECENCY);
-        } else {
-          links.unshift(link);
-          do_check_eq(link.frecency, SUGGESTED_FRECENCY);
-        }
-        isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], ["hrblock.com", "freetaxusa.com"]);
-        resolve();
-      }
-    });
-  }
-
-  function TestRemovingSuggestedTile() {
-    this.count = 0;
-    this.promise = new Promise(resolve => {
-      this.onLinkChanged = (directoryLinksProvider, link) => {
-        this.count++;
-
-        do_check_eq(link.type, "affiliate");
-        do_check_eq(this.count, 1);
-        do_check_eq(link.frecency, SUGGESTED_FRECENCY);
-        do_check_eq(link.url, links.shift().url);
-        isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], []);
-        resolve();
-      }
-    });
-  }
-
-  // Test first call to '_updateSuggestedTile()', called when fetching directory links.
-  yield testObserver.promise;
-  DirectoryLinksProvider.removeObserver(testObserver);
-
-  // Removing a top site that doesn't have a suggested link should
-  // not change the current suggested tile.
-  let removedTopsite = topSites.shift();
-  do_check_eq(removedTopsite, "site0.com");
-  do_check_false(NewTabUtils.isTopPlacesSite(removedTopsite));
-  let updateSuggestedTile = DirectoryLinksProvider._handleLinkChanged({
-    url: "http://" + removedTopsite,
-    type: "history",
-  });
-  do_check_false(updateSuggestedTile);
-
-  // Removing a top site that has a suggested link should
-  // remove any current suggested tile and add a new one.
-  testObserver = new TestChangingSuggestedTile();
-  DirectoryLinksProvider.addObserver(testObserver);
-  removedTopsite = topSites.shift();
-  do_check_eq(removedTopsite, "1040.com");
-  do_check_false(NewTabUtils.isTopPlacesSite(removedTopsite));
-  DirectoryLinksProvider.onLinkChanged(DirectoryLinksProvider, {
-    url: "http://" + removedTopsite,
-    type: "history",
-  });
-  yield testObserver.promise;
-  do_check_eq(testObserver.count, 2);
-  DirectoryLinksProvider.removeObserver(testObserver);
-
-  // Removing all top sites with suggested links should remove
-  // the current suggested link and not replace it.
-  topSites = [];
-  testObserver = new TestRemovingSuggestedTile();
-  DirectoryLinksProvider.addObserver(testObserver);
-  DirectoryLinksProvider.onManyLinksChanged();
-  yield testObserver.promise;
-
-  // Cleanup
-  yield promiseCleanDirectoryLinksProvider();
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
-  NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
-  NewTabUtils.getProviderLinks = origGetProviderLinks;
-});
-
-add_task(function test_suggestedLinksMap() {
-  let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3, suggestedTile4], "directory": [someOtherSite]};
-  let dataURI = 'data:application/json,' + JSON.stringify(data);
-
-  yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
-  let links = yield fetchData();
-
-  // Ensure the suggested tiles were not considered directory tiles.
-  do_check_eq(links.length, 1);
-  let expected_data = [{url: "http://someothersite.com", title: "Not_A_Suggested_Site", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}];
-  isIdentical(links, expected_data);
-
-  // Check for correctly saved suggested tiles data.
-  expected_data = {
-    "taxact.com": [suggestedTile1, suggestedTile2, suggestedTile3],
-    "hrblock.com": [suggestedTile1, suggestedTile2],
-    "1040.com": [suggestedTile1, suggestedTile3],
-    "taxslayer.com": [suggestedTile1, suggestedTile2, suggestedTile3],
-    "freetaxusa.com": [suggestedTile2, suggestedTile3],
-  };
-  do_check_eq([...DirectoryLinksProvider._suggestedLinks.keys()].indexOf("sponsoredtarget.com"), -1);
-
-  DirectoryLinksProvider._suggestedLinks.forEach((suggestedLinks, site) => {
-    let suggestedLinksItr = suggestedLinks.values();
-    for (let link of expected_data[site]) {
-      isIdentical(suggestedLinksItr.next().value, link);
-    }
-  })
-
-  yield promiseCleanDirectoryLinksProvider();
-});
-
-add_task(function test_topSitesWithSuggestedLinks() {
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
-
-  let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
-  let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
-  NewTabUtils.isTopPlacesSite = function(site) {
-    return topSites.indexOf(site) >= 0;
-  }
-
-  // Mock out getProviderLinks() so we don't have to populate cache in NewTabUtils
-  let origGetProviderLinks = NewTabUtils.getProviderLinks;
-  NewTabUtils.getProviderLinks = function(provider) {
-    return [];
-  }
-
-  // We start off with no top sites with suggested links.
-  do_check_eq(DirectoryLinksProvider._topSitesWithSuggestedLinks.size, 0);
-
-  let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3], "directory": [someOtherSite]};
-  let dataURI = 'data:application/json,' + JSON.stringify(data);
-
-  yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
-  let links = yield fetchData();
-
-  // Check we've populated suggested links as expected.
-  do_check_eq(DirectoryLinksProvider._suggestedLinks.size, 5);
-
-  // When many sites change, we update _topSitesWithSuggestedLinks as expected.
-  let expectedTopSitesWithSuggestedLinks = ["hrblock.com", "1040.com", "freetaxusa.com"];
-  DirectoryLinksProvider._handleManyLinksChanged();
-  isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks);
-
-  // Removing site6.com as a topsite has no impact on _topSitesWithSuggestedLinks.
-  let popped = topSites.pop();
-  DirectoryLinksProvider._handleLinkChanged({url: "http://" + popped});
-  isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks);
-
-  // Removing freetaxusa.com as a topsite will remove it from _topSitesWithSuggestedLinks.
-  popped = topSites.pop();
-  expectedTopSitesWithSuggestedLinks.pop();
-  DirectoryLinksProvider._handleLinkChanged({url: "http://" + popped});
-  isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks);
-
-  // Re-adding freetaxusa.com as a topsite will add it to _topSitesWithSuggestedLinks.
-  topSites.push(popped);
-  expectedTopSitesWithSuggestedLinks.push(popped);
-  DirectoryLinksProvider._handleLinkChanged({url: "http://" + popped});
-  isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks);
-
-  // Cleanup.
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
-  NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
-  NewTabUtils.getProviderLinks = origGetProviderLinks;
-});
-
-add_task(function test_suggestedAttributes() {
-  let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
-  NewTabUtils.isTopPlacesSite = () => true;
-
-  let frecent_sites = "addons.mozilla.org,air.mozilla.org,blog.mozilla.org,bugzilla.mozilla.org,developer.mozilla.org,etherpad.mozilla.org,forums.mozillazine.org,hacks.mozilla.org,hg.mozilla.org,mozilla.org,planet.mozilla.org,quality.mozilla.org,support.mozilla.org,treeherder.mozilla.org,wiki.mozilla.org".split(",");
-  let imageURI = "https://image/";
-  let title = "the title";
-  let type = "affiliate";
-  let url = "http://test.url/";
-  let data = {
-    suggested: [{
-      frecent_sites,
-      imageURI,
-      title,
-      type,
-      url
-    }]
-  };
-  let dataURI = "data:application/json," + escape(JSON.stringify(data));
-
-  yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
-
-  // Wait for links to get loaded
-  let gLinks = NewTabUtils.links;
-  gLinks.addProvider(DirectoryLinksProvider);
-  gLinks.populateCache();
-  yield new Promise(resolve => {
-    NewTabUtils.allPages.register({
-      observe: _ => _,
-      update() {
-        NewTabUtils.allPages.unregister(this);
-        resolve();
-      }
-    });
-  });
-
-  // Make sure we get the expected attributes on the suggested tile
-  let link = gLinks.getLinks()[0];
-  do_check_eq(link.imageURI, imageURI);
-  do_check_eq(link.targetedName, "Mozilla");
-  do_check_eq(link.targetedSite, frecent_sites[0]);
-  do_check_eq(link.title, title);
-  do_check_eq(link.type, type);
-  do_check_eq(link.url, url);
-
-  // Cleanup.
-  NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
-  gLinks.removeProvider(DirectoryLinksProvider);
-  DirectoryLinksProvider.removeObserver(gLinks);
-});
-
-add_task(function test_frequencyCappedSites_views() {
-  Services.prefs.setCharPref(kPingUrlPref, "");
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
-  let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
-  NewTabUtils.isTopPlacesSite = () => true;
-
-  let testUrl = "http://frequency.capped/link";
-  let targets = ["top.site.com"];
-  let data = {
-    suggested: [{
-      type: "affiliate",
-      frecent_sites: targets,
-      url: testUrl
-    }],
-    directory: [{
-      type: "organic",
-      url: "http://directory.site/"
-    }]
-  };
-  let dataURI = "data:application/json," + JSON.stringify(data);
-
-  yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
-
-  // Wait for links to get loaded
-  let gLinks = NewTabUtils.links;
-  gLinks.addProvider(DirectoryLinksProvider);
-  gLinks.populateCache();
-  yield new Promise(resolve => {
-    NewTabUtils.allPages.register({
-      observe: _ => _,
-      update() {
-        NewTabUtils.allPages.unregister(this);
-        resolve();
-      }
-    });
-  });
-
-  function synthesizeAction(action) {
-    DirectoryLinksProvider.reportSitesAction([{
-      link: {
-        targetedSite: targets[0],
-        url: testUrl
-      }
-    }], action, 0);
-  }
-
-  function checkFirstTypeAndLength(type, length) {
-    let links = gLinks.getLinks();
-    do_check_eq(links[0].type, type);
-    do_check_eq(links.length, length);
-  }
-
-  // Make sure we get 5 views of the link before it is removed
-  checkFirstTypeAndLength("affiliate", 2);
-  synthesizeAction("view");
-  checkFirstTypeAndLength("affiliate", 2);
-  synthesizeAction("view");
-  checkFirstTypeAndLength("affiliate", 2);
-  synthesizeAction("view");
-  checkFirstTypeAndLength("affiliate", 2);
-  synthesizeAction("view");
-  checkFirstTypeAndLength("affiliate", 2);
-  synthesizeAction("view");
-  checkFirstTypeAndLength("organic", 1);
-
-  // Cleanup.
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
-  NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
-  gLinks.removeProvider(DirectoryLinksProvider);
-  DirectoryLinksProvider.removeObserver(gLinks);
-  Services.prefs.setCharPref(kPingUrlPref, kPingUrl);
-});
-
-add_task(function test_frequencyCappedSites_click() {
-  Services.prefs.setCharPref(kPingUrlPref, "");
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
-  let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
-  NewTabUtils.isTopPlacesSite = () => true;
-
-  let testUrl = "http://frequency.capped/link";
-  let targets = ["top.site.com"];
-  let data = {
-    suggested: [{
-      type: "affiliate",
-      frecent_sites: targets,
-      url: testUrl
-    }],
-    directory: [{
-      type: "organic",
-      url: "http://directory.site/"
-    }]
-  };
-  let dataURI = "data:application/json," + JSON.stringify(data);
-
-  yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
-
-  // Wait for links to get loaded
-  let gLinks = NewTabUtils.links;
-  gLinks.addProvider(DirectoryLinksProvider);
-  gLinks.populateCache();
-  yield new Promise(resolve => {
-    NewTabUtils.allPages.register({
-      observe: _ => _,
-      update() {
-        NewTabUtils.allPages.unregister(this);
-        resolve();
-      }
-    });
-  });
-
-  function synthesizeAction(action) {
-    DirectoryLinksProvider.reportSitesAction([{
-      link: {
-        targetedSite: targets[0],
-        url: testUrl
-      }
-    }], action, 0);
-  }
-
-  function checkFirstTypeAndLength(type, length) {
-    let links = gLinks.getLinks();
-    do_check_eq(links[0].type, type);
-    do_check_eq(links.length, length);
-  }
-
-  // Make sure the link disappears after the first click
-  checkFirstTypeAndLength("affiliate", 2);
-  synthesizeAction("view");
-  checkFirstTypeAndLength("affiliate", 2);
-  synthesizeAction("click");
-  checkFirstTypeAndLength("organic", 1);
-
-  // Cleanup.
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
-  NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
-  gLinks.removeProvider(DirectoryLinksProvider);
-  DirectoryLinksProvider.removeObserver(gLinks);
-  Services.prefs.setCharPref(kPingUrlPref, kPingUrl);
-});
-
 add_task(function test_reportSitesAction() {
   yield DirectoryLinksProvider.init();
   let deferred, expectedPath, expectedPost;
   let done = false;
   server.registerPrefixHandler(kPingPath, (aRequest, aResponse) => {
     if (done) {
       return;
     }
@@ -811,16 +353,49 @@ add_task(function test_DirectoryLinksPro
 
   yield testObserver.deferred.promise;
   DirectoryLinksProvider._removeObservers();
   do_check_eq(DirectoryLinksProvider._observers.size, 0);
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
+add_task(function test_linksURL_locale() {
+  let data = {
+    "en-US": [{url: "http://example.com", title: "US"}],
+    "zh-CN": [
+              {url: "http://example.net", title: "CN"},
+              {url:"http://example.net/2", title: "CN2"}
+    ],
+  };
+  let dataURI = 'data:application/json,' + JSON.stringify(data);
+
+  yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+
+  let links;
+  let expected_data;
+
+  links = yield fetchData();
+  do_check_eq(links.length, 1);
+  expected_data = [{url: "http://example.com", title: "US", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}];
+  isIdentical(links, expected_data);
+
+  yield promiseDirectoryDownloadOnPrefChange("general.useragent.locale", "zh-CN");
+
+  links = yield fetchData();
+  do_check_eq(links.length, 2)
+  expected_data = [
+    {url: "http://example.net", title: "CN", frecency: DIRECTORY_FRECENCY, lastVisitDate: 2},
+    {url: "http://example.net/2", title: "CN2", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}
+  ];
+  isIdentical(links, expected_data);
+
+  yield promiseCleanDirectoryLinksProvider();
+});
+
 add_task(function test_DirectoryLinksProvider__prefObserver_url() {
   yield promiseSetupDirectoryLinksProvider({linksURL: kTestURL});
 
   let links = yield fetchData();
   do_check_eq(links.length, 1);
   let expectedData =  [{url: "http://example.com", title: "LocalSource", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}];
   isIdentical(links, expectedData);
 
@@ -840,23 +415,18 @@ add_task(function test_DirectoryLinksPro
   yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, exampleUrl + " ");
   // we now should see empty links
   newLinks = yield fetchData();
   isIdentical(newLinks, []);
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
-add_task(function test_DirectoryLinksProvider_getLinks_noDirectoryData() {
-  let data = {
-    "directory": [],
-  };
-  let dataURI = 'data:application/json,' + JSON.stringify(data);
-  yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
-
+add_task(function test_DirectoryLinksProvider_getLinks_noLocaleData() {
+  yield promiseSetupDirectoryLinksProvider({locale: 'zh-CN'});
   let links = yield fetchData();
   do_check_eq(links.length, 0);
   yield promiseCleanDirectoryLinksProvider();
 });
 
 add_task(function test_DirectoryLinksProvider_getLinks_badData() {
   let data = {
     "en-US": {
@@ -982,89 +552,89 @@ add_task(function test_DirectoryLinksPro
   yield OS.File.writeAtomic(directoryLinksFilePath, '{"en-US":');
   let data = yield fetchData();
   isIdentical(data, []);
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
 add_task(function test_DirectoryLinksProvider_getAllowedLinks() {
-  let data = {"directory": [
+  let data = {"en-US": [
     {url: "ftp://example.com"},
     {url: "http://example.net"},
     {url: "javascript:5"},
     {url: "https://example.com"},
     {url: "httpJUNKjavascript:42"},
     {url: "data:text/plain,hi"},
     {url: "http/bork:eh"},
   ]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
   let links = yield fetchData();
   do_check_eq(links.length, 2);
 
   // The only remaining url should be http and https
-  do_check_eq(links[0].url, data["directory"][1].url);
-  do_check_eq(links[1].url, data["directory"][3].url);
+  do_check_eq(links[0].url, data["en-US"][1].url);
+  do_check_eq(links[1].url, data["en-US"][3].url);
 });
 
 add_task(function test_DirectoryLinksProvider_getAllowedImages() {
-  let data = {"directory": [
+  let data = {"en-US": [
     {url: "http://example.com", imageURI: "ftp://example.com"},
     {url: "http://example.com", imageURI: "http://example.net"},
     {url: "http://example.com", imageURI: "javascript:5"},
     {url: "http://example.com", imageURI: "https://example.com"},
     {url: "http://example.com", imageURI: "httpJUNKjavascript:42"},
     {url: "http://example.com", imageURI: "data:text/plain,hi"},
     {url: "http://example.com", imageURI: "http/bork:eh"},
   ]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
   let links = yield fetchData();
   do_check_eq(links.length, 2);
 
   // The only remaining images should be https and data
-  do_check_eq(links[0].imageURI, data["directory"][3].imageURI);
-  do_check_eq(links[1].imageURI, data["directory"][5].imageURI);
+  do_check_eq(links[0].imageURI, data["en-US"][3].imageURI);
+  do_check_eq(links[1].imageURI, data["en-US"][5].imageURI);
 });
 
 add_task(function test_DirectoryLinksProvider_getAllowedEnhancedImages() {
-  let data = {"directory": [
+  let data = {"en-US": [
     {url: "http://example.com", enhancedImageURI: "ftp://example.com"},
     {url: "http://example.com", enhancedImageURI: "http://example.net"},
     {url: "http://example.com", enhancedImageURI: "javascript:5"},
     {url: "http://example.com", enhancedImageURI: "https://example.com"},
     {url: "http://example.com", enhancedImageURI: "httpJUNKjavascript:42"},
     {url: "http://example.com", enhancedImageURI: "data:text/plain,hi"},
     {url: "http://example.com", enhancedImageURI: "http/bork:eh"},
   ]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
   let links = yield fetchData();
   do_check_eq(links.length, 2);
 
   // The only remaining enhancedImages should be http and https and data
-  do_check_eq(links[0].enhancedImageURI, data["directory"][3].enhancedImageURI);
-  do_check_eq(links[1].enhancedImageURI, data["directory"][5].enhancedImageURI);
+  do_check_eq(links[0].enhancedImageURI, data["en-US"][3].enhancedImageURI);
+  do_check_eq(links[1].enhancedImageURI, data["en-US"][5].enhancedImageURI);
 });
 
 add_task(function test_DirectoryLinksProvider_getEnhancedLink() {
-  let data = {"enhanced": [
+  let data = {"en-US": [
     {url: "http://example.net", enhancedImageURI: "data:,net1"},
     {url: "http://example.com", enhancedImageURI: "data:,com1"},
     {url: "http://example.com", enhancedImageURI: "data:,com2"},
   ]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
   let links = yield fetchData();
-  do_check_eq(links.length, 0); // There are no directory links.
+  do_check_eq(links.length, 3);
 
   function checkEnhanced(url, image) {
     let enhanced = DirectoryLinksProvider.getEnhancedLink({url: url});
     do_check_eq(enhanced && enhanced.enhancedImageURI, image);
   }
 
   // Get the expected image for the same site
   checkEnhanced("http://example.net/", "data:,net1");
@@ -1085,76 +655,27 @@ add_task(function test_DirectoryLinksPro
 
   // Undefined for not enhanced
   checkEnhanced("http://sub.example.net/", undefined);
   checkEnhanced("http://example.org", undefined);
   checkEnhanced("http://localhost", undefined);
   checkEnhanced("http://127.0.0.1", undefined);
 
   // Make sure old data is not cached
-  data = {"enhanced": [
+  data = {"en-US": [
     {url: "http://example.com", enhancedImageURI: "data:,fresh"},
   ]};
   dataURI = 'data:application/json,' + JSON.stringify(data);
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   links = yield fetchData();
-  do_check_eq(links.length, 0); // There are no directory links.
+  do_check_eq(links.length, 1);
   checkEnhanced("http://example.net", undefined);
   checkEnhanced("http://example.com", "data:,fresh");
 });
 
-add_task(function test_DirectoryLinksProvider_enhancedURIs() {
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
-  let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
-  NewTabUtils.isTopPlacesSite = () => true;
-
-  let data = {
-    "suggested": [
-      {url: "http://example.net", enhancedImageURI: "data:,net1", title:"SuggestedTitle", frecent_sites: ["test.com"]}
-    ],
-    "directory": [
-      {url: "http://example.net", enhancedImageURI: "data:,net2", title:"DirectoryTitle"}
-    ]
-  };
-  let dataURI = 'data:application/json,' + JSON.stringify(data);
-  yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
-
-  // Wait for links to get loaded
-  let gLinks = NewTabUtils.links;
-  gLinks.addProvider(DirectoryLinksProvider);
-  gLinks.populateCache();
-  yield new Promise(resolve => {
-    NewTabUtils.allPages.register({
-      observe: _ => _,
-      update() {
-        NewTabUtils.allPages.unregister(this);
-        resolve();
-      }
-    });
-  });
-
-  // Check that we've saved the directory tile.
-  let links = yield fetchData();
-  do_check_eq(links.length, 1);
-  do_check_eq(links[0].title, "DirectoryTitle");
-  do_check_eq(links[0].enhancedImageURI, "data:,net2");
-
-  // Check that the suggested tile with the same URL replaces the directory tile.
-  links = gLinks.getLinks();
-  do_check_eq(links.length, 1);
-  do_check_eq(links[0].title, "SuggestedTitle");
-  do_check_eq(links[0].enhancedImageURI, "data:,net1");
-
-  // Cleanup.
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
-  NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
-  gLinks.removeProvider(DirectoryLinksProvider);
-});
-
 add_task(function test_DirectoryLinksProvider_setDefaultEnhanced() {
   function checkDefault(expected) {
     Services.prefs.clearUserPref(kNewtabEnhancedPref);
     do_check_eq(Services.prefs.getBoolPref(kNewtabEnhancedPref), expected);
   }
 
   // Use the default donottrack prefs (enabled = false)
   Services.prefs.clearUserPref("privacy.donottrackheader.enabled");
--- a/browser/themes/shared/newtab/newTab.inc.css
+++ b/browser/themes/shared/newtab/newTab.inc.css
@@ -117,51 +117,35 @@
   background-origin: padding-box;
   background-clip: padding-box;
   background-repeat: no-repeat;
   background-size: cover;
   border-radius: inherit;
   transition: opacity 100ms ease-out;
 }
 
-.newtab-cell:not([ignorehover]) .newtab-site:hover .newtab-thumbnail.enhanced-content {
+.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-suggested  {
+.newtab-title {
   color: #5c5c5c;
 }
 
-.newtab-suggested:hover {
-  color: #588FE4;
-  border: 1px solid #588FE4;
-}
-
-.newtab-suggested[active] {
-  background-color: rgba(51, 51, 51, 0.95);
-  border: 0;
-  color: white;
-}
-
-.newtab-suggested {
-  background-color: white;
-}
-
 .newtab-site:hover .newtab-title {
   color: #222;
 }
 
 .newtab-site[pinned] .newtab-title {
   padding: 0 15px;
 }
 
--- a/toolkit/modules/NewTabUtils.jsm
+++ b/toolkit/modules/NewTabUtils.jsm
@@ -7,17 +7,16 @@
 this.EXPORTED_SYMBOLS = ["NewTabUtils"];
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
   "resource://gre/modules/PlacesUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
   "resource://gre/modules/PageThumbs.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "BinarySearch",
@@ -711,22 +710,27 @@ let PlacesProvider = {
  */
 let Links = {
   /**
    * The maximum number of links returned by getLinks.
    */
   maxNumLinks: LINKS_GET_LINKS_LIMIT,
 
   /**
+   * The link providers.
+   */
+  _providers: new Set(),
+
+  /**
    * A mapping from each provider to an object { sortedLinks, siteMap, linkMap }.
    * sortedLinks is the cached, sorted array of links for the provider.
    * siteMap is a mapping from base domains to URL count associated with the domain.
    * linkMap is a Map from link URLs to link objects.
    */
-  _providers: new Map(),
+  _providerLinks: new Map(),
 
   /**
    * The properties of link objects used to sort them.
    */
   _sortProperties: [
     "frecency",
     "lastVisitDate",
     "url",
@@ -737,27 +741,28 @@ let Links = {
    */
   _populateCallbacks: [],
 
   /**
    * Adds a link provider.
    * @param aProvider The link provider.
    */
   addProvider: function Links_addProvider(aProvider) {
-    this._providers.set(aProvider, null);
+    this._providers.add(aProvider);
     aProvider.addObserver(this);
   },
 
   /**
    * Removes a link provider.
    * @param aProvider The link provider.
    */
   removeProvider: function Links_removeProvider(aProvider) {
     if (!this._providers.delete(aProvider))
       throw new Error("Unknown provider");
+    this._providerLinks.delete(aProvider);
   },
 
   /**
    * Populates the cache with fresh links from the providers.
    * @param aCallback The callback to call when finished (optional).
    * @param aForce When true, populates the cache even when it's already filled.
    */
   populateCache: function Links_populateCache(aCallback, aForce) {
@@ -780,17 +785,17 @@ let Links = {
           } catch (e) {
             // We want to proceed even if a callback fails.
           }
         }
       }
     }
 
     let numProvidersRemaining = this._providers.size;
-    for (let [provider, links] of this._providers) {
+    for (let provider of this._providers) {
       this._populateProviderCache(provider, () => {
         if (--numProvidersRemaining == 0)
           executeCallbacks();
       }, aForce);
     }
 
     this._addObserver();
   },
@@ -830,19 +835,17 @@ let Links = {
 
     return pinnedLinks;
   },
 
   /**
    * Resets the links cache.
    */
   resetCache: function Links_resetCache() {
-    for (let provider of this._providers.keys()) {
-      this._providers.set(provider, null);
-    }
+    this._providerLinks.clear();
   },
 
   /**
    * Compares two links.
    * @param aLink1 The first link.
    * @param aLink2 The second link.
    * @return A negative number if aLink1 is ordered before aLink2, zero if
    *         aLink1 and aLink2 have the same ordering, or a positive number if
@@ -870,85 +873,56 @@ let Links = {
     let previousURLCount = map.get(site);
     if (previousURLCount === 1) {
       map.delete(site);
     } else {
       map.set(site, previousURLCount - 1);
     }
   },
 
-  populateProviderCache: function(provider, callback) {
-    if (!this._providers.has(provider)) {
-      throw new Error("Can only populate provider cache for existing provider.");
-    }
-
-    return this._populateProviderCache(provider, callback, false);
-  },
-
   /**
    * Calls getLinks on the given provider and populates our cache for it.
    * @param aProvider The provider whose cache will be populated.
    * @param aCallback The callback to call when finished.
    * @param aForce When true, populates the provider's cache even when it's
    *               already filled.
    */
-  _populateProviderCache: function (aProvider, aCallback, aForce) {
-    let cache = this._providers.get(aProvider);
-    let createCache = !cache;
-    if (createCache) {
-      cache = {
-        // Start with a resolved promise.
-        populatePromise: new Promise(resolve => resolve()),
-      };
-      this._providers.set(aProvider, cache);
-    }
-    // Chain the populatePromise so that calls are effectively queued.
-    cache.populatePromise = cache.populatePromise.then(() => {
-      return new Promise(resolve => {
-        if (!createCache && !aForce) {
-          aCallback();
-          resolve();
-          return;
-        }
-        aProvider.getLinks(links => {
-          // Filter out null and undefined links so we don't have to deal with
-          // them in getLinks when merging links from providers.
-          links = links.filter((link) => !!link);
-          cache.sortedLinks = links;
-          cache.siteMap = links.reduce((map, link) => {
+  _populateProviderCache: function Links_populateProviderCache(aProvider, aCallback, aForce) {
+    if (this._providerLinks.has(aProvider) && !aForce) {
+      aCallback();
+    } else {
+      aProvider.getLinks(links => {
+        // Filter out null and undefined links so we don't have to deal with
+        // them in getLinks when merging links from providers.
+        links = links.filter((link) => !!link);
+        this._providerLinks.set(aProvider, {
+          sortedLinks: links,
+          siteMap: links.reduce((map, link) => {
             this._incrementSiteMap(map, link);
             return map;
-          }, new Map());
-          cache.linkMap = links.reduce((map, link) => {
+          }, new Map()),
+          linkMap: links.reduce((map, link) => {
             map.set(link.url, link);
             return map;
-          }, new Map());
-          aCallback();
-          resolve();
+          }, new Map()),
         });
+        aCallback();
       });
-    });
+    }
   },
 
   /**
    * Merges the cached lists of links from all providers whose lists are cached.
    * @return The merged list.
    */
   _getMergedProviderLinks: function Links__getMergedProviderLinks() {
     // Build a list containing a copy of each provider's sortedLinks list.
     let linkLists = [];
-    for (let provider of this._providers.keys()) {
-      if (!AllPages.enhanced && provider != PlacesProvider) {
-        // Only show history tiles if we're not in 'enhanced' mode.
-        continue;
-      }
-      let links = this._providers.get(provider);
-      if (links && links.sortedLinks) {
-        linkLists.push(links.sortedLinks.slice());
-      }
+    for (let links of this._providerLinks.values()) {
+      linkLists.push(links.sortedLinks.slice());
     }
 
     function getNextLink() {
       let minLinks = null;
       for (let links of linkLists) {
         if (links.length &&
             (!minLinks || Links.compareLinks(links[0], minLinks[0]) < 0))
           minLinks = links;
@@ -967,63 +941,50 @@ let Links = {
   },
 
   /**
    * Called by a provider to notify us when a single link changes.
    * @param aProvider The provider whose link changed.
    * @param aLink The link that changed.  If the link is new, it must have all
    *              of the _sortProperties.  Otherwise, it may have as few or as
    *              many as is convenient.
-   * @param aIndex The current index of the changed link in the sortedLinks
-                   cache in _providers. Defaults to -1 if the provider doesn't know the index
-   * @param aDeleted Boolean indicating if the provider has deleted the link.
    */
-  onLinkChanged: function Links_onLinkChanged(aProvider, aLink, aIndex=-1, aDeleted=false) {
+  onLinkChanged: function Links_onLinkChanged(aProvider, aLink) {
     if (!("url" in aLink))
       throw new Error("Changed links must have a url property");
 
-    let links = this._providers.get(aProvider);
+    let links = this._providerLinks.get(aProvider);
     if (!links)
       // This is not an error, it just means that between the time the provider
       // was added and the future time we call getLinks on it, it notified us of
       // a change.
       return;
 
     let { sortedLinks, siteMap, linkMap } = links;
     let existingLink = linkMap.get(aLink.url);
     let insertionLink = null;
     let updatePages = false;
 
     if (existingLink) {
       // Update our copy's position in O(lg n) by first removing it from its
       // list.  It's important to do this before modifying its properties.
       if (this._sortProperties.some(prop => prop in aLink)) {
-        let idx = aIndex;
-        if (idx < 0) {
-          idx = this._indexOf(sortedLinks, existingLink);
-        } else if (this.compareLinks(aLink, sortedLinks[idx]) != 0) {
-          throw new Error("aLink should be the same as sortedLinks[idx]");
-        }
-
+        let idx = this._indexOf(sortedLinks, existingLink);
         if (idx < 0) {
           throw new Error("Link should be in _sortedLinks if in _linkMap");
         }
         sortedLinks.splice(idx, 1);
-
-        if (aDeleted) {
-          updatePages = true;
-          linkMap.delete(existingLink.url);
-          this._decrementSiteMap(siteMap, existingLink);
-        } else {
-          // Update our copy's properties.
-          Object.assign(existingLink, aLink);
-
-          // Finally, reinsert our copy below.
-          insertionLink = existingLink;
+        // Update our copy's properties.
+        for (let prop of this._sortProperties) {
+          if (prop in aLink) {
+            existingLink[prop] = aLink[prop];
+          }
         }
+        // Finally, reinsert our copy below.
+        insertionLink = existingLink;
       }
       // Update our copy's title in O(1).
       if ("title" in aLink && aLink.title != existingLink.title) {
         existingLink.title = aLink.title;
         updatePages = true;
       }
     }
     else if (this._sortProperties.every(prop => prop in aLink)) {
@@ -1243,30 +1204,18 @@ this.NewTabUtils = {
       this._initialized = true;
       ExpirationFilter.init();
       Telemetry.init();
       return true;
     }
     return false;
   },
 
-  getProviderLinks: function(aProvider) {
-    let cache = Links._providers.get(aProvider);
-    if (cache && cache.sortedLinks) {
-      return cache.sortedLinks;
-    }
-    return [];
-  },
-
   isTopSiteGivenProvider: function(aSite, aProvider) {
-    let cache = Links._providers.get(aProvider);
-    if (cache && cache.siteMap) {
-      return cache.siteMap.has(aSite);
-    }
-    return false;
+    return Links._providerLinks.get(aProvider).siteMap.has(aSite);
   },
 
   isTopPlacesSite: function(aSite) {
     return this.isTopSiteGivenProvider(aSite, PlacesProvider);
   },
 
   /**
    * Restores all sites that have been removed from the grid.
@@ -1294,11 +1243,10 @@ this.NewTabUtils = {
     Links.populateCache(aCallback, true);
   },
 
   links: Links,
   allPages: AllPages,
   linkChecker: LinkChecker,
   pinnedLinks: PinnedLinks,
   blockedLinks: BlockedLinks,
-  gridPrefs: GridPrefs,
-  placesProvider: PlacesProvider
+  gridPrefs: GridPrefs
 };
--- a/toolkit/modules/tests/xpcshell/test_NewTabUtils.js
+++ b/toolkit/modules/tests/xpcshell/test_NewTabUtils.js
@@ -1,120 +1,33 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // See also browser/base/content/test/newtab/.
 
 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
 Cu.import("resource://gre/modules/NewTabUtils.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-const PREF_NEWTAB_ENHANCED = "browser.newtabpage.enhanced";
 
 function run_test() {
-  Services.prefs.setBoolPref(PREF_NEWTAB_ENHANCED, true);
   run_next_test();
 }
 
-add_task(function validCacheMidPopulation() {
-  let expectedLinks = makeLinks(0, 3, 1);
-
-  let provider = new TestProvider(done => done(expectedLinks));
-  provider.maxNumLinks = expectedLinks.length;
-
-  NewTabUtils.initWithoutProviders();
-  NewTabUtils.links.addProvider(provider);
-  let promise = new Promise(resolve => NewTabUtils.links.populateCache(resolve));
-
-  // isTopSiteGivenProvider() and getProviderLinks() should still return results
-  // even when cache is empty or being populated.
-  do_check_false(NewTabUtils.isTopSiteGivenProvider("example1.com", provider));
-  do_check_links(NewTabUtils.getProviderLinks(provider), []);
-
-  yield promise;
-
-  // Once the cache is populated, we get the expected results
-  do_check_true(NewTabUtils.isTopSiteGivenProvider("example1.com", provider));
-  do_check_links(NewTabUtils.getProviderLinks(provider), expectedLinks);
-  NewTabUtils.links.removeProvider(provider);
-});
-
-add_task(function notifyLinkDelete() {
-  let expectedLinks = makeLinks(0, 3, 1);
-
-  let provider = new TestProvider(done => done(expectedLinks));
-  provider.maxNumLinks = expectedLinks.length;
-
-  NewTabUtils.initWithoutProviders();
-  NewTabUtils.links.addProvider(provider);
-  yield new Promise(resolve => NewTabUtils.links.populateCache(resolve));
-
-  do_check_links(NewTabUtils.links.getLinks(), expectedLinks);
-
-  // Remove a link.
-  let removedLink = expectedLinks[2];
-  provider.notifyLinkChanged(removedLink, 2, true);
-  let links = NewTabUtils.links._providers.get(provider);
-
-  // Check that sortedLinks is correctly updated.
-  do_check_links(NewTabUtils.links.getLinks(), expectedLinks.slice(0, 2));
-
-  // Check that linkMap is accurately updated.
-  do_check_eq(links.linkMap.size, 2);
-  do_check_true(links.linkMap.get(expectedLinks[0].url));
-  do_check_true(links.linkMap.get(expectedLinks[1].url));
-  do_check_false(links.linkMap.get(removedLink.url));
-
-  // Check that siteMap is correctly updated.
-  do_check_eq(links.siteMap.size, 2);
-  do_check_true(links.siteMap.has(NewTabUtils.extractSite(expectedLinks[0].url)));
-  do_check_true(links.siteMap.has(NewTabUtils.extractSite(expectedLinks[1].url)));
-  do_check_false(links.siteMap.has(NewTabUtils.extractSite(removedLink.url)));
-
-  NewTabUtils.links.removeProvider(provider);
-});
-
-add_task(function populatePromise() {
-  let count = 0;
-  let expectedLinks = makeLinks(0, 10, 2);
-
-  let getLinksFcn = Task.async(function* (callback) {
-    //Should not be calling getLinksFcn twice
-    count++;
-    do_check_eq(count, 1);
-    yield Promise.resolve();
-    callback(expectedLinks);
-  });
-
-  let provider = new TestProvider(getLinksFcn);
-
-  NewTabUtils.initWithoutProviders();
-  NewTabUtils.links.addProvider(provider);
-
-  NewTabUtils.links.populateProviderCache(provider, () => {});
-  NewTabUtils.links.populateProviderCache(provider, () => {
-    do_check_links(NewTabUtils.links.getLinks(), expectedLinks);
-    NewTabUtils.links.removeProvider(provider);
-  });
-});
-
 add_task(function isTopSiteGivenProvider() {
   let expectedLinks = makeLinks(0, 10, 2);
 
   // The lowest 2 frecencies have the same base domain.
   expectedLinks[expectedLinks.length - 2].url = expectedLinks[expectedLinks.length - 1].url + "Test";
 
   let provider = new TestProvider(done => done(expectedLinks));
   provider.maxNumLinks = expectedLinks.length;
 
   NewTabUtils.initWithoutProviders();
   NewTabUtils.links.addProvider(provider);
-  yield new Promise(resolve => NewTabUtils.links.populateCache(resolve));
+  NewTabUtils.links.populateCache(function () {}, false);
 
   do_check_eq(NewTabUtils.isTopSiteGivenProvider("example2.com", provider), true);
   do_check_eq(NewTabUtils.isTopSiteGivenProvider("example1.com", provider), false);
 
   // Push out frecency 2 because the maxNumLinks is reached when adding frecency 3
   let newLink = makeLink(3);
   provider.notifyLinkChanged(newLink);
 
@@ -144,17 +57,18 @@ add_task(function multipleProviders() {
   let evenProvider = new TestProvider(done => done(evenLinks));
   let oddLinks = makeLinks(0, 2 * NewTabUtils.links.maxNumLinks - 1, 2);
   let oddProvider = new TestProvider(done => done(oddLinks));
 
   NewTabUtils.initWithoutProviders();
   NewTabUtils.links.addProvider(evenProvider);
   NewTabUtils.links.addProvider(oddProvider);
 
-  yield new Promise(resolve => NewTabUtils.links.populateCache(resolve));
+  // This is sync since the providers' getLinks are sync.
+  NewTabUtils.links.populateCache(function () {}, false);
 
   let links = NewTabUtils.links.getLinks();
   let expectedLinks = makeLinks(NewTabUtils.links.maxNumLinks,
                                 2 * NewTabUtils.links.maxNumLinks,
                                 1);
   do_check_eq(links.length, NewTabUtils.links.maxNumLinks);
   do_check_links(links, expectedLinks);
 
@@ -164,17 +78,18 @@ add_task(function multipleProviders() {
 
 add_task(function changeLinks() {
   let expectedLinks = makeLinks(0, 20, 2);
   let provider = new TestProvider(done => done(expectedLinks));
 
   NewTabUtils.initWithoutProviders();
   NewTabUtils.links.addProvider(provider);
 
-  yield new Promise(resolve => NewTabUtils.links.populateCache(resolve));
+  // This is sync since the provider's getLinks is sync.
+  NewTabUtils.links.populateCache(function () {}, false);
 
   do_check_links(NewTabUtils.links.getLinks(), expectedLinks);
 
   // Notify of a new link.
   let newLink = makeLink(19);
   expectedLinks.splice(1, 0, newLink);
   provider.notifyLinkChanged(newLink);
   do_check_links(NewTabUtils.links.getLinks(), expectedLinks);
@@ -204,58 +119,56 @@ add_task(function changeLinks() {
   expectedLinks.pop();
   do_check_eq(expectedLinks.length, provider.maxNumLinks); // Sanity check.
   provider.notifyLinkChanged(newLink);
   do_check_links(NewTabUtils.links.getLinks(), expectedLinks);
 
   // Notify of many links changed.
   expectedLinks = makeLinks(0, 3, 1);
   provider.notifyManyLinksChanged();
-
-  // Since _populateProviderCache() is async, we must wait until the provider's
-  // populate promise has been resolved.
-  yield NewTabUtils.links._providers.get(provider).populatePromise;
-
-  // NewTabUtils.links will now repopulate its cache
+  // NewTabUtils.links will now repopulate its cache, which is sync since
+  // the provider's getLinks is sync.
   do_check_links(NewTabUtils.links.getLinks(), expectedLinks);
 
   NewTabUtils.links.removeProvider(provider);
 });
 
 add_task(function oneProviderAlreadyCached() {
   let links1 = makeLinks(0, 10, 1);
   let provider1 = new TestProvider(done => done(links1));
 
   NewTabUtils.initWithoutProviders();
   NewTabUtils.links.addProvider(provider1);
 
-  yield new Promise(resolve => NewTabUtils.links.populateCache(resolve));
+  // This is sync since the provider's getLinks is sync.
+  NewTabUtils.links.populateCache(function () {}, false);
   do_check_links(NewTabUtils.links.getLinks(), links1);
 
   let links2 = makeLinks(10, 20, 1);
   let provider2 = new TestProvider(done => done(links2));
   NewTabUtils.links.addProvider(provider2);
 
-  yield new Promise(resolve => NewTabUtils.links.populateCache(resolve));
+  NewTabUtils.links.populateCache(function () {}, false);
   do_check_links(NewTabUtils.links.getLinks(), links2.concat(links1));
 
   NewTabUtils.links.removeProvider(provider1);
   NewTabUtils.links.removeProvider(provider2);
 });
 
 add_task(function newLowRankedLink() {
   // Init a provider with 10 links and make its maximum number also 10.
   let links = makeLinks(0, 10, 1);
   let provider = new TestProvider(done => done(links));
   provider.maxNumLinks = links.length;
 
   NewTabUtils.initWithoutProviders();
   NewTabUtils.links.addProvider(provider);
 
-  yield new Promise(resolve => NewTabUtils.links.populateCache(resolve));
+  // This is sync since the provider's getLinks is sync.
+  NewTabUtils.links.populateCache(function () {}, false);
   do_check_links(NewTabUtils.links.getLinks(), links);
 
   // Notify of a new link that's low-ranked enough not to make the list.
   let newLink = makeLink(0);
   provider.notifyLinkChanged(newLink);
   do_check_links(NewTabUtils.links.getLinks(), links);
 
   // Notify about the new link's title change.
@@ -324,29 +237,26 @@ function TestProvider(getLinksFn) {
   this.getLinks = getLinksFn;
   this._observers = new Set();
 }
 
 TestProvider.prototype = {
   addObserver: function (observer) {
     this._observers.add(observer);
   },
-  notifyLinkChanged: function (link, index=-1, deleted=false) {
-    this._notifyObservers("onLinkChanged", link, index, deleted);
+  notifyLinkChanged: function (link) {
+    this._notifyObservers("onLinkChanged", link);
   },
   notifyManyLinksChanged: function () {
     this._notifyObservers("onManyLinksChanged");
   },
-  _notifyObservers: function () {
-    let observerMethodName = arguments[0];
-    let args = Array.prototype.slice.call(arguments, 1);
-    args.unshift(this);
+  _notifyObservers: function (observerMethodName, arg) {
     for (let obs of this._observers) {
       if (obs[observerMethodName])
-        obs[observerMethodName].apply(NewTabUtils.links, args);
+        obs[observerMethodName](this, arg);
     }
   },
 };
 
 function do_check_links(actualLinks, expectedLinks) {
   do_check_true(Array.isArray(actualLinks));
   do_check_eq(actualLinks.length, expectedLinks.length);
   for (let i = 0; i < expectedLinks.length; i++) {