Bug 1214287 - 2-4% linux64/win7/8 ts_paint regression on inbound (v.44) seen on Oct 12, 2015 from rev 3012b7a2c97c r=mconley
authorOlivier Yiptong <olivier@olivieryiptong.com>
Fri, 30 Oct 2015 23:00:35 -0400
changeset 270884 ce12a70a081a64662f4fabaa5cc6a730874b60d3
parent 270883 d8d75be11275a4aa285a3d17edb8f647647f2527
child 270885 696ea2cdc032d1d031decb9c8f45d938c23757a4
push id18459
push usercbook@mozilla.com
push dateTue, 03 Nov 2015 11:14:32 +0000
treeherderb2g-inbound@09173d8e6694 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1214287
milestone45.0a1
Bug 1214287 - 2-4% linux64/win7/8 ts_paint regression on inbound (v.44) seen on Oct 12, 2015 from rev 3012b7a2c97c r=mconley
browser/components/newtab/RemoteAboutNewTab.jsm
browser/components/newtab/RemoteDirectoryLinksProvider.jsm
browser/components/newtab/moz.build
browser/components/newtab/tests/xpcshell/test_RemoteDirectoryLinksProvider.js
browser/components/newtab/tests/xpcshell/xpcshell.ini
browser/components/nsBrowserGlue.js
browser/modules/DirectoryLinksProvider.jsm
--- a/browser/components/newtab/RemoteAboutNewTab.jsm
+++ b/browser/components/newtab/RemoteAboutNewTab.jsm
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* globals Services, XPCOMUtils, RemotePages, RemoteNewTabLocation, RemoteNewTabUtils, Task  */
-/* globals BackgroundPageThumbs, PageThumbs, RemoteDirectoryLinksProvider */
+/* globals BackgroundPageThumbs, PageThumbs, DirectoryLinksProvider */
 /* exported RemoteAboutNewTab */
 
 "use strict";
 
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 const XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
 
@@ -22,18 +22,18 @@ Cu.importGlobalProperties(["URL"]);
 XPCOMUtils.defineLazyModuleGetter(this, "RemotePages",
   "resource://gre/modules/RemotePageManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RemoteNewTabUtils",
   "resource:///modules/RemoteNewTabUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BackgroundPageThumbs",
   "resource://gre/modules/BackgroundPageThumbs.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
   "resource://gre/modules/PageThumbs.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RemoteDirectoryLinksProvider",
-  "resource:///modules/RemoteDirectoryLinksProvider.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DirectoryLinksProvider",
+  "resource:///modules/DirectoryLinksProvider.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RemoteNewTabLocation",
   "resource:///modules/RemoteNewTabLocation.jsm");
 
 let RemoteAboutNewTab = {
 
   pageListener: null,
 
   /**
@@ -167,17 +167,17 @@ let RemoteAboutNewTab = {
 
   /**
    * Get the set of enhanced links (if any) from the Directory Links Provider.
    */
   getEnhancedLinks: function() {
     let enhancedLinks = [];
     for (let link of RemoteNewTabUtils.links.getLinks()) {
       if (link) {
-        enhancedLinks.push(RemoteDirectoryLinksProvider.getEnhancedLink(link));
+        enhancedLinks.push(DirectoryLinksProvider.getEnhancedLink(link));
       }
     }
     return enhancedLinks;
   },
 
   /**
    * Listens for a preference change or session purge for all pages and sends
    * a message to update the pages that are open. If a session purge occured,
deleted file mode 100644
--- a/browser/components/newtab/RemoteDirectoryLinksProvider.jsm
+++ /dev/null
@@ -1,1171 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["RemoteDirectoryLinksProvider"];
-
-const Ci = Components.interfaces;
-const Cc = Components.classes;
-const Cu = Components.utils;
-const ParserUtils =  Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils);
-
-Cu.importGlobalProperties(["XMLHttpRequest"]);
-
-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, "RemoteNewTabUtils",
-  "resource:///modules/RemoteNewTabUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "OS",
-  "resource://gre/modules/osfile.jsm")
-XPCOMUtils.defineLazyModuleGetter(this, "Promise",
-  "resource://gre/modules/Promise.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
-  "resource://gre/modules/UpdateUtils.jsm");
-XPCOMUtils.defineLazyServiceGetter(this, "eTLD",
-  "@mozilla.org/network/effective-tld-service;1",
-  "nsIEffectiveTLDService");
-XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => {
-  return new TextDecoder();
-});
-XPCOMUtils.defineLazyGetter(this, "gCryptoHash", function () {
-  return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
-});
-XPCOMUtils.defineLazyGetter(this, "gUnicodeConverter", function () {
-  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
-                    .createInstance(Ci.nsIScriptableUnicodeConverter);
-  converter.charset = 'utf8';
-  return converter;
-});
-
-
-// The filename where directory links are stored locally
-const DIRECTORY_LINKS_FILE = "directoryLinks.json";
-const DIRECTORY_LINKS_TYPE = "application/json";
-
-// The preference that tells whether to match the OS locale
-const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
-
-// The preference that tells what locale the user selected
-const PREF_SELECTED_LOCALE = "general.useragent.locale";
-
-// The preference that tells where to obtain directory links
-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 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"]);
-
-// Only allow urls to Mozilla's CDN or empty (for data URIs)
-const ALLOWED_URL_BASE = new Set(["mozilla.net", ""]);
-
-// The frecency of a directory link
-const DIRECTORY_FRECENCY = 1000;
-
-// The frecency of a suggested link
-const SUGGESTED_FRECENCY = Infinity;
-
-// The filename where frequency cap data stored locally
-const FREQUENCY_CAP_FILE = "frequencyCap.json";
-
-// Default settings for daily and total frequency caps
-const DEFAULT_DAILY_FREQUENCY_CAP = 3;
-const DEFAULT_TOTAL_FREQUENCY_CAP = 10;
-
-// Default timeDelta to prune unused frequency cap objects
-// currently set to 10 days in milliseconds
-const DEFAULT_PRUNE_TIME_DELTA = 10*24*60*60*1000;
-
-// The min number of visible (not blocked) history tiles to have before showing suggested tiles
-const MIN_VISIBLE_HISTORY_TILES = 8;
-
-// The max number of visible (not blocked) history tiles to test for inadjacency
-const MAX_VISIBLE_HISTORY_TILES = 15;
-
-// 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"];
-
-// Location of inadjacent sites json
-const INADJACENCY_SOURCE = "chrome://browser/content/newtab/newTab.inadjacent.json";
-
-/**
- * Singleton that serves as the provider of directory links.
- * Directory links are a hard-coded set of links shown if a user's link
- * inventory is empty.
- */
-let RemoteDirectoryLinksProvider = {
-
-  __linksURL: null,
-
-  _observers: new Set(),
-
-  // links download deferred, resolved upon download completion
-  _downloadDeferred: null,
-
-  // 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 a list of suggested link objects
-   */
-  _suggestedLinks: new Map(),
-
-  /**
-   * Frequency Cap object - maintains daily and total tile counts, and frequency cap settings
-   */
-  _frequencyCaps: {},
-
-  /**
-   * A set of top sites that we can provide suggested links for
-   */
-  _topSitesWithSuggestedLinks: new Set(),
-
-  /**
-   * lookup Set of inadjacent domains
-   */
-  _inadjacentSites: new Set(),
-
-  /**
-   * This flag is set if there is a suggested tile configured to avoid
-   * inadjacent sites in new tab
-   */
-  _avoidInadjacentSites: false,
-
-  /**
-   * This flag is set if _avoidInadjacentSites is true and there is
-   * an inadjacent site in the new tab
-   */
-  _newTabHasInadjacentSite: false,
-
-  get _observedPrefs() {
-    return Object.freeze({
-      enhanced: PREF_NEWTAB_ENHANCED,
-      linksURL: PREF_DIRECTORY_SOURCE,
-      matchOSLocale: PREF_MATCH_OS_LOCALE,
-      prefSelectedLocale: PREF_SELECTED_LOCALE,
-    });
-  },
-
-  get _linksURL() {
-    if (!this.__linksURL) {
-      try {
-        this.__linksURL = Services.prefs.getCharPref(this._observedPrefs["linksURL"]);
-        this.__linksURLModified = Services.prefs.prefHasUserValue(this._observedPrefs["linksURL"]);
-      }
-      catch (e) {
-        Cu.reportError("Error fetching directory links url from prefs: " + e);
-      }
-    }
-    return this.__linksURL;
-  },
-
-  /**
-   * Gets the currently selected locale for display.
-   * @return  the selected locale or "en-US" if none is selected
-   */
-  get locale() {
-    let matchOS;
-    try {
-      matchOS = Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE);
-    }
-    catch (e) {}
-
-    if (matchOS) {
-      return Services.locale.getLocaleComponentForUserAgent();
-    }
-
-    try {
-      let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE,
-                                                  Ci.nsIPrefLocalizedString);
-      if (locale) {
-        return locale.data;
-      }
-    }
-    catch (e) {}
-
-    try {
-      return Services.prefs.getCharPref(PREF_SELECTED_LOCALE);
-    }
-    catch (e) {}
-
-    return "en-US";
-  },
-
-  /**
-   * Set appropriate default ping behavior controlled by enhanced pref
-   */
-  _setDefaultEnhanced: function RemoteDirectoryLinksProvider_setDefaultEnhanced() {
-    if (!Services.prefs.prefHasUserValue(PREF_NEWTAB_ENHANCED)) {
-      let enhanced = Services.prefs.getBoolPref(PREF_NEWTAB_ENHANCED);
-      try {
-        // Default to not enhanced if DNT is set to tell websites to not track
-        if (Services.prefs.getBoolPref("privacy.donottrackheader.enabled")) {
-          enhanced = false;
-        }
-      }
-      catch(ex) {}
-      Services.prefs.setBoolPref(PREF_NEWTAB_ENHANCED, enhanced);
-    }
-  },
-
-  observe: function RemoteDirectoryLinksProvider_observe(aSubject, aTopic, aData) {
-    if (aTopic == "nsPref:changed") {
-      switch (aData) {
-        // Re-set the default in case the user clears the pref
-        case this._observedPrefs.enhanced:
-          this._setDefaultEnhanced();
-          break;
-
-        case this._observedPrefs.linksURL:
-          delete this.__linksURL;
-          // fallthrough
-
-        // Force directory download on changes to fetch related prefs
-        case this._observedPrefs.matchOSLocale:
-        case this._observedPrefs.prefSelectedLocale:
-          this._fetchAndCacheLinksIfNecessary(true);
-          break;
-      }
-    }
-  },
-
-  _addPrefsObserver: function RemoteDirectoryLinksProvider_addObserver() {
-    for (let pref in this._observedPrefs) {
-      let prefName = this._observedPrefs[pref];
-      Services.prefs.addObserver(prefName, this, false);
-    }
-  },
-
-  _removePrefsObserver: function RemoteDirectoryLinksProvider_removeObserver() {
-    for (let pref in this._observedPrefs) {
-      let prefName = this._observedPrefs[pref];
-      Services.prefs.removeObserver(prefName, this);
-    }
-  },
-
-  _cacheSuggestedLinks: function(link) {
-    // Don't cache links that don't have the expected 'frecent_sites'
-    if (!link.frecent_sites) {
-      return;
-    }
-
-    for (let suggestedSite of link.frecent_sites) {
-      let suggestedMap = this._suggestedLinks.get(suggestedSite) || new Map();
-      suggestedMap.set(link.url, link);
-      this._setupStartEndTime(link);
-      this._suggestedLinks.set(suggestedSite, suggestedMap);
-    }
-  },
-
-  _fetchAndCacheLinks: function RemoteDirectoryLinksProvider_fetchAndCacheLinks(uri) {
-    // Replace with the same display locale used for selecting links data
-    uri = uri.replace("%LOCALE%", this.locale);
-    uri = uri.replace("%CHANNEL%", UpdateUtils.UpdateChannel);
-
-    return this._downloadJsonData(uri).then(json => {
-      return OS.File.writeAtomic(this._directoryFilePath, json, {tmpPath: this._directoryFilePath + ".tmp"});
-    });
-  },
-
-  /**
-   * Downloads a links with json content
-   * @param download uri
-   * @return promise resolved to json string, "{}" returned if status != 200
-   */
-  _downloadJsonData: function RemoteDirectoryLinksProvider__downloadJsonData(uri) {
-    let deferred = Promise.defer();
-    let xmlHttp = this._newXHR();
-
-    xmlHttp.onload = function(aResponse) {
-      let json = this.responseText;
-      if (this.status && this.status != 200) {
-        json = "{}";
-      }
-      deferred.resolve(json);
-    };
-
-    xmlHttp.onerror = function(e) {
-      deferred.reject("Fetching " + uri + " results in error code: " + e.target.status);
-    };
-
-    try {
-      xmlHttp.open("GET", uri);
-      // Override the type so XHR doesn't complain about not well-formed XML
-      xmlHttp.overrideMimeType(DIRECTORY_LINKS_TYPE);
-      // Set the appropriate request type for servers that require correct types
-      xmlHttp.setRequestHeader("Content-Type", DIRECTORY_LINKS_TYPE);
-      xmlHttp.send();
-    } catch (e) {
-      deferred.reject("Error fetching " + uri);
-      Cu.reportError(e);
-    }
-    return deferred.promise;
-  },
-
-  /**
-   * Downloads directory links if needed
-   * @return promise resolved immediately if no download needed, or upon completion
-   */
-  _fetchAndCacheLinksIfNecessary: function RemoteDirectoryLinksProvider_fetchAndCacheLinksIfNecessary(forceDownload=false) {
-    if (this._downloadDeferred) {
-      // fetching links already - just return the promise
-      return this._downloadDeferred.promise;
-    }
-
-    if (forceDownload || this._needsDownload) {
-      this._downloadDeferred = Promise.defer();
-      this._fetchAndCacheLinks(this._linksURL).then(() => {
-        // the new file was successfully downloaded and cached, so update a timestamp
-        this._lastDownloadMS = Date.now();
-        this._downloadDeferred.resolve();
-        this._downloadDeferred = null;
-        this._callObservers("onManyLinksChanged")
-      },
-      error => {
-        this._downloadDeferred.resolve();
-        this._downloadDeferred = null;
-        this._callObservers("onDownloadFail");
-      });
-      return this._downloadDeferred.promise;
-    }
-
-    // download is not needed
-    return Promise.resolve();
-  },
-
-  /**
-   * @return true if download is needed, false otherwise
-   */
-  get _needsDownload () {
-    // fail if last download occured less then 24 hours ago
-    if ((Date.now() - this._lastDownloadMS) > this._downloadIntervalMS) {
-      return true;
-    }
-    return false;
-  },
-
-  /**
-   * Create a new XMLHttpRequest that is anonymous, i.e., doesn't send cookies
-   */
-  _newXHR() {
-    return new XMLHttpRequest({mozAnon: true});
-  },
-
-  /**
-   * 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.
-   */
-  _readDirectoryLinksFile: function RemoteDirectoryLinksProvider_readDirectoryLinksFile() {
-    let emptyOutput = {directory: [], suggested: [], enhanced: []};
-    return OS.File.read(this._directoryFilePath).then(binaryData => {
-      let output;
-      try {
-        let json = gTextDecoder.decode(binaryData);
-        let linksObj = JSON.parse(json);
-        output = {directory: linksObj.directory || [],
-                  suggested: linksObj.suggested || [],
-                  enhanced:  linksObj.enhanced  || []};
-      }
-      catch (e) {
-        Cu.reportError(e);
-      }
-      return output || emptyOutput;
-    },
-    error => {
-      Cu.reportError(error);
-      return emptyOutput;
-    });
-  },
-
-  /**
-   * Translates link.time_limits to UTC miliseconds and sets
-   * link.startTime and link.endTime properties in link object
-   */
-  _setupStartEndTime: function RemoteDirectoryLinksProvider_setupStartEndTime(link) {
-    // set start/end limits. Use ISO_8601 format: '2014-01-10T20:20:20.600Z'
-    // (details here http://en.wikipedia.org/wiki/ISO_8601)
-    // Note that if timezone is missing, FX will interpret as local time
-    // meaning that the server can sepecify any time, but if the capmaign
-    // needs to start at same time across multiple timezones, the server
-    // omits timezone indicator
-    if (!link.time_limits) {
-      return;
-    }
-
-    let parsedTime;
-    if (link.time_limits.start) {
-      parsedTime = Date.parse(link.time_limits.start);
-      if (parsedTime && !isNaN(parsedTime)) {
-        link.startTime = parsedTime;
-      }
-    }
-    if (link.time_limits.end) {
-      parsedTime = Date.parse(link.time_limits.end);
-      if (parsedTime && !isNaN(parsedTime)) {
-        link.endTime = parsedTime;
-      }
-    }
-  },
-
-  /*
-   * Handles campaign timeout
-   */
-  _onCampaignTimeout: function RemoteDirectoryLinksProvider_onCampaignTimeout() {
-    // _campaignTimeoutID is invalid here, so just set it to null
-    this._campaignTimeoutID = null;
-    this._updateSuggestedTile();
-  },
-
-  /*
-   * Clears capmpaign timeout
-   */
-  _clearCampaignTimeout: function RemoteDirectoryLinksProvider_clearCampaignTimeout() {
-    if (this._campaignTimeoutID) {
-      clearTimeout(this._campaignTimeoutID);
-      this._campaignTimeoutID = null;
-    }
-  },
-
-  /**
-   * Setup capmpaign timeout to recompute suggested tiles upon
-   * reaching soonest start or end time for the campaign
-   * @param timeout in milliseconds
-   */
-  _setupCampaignTimeCheck: function RemoteDirectoryLinksProvider_setupCampaignTimeCheck(timeout) {
-    // sanity check
-    if (!timeout || timeout <= 0) {
-      return;
-    }
-    this._clearCampaignTimeout();
-    // setup next timeout
-    this._campaignTimeoutID = setTimeout(this._onCampaignTimeout.bind(this), timeout);
-  },
-
-  /**
-   * Test link for campaign time limits: checks if link falls within start/end time
-   * and returns an object containing a use flag and the timeoutDate milliseconds
-   * when the link has to be re-checked for campaign start-ready or end-reach
-   * @param link
-   * @return object {use: true or false, timeoutDate: milliseconds or null}
-   */
-  _testLinkForCampaignTimeLimits: function RemoteDirectoryLinksProvider_testLinkForCampaignTimeLimits(link) {
-    let currentTime = Date.now();
-    // test for start time first
-    if (link.startTime && link.startTime > currentTime) {
-      // not yet ready for start
-      return {use: false, timeoutDate: link.startTime};
-    }
-    // otherwise check for end time
-    if (link.endTime) {
-      // passed end time
-      if (link.endTime <= currentTime) {
-        return {use: false};
-      }
-      // otherwise link is still ok, but we need to set timeoutDate
-      return {use: true, timeoutDate: link.endTime};
-    }
-    // if we are here, the link is ok and no timeoutDate needed
-    return {use: true};
-  },
-
-  /**
-   * Get the enhanced link object for a link (whether history or directory)
-   */
-  getEnhancedLink: function RemoteDirectoryLinksProvider_getEnhancedLink(link) {
-    // Use the provided link if it's already enhanced
-    return link.enhancedImageURI && link ? link :
-           this._enhancedLinks.get(RemoteNewTabUtils.extractSite(link.url));
-  },
-
-  /**
-   * Check if a url's scheme is in a Set of allowed schemes and if the base
-   * domain is allowed.
-   * @param url to check
-   * @param allowed Set of allowed schemes
-   * @param checkBase boolean to check the base domain
-   */
-  isURLAllowed(url, allowed, checkBase) {
-    // Assume no url is an allowed url
-    if (!url) {
-      return true;
-    }
-
-    let scheme = "", base = "";
-    try {
-      // A malformed url will not be allowed
-      let uri = Services.io.newURI(url, null, null);
-      scheme = uri.scheme;
-
-      // URIs without base domains will be allowed
-      base = Services.eTLD.getBaseDomain(uri);
-    }
-    catch(ex) {}
-    // Require a scheme match and the base only if desired
-    return allowed.has(scheme) && (!checkBase || ALLOWED_URL_BASE.has(base));
-  },
-
-  _escapeChars(text) {
-    let charMap = {
-      '&': '&amp;',
-      '<': '&lt;',
-      '>': '&gt;',
-      '"': '&quot;',
-      "'": '&#039;'
-    };
-
-    return text.replace(/[&<>"']/g, (character) => charMap[character]);
-  },
-
-  /**
-   * Gets the current set of directory links.
-   * @param aCallback The function that the array of links is passed to.
-   */
-  getLinks: function RemoteDirectoryLinksProvider_getLinks(aCallback) {
-    this._readDirectoryLinksFile().then(rawLinks => {
-      // Reset the cache of suggested tiles and enhanced images for this new set of links
-      this._enhancedLinks.clear();
-      this._suggestedLinks.clear();
-      this._clearCampaignTimeout();
-      this._avoidInadjacentSites = false;
-
-      // Only check base domain for images when using the default pref
-      let checkBase = !this.__linksURLModified;
-      let validityFilter = function(link) {
-        // Make sure the link url is allowed and images too if they exist
-        return this.isURLAllowed(link.url, ALLOWED_LINK_SCHEMES, false) &&
-               this.isURLAllowed(link.imageURI, ALLOWED_IMAGE_SCHEMES, checkBase) &&
-               this.isURLAllowed(link.enhancedImageURI, ALLOWED_IMAGE_SCHEMES, checkBase);
-      }.bind(this);
-
-      rawLinks.suggested.filter(validityFilter).forEach((link, position) => {
-        // Suggested sites must have an adgroup name.
-        if (!link.adgroup_name) {
-          return;
-        }
-
-        let sanitizeFlags = ParserUtils.SanitizerCidEmbedsOnly |
-          ParserUtils.SanitizerDropForms |
-          ParserUtils.SanitizerDropNonCSSPresentation;
-
-        link.explanation = this._escapeChars(link.explanation ? ParserUtils.convertToPlainText(link.explanation, sanitizeFlags, 0) : "");
-        link.targetedName = this._escapeChars(ParserUtils.convertToPlainText(link.adgroup_name, sanitizeFlags, 0));
-        link.lastVisitDate = rawLinks.suggested.length - position;
-        // check if link wants to avoid inadjacent sites
-        if (link.check_inadjacency) {
-          this._avoidInadjacentSites = true;
-        }
-
-        // 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._updateFrequencyCapSettings(link);
-      });
-
-      rawLinks.enhanced.filter(validityFilter).forEach((link, position) => {
-        link.lastVisitDate = rawLinks.enhanced.length - position;
-
-        // Stash the enhanced image for the site
-        if (link.enhancedImageURI) {
-          this._enhancedLinks.set(RemoteNewTabUtils.extractSite(link.url), link);
-        }
-      });
-
-      let links = rawLinks.directory.filter(validityFilter).map((link, position) => {
-        link.lastVisitDate = rawLinks.directory.length - position;
-        link.frecency = DIRECTORY_FRECENCY;
-        return link;
-      });
-
-      // Allow for one link suggestion on top of the default directory links
-      this.maxNumLinks = links.length + 1;
-
-      // prune frequency caps of outdated urls
-      this._pruneFrequencyCapUrls();
-      // write frequency caps object to disk asynchronously
-      this._writeFrequencyCapFile();
-
-      return links;
-    }).catch(ex => {
-      Cu.reportError(ex);
-      return [];
-    }).then(links => {
-      aCallback(links);
-      this._populatePlacesLinks();
-    });
-  },
-
-  init: function RemoteDirectoryLinksProvider_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;
-
-    // setup frequency cap file path
-    this._frequencyCapFilePath = OS.Path.join(OS.Constants.Path.localProfileDir, FREQUENCY_CAP_FILE);
-    // setup inadjacent sites URL
-    this._inadjacentSitesUrl = INADJACENCY_SOURCE;
-
-    RemoteNewTabUtils.placesProvider.addObserver(this);
-    RemoteNewTabUtils.links.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);
-      }
-      // read frequency cap file
-      yield this._readFrequencyCapFile();
-      // fetch directory on startup without force
-      yield this._fetchAndCacheLinksIfNecessary();
-      // fecth inadjacent sites on startup
-      yield this._loadInadjacentSites();
-    }.bind(this));
-  },
-
-  _handleManyLinksChanged: function() {
-    this._topSitesWithSuggestedLinks.clear();
-    this._suggestedLinks.forEach((suggestedLinks, site) => {
-      if (RemoteNewTabUtils.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 = RemoteNewTabUtils.extractSite(aLink.url);
-    let linkStored = this._topSitesWithSuggestedLinks.has(changedLinkSite);
-
-    if (!RemoteNewTabUtils.isTopPlacesSite(changedLinkSite) && linkStored) {
-      this._topSitesWithSuggestedLinks.delete(changedLinkSite);
-      return true;
-    }
-
-    if (this._suggestedLinks.has(changedLinkSite) &&
-        RemoteNewTabUtils.isTopPlacesSite(changedLinkSite) && !linkStored) {
-      this._topSitesWithSuggestedLinks.add(changedLinkSite);
-      return true;
-    }
-
-    // always run _updateSuggestedTile if aLink is inadjacent
-    // and there are tiles configured to avoid it
-    if (this._avoidInadjacentSites && this._isInadjacentLink(aLink)) {
-      return true;
-    }
-
-    return false;
-  },
-
-  _populatePlacesLinks: function () {
-    RemoteNewTabUtils.links.populateProviderCache(RemoteNewTabUtils.placesProvider, () => {
-      this._handleManyLinksChanged();
-    });
-  },
-
-  onDeleteURI: function(aProvider, aLink) {
-    let {url} = aLink;
-    // remove clicked flag for that url and
-    // call observer upon disk write completion
-    this._removeTileClick(url).then(() => {
-      this._callObservers("onDeleteURI", url);
-    });
-  },
-
-  onClearHistory: function() {
-    // remove all clicked flags and call observers upon file write
-    this._removeAllTileClicks().then(() => {
-      this._callObservers("onClearHistory");
-    });
-  },
-
-  onLinkChanged: function (aProvider, aLink) {
-    // Make sure RemoteNewTabUtils.links handles the notification first.
-    setTimeout(() => {
-      if (this._handleLinkChanged(aLink) || this._shouldUpdateSuggestedTile()) {
-        this._updateSuggestedTile();
-      }
-    }, 0);
-  },
-
-  onManyLinksChanged: function () {
-    // Make sure RemoteNewTabUtils.links handles the notification first.
-    setTimeout(() => {
-      this._handleManyLinksChanged();
-    }, 0);
-  },
-
-  _getCurrentTopSiteCount: function() {
-    let visibleTopSiteCount = 0;
-    let newTabLinks = RemoteNewTabUtils.links.getLinks();
-    for (let link of newTabLinks.slice(0, MIN_VISIBLE_HISTORY_TILES)) {
-      // compute visibleTopSiteCount for suggested tiles
-      if (link && (link.type == "history" || link.type == "enhanced")) {
-        visibleTopSiteCount++;
-      }
-    }
-    // since newTabLinks are available, set _newTabHasInadjacentSite here
-    // note that _shouldUpdateSuggestedTile is called by _updateSuggestedTile
-    this._newTabHasInadjacentSite = this._avoidInadjacentSites && this._checkForInadjacentSites(newTabLinks);
-
-    return visibleTopSiteCount;
-  },
-
-  _shouldUpdateSuggestedTile: function() {
-    let sortedLinks = RemoteNewTabUtils.getProviderLinks(this);
-
-    let mostFrecentLink = {};
-    if (sortedLinks && sortedLinks.length) {
-      mostFrecentLink = sortedLinks[0]
-    }
-
-    let currTopSiteCount = this._getCurrentTopSiteCount();
-    if ((!mostFrecentLink.targetedSite && currTopSiteCount >= MIN_VISIBLE_HISTORY_TILES) ||
-        (mostFrecentLink.targetedSite && currTopSiteCount < MIN_VISIBLE_HISTORY_TILES)) {
-      // If mostFrecentLink has a targetedSite then mostFrecentLink is a suggested link.
-      // If we have enough history links (8+) to show a suggested tile and we are not
-      // already showing one, then we should update (to *attempt* to add a suggested tile).
-      // OR if we don't have enough history to show a suggested tile (<8) and we are
-      // currently showing one, we should update (to remove it).
-      return true;
-    }
-
-    return false;
-  },
-
-  /**
-   * 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 = RemoteNewTabUtils.getProviderLinks(this);
-
-    if (!sortedLinks) {
-      // If RemoteNewTabUtils.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 || !this._shouldUpdateSuggestedTile()) {
-      // There are no potential suggested links we can show or not
-      // enough history for a suggested tile.
-      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 nextTimeout;
-    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._testFrequencyCapLimits(url)) {
-          return;
-        }
-
-        // as we iterate suggestedLinks, check for campaign start/end
-        // time limits, and set nextTimeout to the closest timestamp
-        let {use, timeoutDate} = this._testLinkForCampaignTimeLimits(suggestedLink);
-        // update nextTimeout is necessary
-        if (timeoutDate && (!nextTimeout || nextTimeout > timeoutDate)) {
-          nextTimeout = timeoutDate;
-        }
-        // Skip link if it falls outside campaign time limits
-        if (!use) {
-          return;
-        }
-
-        // Skip link if it avoids inadjacent sites and newtab has one
-        if (suggestedLink.check_inadjacency && this._newTabHasInadjacentSite) {
-          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);
-      })
-    });
-
-    // setup timeout check for starting or ending campaigns
-    if (nextTimeout) {
-      this._setupCampaignTimeCheck(nextTimeout - Date.now());
-    }
-
-    // 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;
-   },
-
-  /**
-   * Loads inadjacent sites
-   * @return a promise resolved when lookup Set for sites is built
-   */
-  _loadInadjacentSites: function RemoteDirectoryLinksProvider_loadInadjacentSites() {
-    return this._downloadJsonData(this._inadjacentSitesUrl).then(jsonString => {
-      let jsonObject = {};
-      try {
-        jsonObject = JSON.parse(jsonString);
-      }
-      catch (e) {
-        Cu.reportError(e);
-      }
-
-      this._inadjacentSites = new Set(jsonObject.domains);
-    });
-  },
-
-  /**
-   * Genegrates hash suitable for looking up inadjacent site
-   * @param value to hsh
-   * @return hased value, base64-ed
-   */
-  _generateHash: function RemoteDirectoryLinksProvider_generateHash(value) {
-    let byteArr = gUnicodeConverter.convertToByteArray(value);
-    gCryptoHash.init(gCryptoHash.MD5);
-    gCryptoHash.update(byteArr, byteArr.length);
-    return gCryptoHash.finish(true);
-  },
-
-  /**
-   * Checks if link belongs to inadjacent domain
-   * @param link to check
-   * @return true for inadjacent domains, false otherwise
-   */
-  _isInadjacentLink: function RemoteDirectoryLinksProvider_isInadjacentLink(link) {
-    let baseDomain = link.baseDomain || RemoteNewTabUtils.extractSite(link.url || "");
-    if (!baseDomain) {
-        return false;
-    }
-    // check if hashed domain is inadjacent
-    return this._inadjacentSites.has(this._generateHash(baseDomain));
-  },
-
-  /**
-   * Checks if new tab has inadjacent site
-   * @param new tab links (or nothing, in which case RemoteNewTabUtils.links.getLinks() is called
-   * @return true if new tab shows has inadjacent site
-   */
-  _checkForInadjacentSites: function RemoteDirectoryLinksProvider_checkForInadjacentSites(newTabLink) {
-    let links = newTabLink || RemoteNewTabUtils.links.getLinks();
-    for (let link of links.slice(0, MAX_VISIBLE_HISTORY_TILES)) {
-      // check links against inadjacent list - specifically include ALL link types
-      if (this._isInadjacentLink(link)) {
-        return true;
-      }
-    }
-    return false;
-  },
-
-  /**
-   * Reads json file, parses its content, and returns resulting object
-   * @param json file path
-   * @param json object to return in case file read or parse fails
-   * @return a promise resolved to a valid object or undefined upon error
-   */
-  _readJsonFile: Task.async(function* (filePath, nullObject) {
-    let jsonObj;
-    try {
-      let binaryData = yield OS.File.read(filePath);
-      let json = gTextDecoder.decode(binaryData);
-      jsonObj = JSON.parse(json);
-    }
-    catch (e) {}
-    return jsonObj || nullObject;
-  }),
-
-  /**
-   * Loads frequency cap object from file and parses its content
-   * @return a promise resolved upon load completion
-   *         on error or non-exstent file _frequencyCaps is set to empty object
-   */
-  _readFrequencyCapFile: Task.async(function* () {
-    // set _frequencyCaps object to file's content or empty object
-    this._frequencyCaps = yield this._readJsonFile(this._frequencyCapFilePath, {});
-  }),
-
-  /**
-   * Saves frequency cap object to file
-   * @return a promise resolved upon file i/o completion
-   */
-  _writeFrequencyCapFile: function RemoteDirectoryLinksProvider_writeFrequencyCapFile() {
-    let json = JSON.stringify(this._frequencyCaps || {});
-    return OS.File.writeAtomic(this._frequencyCapFilePath, json, {tmpPath: this._frequencyCapFilePath + ".tmp"});
-  },
-
-  /**
-   * Clears frequency cap object and writes empty json to file
-   * @return a promise resolved upon file i/o completion
-   */
-  _clearFrequencyCap: function RemoteDirectoryLinksProvider_clearFrequencyCap() {
-    this._frequencyCaps = {};
-    return this._writeFrequencyCapFile();
-  },
-
-  /**
-   * updates frequency cap configuration for a link
-   */
-  _updateFrequencyCapSettings: function RemoteDirectoryLinksProvider_updateFrequencyCapSettings(link) {
-    let capsObject = this._frequencyCaps[link.url];
-    if (!capsObject) {
-      // create an object with empty counts
-      capsObject = {
-        dailyViews: 0,
-        totalViews: 0,
-        lastShownDate: 0,
-      };
-      this._frequencyCaps[link.url] = capsObject;
-    }
-    // set last updated timestamp
-    capsObject.lastUpdated = Date.now();
-    // check for link configuration
-    if (link.frequency_caps) {
-      capsObject.dailyCap = link.frequency_caps.daily || DEFAULT_DAILY_FREQUENCY_CAP;
-      capsObject.totalCap = link.frequency_caps.total || DEFAULT_TOTAL_FREQUENCY_CAP;
-    }
-    else {
-      // fallback to defaults
-      capsObject.dailyCap = DEFAULT_DAILY_FREQUENCY_CAP;
-      capsObject.totalCap = DEFAULT_TOTAL_FREQUENCY_CAP;
-    }
-  },
-
-  /**
-   * Prunes frequency cap objects for outdated links
-   * @param timeDetla milliseconds
-   *        all cap objects with lastUpdated less than (now() - timeDelta)
-   *        will be removed. This is done to remove frequency cap objects
-   *        for unused tile urls
-   */
-  _pruneFrequencyCapUrls: function RemoteDirectoryLinksProvider_pruneFrequencyCapUrls(timeDelta = DEFAULT_PRUNE_TIME_DELTA) {
-    let timeThreshold = Date.now() - timeDelta;
-    Object.keys(this._frequencyCaps).forEach(url => {
-      if (this._frequencyCaps[url].lastUpdated <= timeThreshold) {
-        delete this._frequencyCaps[url];
-      }
-    });
-  },
-
-  /**
-   * Checks if supplied timestamp happened today
-   * @param timestamp in milliseconds
-   * @return true if the timestamp was made today, false otherwise
-   */
-  _wasToday: function RemoteDirectoryLinksProvider_wasToday(timestamp) {
-    let showOn = new Date(timestamp);
-    let today = new Date();
-    // call timestamps identical if both day and month are same
-    return showOn.getDate() == today.getDate() &&
-           showOn.getMonth() == today.getMonth() &&
-           showOn.getYear() == today.getYear();
-  },
-
-  /**
-   * adds some number of views for a url
-   * @param url String url of the suggested link
-   */
-  _addFrequencyCapView: function RemoteDirectoryLinksProvider_addFrequencyCapView(url) {
-    let capObject = this._frequencyCaps[url];
-    // sanity check
-    if (!capObject) {
-      return;
-    }
-
-    // if the day is new: reset the daily counter and lastShownDate
-    if (!this._wasToday(capObject.lastShownDate)) {
-      capObject.dailyViews = 0;
-      // update lastShownDate
-      capObject.lastShownDate = Date.now();
-    }
-
-    // bump both daily and total counters
-    capObject.totalViews++;
-    capObject.dailyViews++;
-
-    // if any of the caps is reached - update suggested tiles
-    if (capObject.totalViews >= capObject.totalCap ||
-        capObject.dailyViews >= capObject.dailyCap) {
-      this._updateSuggestedTile();
-    }
-  },
-
-  /**
-   * Sets clicked flag for link url
-   * @param url String url of the suggested link
-   */
-  _setFrequencyCapClick(url) {
-    let capObject = this._frequencyCaps[url];
-    // sanity check
-    if (!capObject) {
-      return;
-    }
-    capObject.clicked = true;
-    // and update suggested tiles, since current tile became invalid
-    this._updateSuggestedTile();
-  },
-
-  /**
-   * Tests frequency cap limits for link url
-   * @param url String url of the suggested link
-   * @return true if link is viewable, false otherwise
-   */
-  _testFrequencyCapLimits: function RemoteDirectoryLinksProvider_testFrequencyCapLimits(url) {
-    let capObject = this._frequencyCaps[url];
-    // sanity check: if url is missing - do not show this tile
-    if (!capObject) {
-      return false;
-    }
-
-    // check for clicked set or total views reached
-    if (capObject.clicked || capObject.totalViews >= capObject.totalCap) {
-      return false;
-    }
-
-    // otherwise check if link is over daily views limit
-    if (this._wasToday(capObject.lastShownDate) &&
-        capObject.dailyViews >= capObject.dailyCap) {
-      return false;
-    }
-
-    // we passed all cap tests: return true
-    return true;
-  },
-
-  /**
-   * Removes clicked flag from frequency cap entry for tile landing url
-   * @param url String url of the suggested link
-   * @return promise resolved upon disk write completion
-   */
-  _removeTileClick: function RemoteDirectoryLinksProvider_removeTileClick(url = "") {
-    // remove trailing slash, to accomodate Places sending site urls ending with '/'
-    let noTrailingSlashUrl = url.replace(/\/$/,"");
-    let capObject = this._frequencyCaps[url] || this._frequencyCaps[noTrailingSlashUrl];
-    // return resolved promise if capObject is not found
-    if (!capObject) {
-      return Promise.resolve();
-    }
-    // otherwise remove clicked flag
-    delete capObject.clicked;
-    return this._writeFrequencyCapFile();
-  },
-
-  /**
-   * Removes all clicked flags from frequency cap object
-   * @return promise resolved upon disk write completion
-   */
-  _removeAllTileClicks: function RemoteDirectoryLinksProvider_removeAllTileClicks() {
-    Object.keys(this._frequencyCaps).forEach(url => {
-      delete this._frequencyCaps[url].clicked;
-    });
-    return this._writeFrequencyCapFile();
-  },
-
-  /**
-   * Return the object to its pre-init state
-   */
-  reset: function RemoteDirectoryLinksProvider_reset() {
-    delete this.__linksURL;
-    this._removePrefsObserver();
-    this._removeObservers();
-  },
-
-  addObserver: function RemoteDirectoryLinksProvider_addObserver(aObserver) {
-    this._observers.add(aObserver);
-  },
-
-  removeObserver: function RemoteDirectoryLinksProvider_removeObserver(aObserver) {
-    this._observers.delete(aObserver);
-  },
-
-  _callObservers(methodName, ...args) {
-    for (let obs of this._observers) {
-      if (typeof(obs[methodName]) == "function") {
-        try {
-          obs[methodName](this, ...args);
-        } catch (err) {
-          Cu.reportError(err);
-        }
-      }
-    }
-  },
-
-  _removeObservers: function() {
-    this._observers.clear();
-  }
-};
--- a/browser/components/newtab/moz.build
+++ b/browser/components/newtab/moz.build
@@ -9,12 +9,11 @@ BROWSER_CHROME_MANIFESTS += ['tests/brow
 XPCSHELL_TESTS_MANIFESTS += [
     'tests/xpcshell/xpcshell.ini',
 ]
 
 EXTRA_JS_MODULES += [
     'NewTabURL.jsm',
     'PlacesProvider.jsm',
     'RemoteAboutNewTab.jsm',
-    'RemoteDirectoryLinksProvider.jsm',
     'RemoteNewTabLocation.jsm',
     'RemoteNewTabUtils.jsm',
 ]
deleted file mode 100644
--- a/browser/components/newtab/tests/xpcshell/test_RemoteDirectoryLinksProvider.js
+++ /dev/null
@@ -1,1326 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-"use strict";
-
-/**
- * This file tests the RemoteDirectoryLinksProvider singleton in the RemoteDirectoryLinksProvider.jsm module.
- */
-
-const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu, Constructor: CC } = Components;
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource:///modules/RemoteDirectoryLinksProvider.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-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");
-Cu.import("resource://gre/modules/PlacesUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-  "resource://gre/modules/NetUtil.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RemoteNewTabUtils",
-  "resource:///modules/RemoteNewTabUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
-  "resource://testing-common/PlacesTestUtils.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 kTestURL = 'data:application/json,' + JSON.stringify(kURLData);
-
-// RemoteDirectoryLinksProvider preferences
-const kLocalePref = RemoteDirectoryLinksProvider._observedPrefs.prefSelectedLocale;
-const kSourceUrlPref = RemoteDirectoryLinksProvider._observedPrefs.linksURL;
-const kPingUrlPref = "browser.newtabpage.directory.ping";
-const kNewtabEnhancedPref = "browser.newtabpage.enhanced";
-
-// httpd settings
-var server;
-const kDefaultServerPort = 9000;
-const kBaseUrl = "http://localhost:" + kDefaultServerPort;
-const kExamplePath = "/exampleTest/";
-const kFailPath = "/fail/";
-const kPingPath = "/ping/";
-const kExampleURL = kBaseUrl + kExamplePath;
-const kFailURL = kBaseUrl + kFailPath;
-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"}]};
-
-const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
-                              "nsIBinaryInputStream",
-                              "setInputStream");
-
-let gLastRequestPath;
-
-let suggestedTile1 = {
-  url: "http://turbotax.com",
-  type: "affiliate",
-  lastVisitDate: 4,
-  adgroup_name: "Adgroup1",
-  frecent_sites: [
-    "taxact.com",
-    "hrblock.com",
-    "1040.com",
-    "taxslayer.com"
-  ]
-};
-let suggestedTile2 = {
-  url: "http://irs.gov",
-  type: "affiliate",
-  lastVisitDate: 3,
-  adgroup_name: "Adgroup2",
-  frecent_sites: [
-    "taxact.com",
-    "hrblock.com",
-    "freetaxusa.com",
-    "taxslayer.com"
-  ]
-};
-let suggestedTile3 = {
-  url: "http://hrblock.com",
-  type: "affiliate",
-  lastVisitDate: 2,
-  adgroup_name: "Adgroup3",
-  frecent_sites: [
-    "taxact.com",
-    "freetaxusa.com",
-    "1040.com",
-    "taxslayer.com"
-  ]
-};
-let suggestedTile4 = {
-  url: "http://sponsoredtile.com",
-  type: "sponsored",
-  lastVisitDate: 1,
-  adgroup_name: "Adgroup4",
-  frecent_sites: [
-    "sponsoredtarget.com"
-  ]
-}
-let suggestedTile5 = {
-  url: "http://eviltile.com",
-  type: "affiliate",
-  lastVisitDate: 5,
-  explanation: "This is an evil tile <form><button formaction='javascript:alert(1)''>X</button></form> muhahaha",
-  adgroup_name: "WE ARE EVIL <link rel='import' href='test.svg'/>",
-  frecent_sites: [
-    "eviltarget.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;
-    aResponse.setStatusLine(null, code);
-    aResponse.setHeader("Content-Type", "application/json");
-    aResponse.write(body);
-  };
-}
-
-function isIdentical(actual, expected) {
-  if (expected == null) {
-    do_check_eq(actual, expected);
-  }
-  else if (typeof expected == "object") {
-    // Make sure all the keys match up
-    do_check_eq(Object.keys(actual).sort() + "", Object.keys(expected).sort());
-
-    // Recursively check each value individually
-    Object.keys(expected).forEach(key => {
-      isIdentical(actual[key], expected[key]);
-    });
-  }
-  else {
-    do_check_eq(actual, expected);
-  }
-}
-
-function fetchData() {
-  let deferred = Promise.defer();
-
-  RemoteDirectoryLinksProvider.getLinks(linkData => {
-    deferred.resolve(linkData);
-  });
-  return deferred.promise;
-}
-
-function readJsonFile(jsonFile = DIRECTORY_LINKS_FILE) {
-  let decoder = new TextDecoder();
-  let directoryLinksFilePath = OS.Path.join(OS.Constants.Path.localProfileDir, jsonFile);
-  return OS.File.read(directoryLinksFilePath).then(array => {
-    let json = decoder.decode(array);
-    return JSON.parse(json);
-  }, () => { return "" });
-}
-
-function cleanJsonFile(jsonFile = DIRECTORY_LINKS_FILE) {
-  let directoryLinksFilePath = OS.Path.join(OS.Constants.Path.localProfileDir, jsonFile);
-  return OS.File.remove(directoryLinksFilePath);
-}
-
-function LinksChangeObserver() {
-  this.deferred = Promise.defer();
-  this.onManyLinksChanged = () => this.deferred.resolve();
-  this.onDownloadFail = this.onManyLinksChanged;
-}
-
-function promiseDirectoryDownloadOnPrefChange(pref, newValue) {
-  let oldValue = Services.prefs.getCharPref(pref);
-  if (oldValue != newValue) {
-    // if the preference value is already equal to newValue
-    // the pref service will not call our observer and we
-    // deadlock. Hence only setup observer if values differ
-    let observer = new LinksChangeObserver();
-    RemoteDirectoryLinksProvider.addObserver(observer);
-    Services.prefs.setCharPref(pref, newValue);
-    return observer.deferred.promise.then(() => {
-      RemoteDirectoryLinksProvider.removeObserver(observer);
-    });
-  }
-  return Promise.resolve();
-}
-
-function promiseSetupRemoteDirectoryLinksProvider(options = {}) {
-  return Task.spawn(function() {
-    let linksURL = options.linksURL || kTestURL;
-    yield RemoteDirectoryLinksProvider.init();
-    yield promiseDirectoryDownloadOnPrefChange(kLocalePref, options.locale || "en-US");
-    yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, linksURL);
-    do_check_eq(RemoteDirectoryLinksProvider._linksURL, linksURL);
-    RemoteDirectoryLinksProvider._lastDownloadMS = options.lastDownloadMS || 0;
-  });
-}
-
-function promiseCleanRemoteDirectoryLinksProvider() {
-  return Task.spawn(function() {
-    yield promiseDirectoryDownloadOnPrefChange(kLocalePref, "en-US");
-    yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, kTestURL);
-    yield RemoteDirectoryLinksProvider._clearFrequencyCap();
-    yield RemoteDirectoryLinksProvider._loadInadjacentSites();
-    RemoteDirectoryLinksProvider._lastDownloadMS  = 0;
-    RemoteDirectoryLinksProvider.reset();
-  });
-}
-
-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);
-  RemoteNewTabUtils.init();
-
-  run_next_test();
-
-  // Teardown.
-  do_register_cleanup(function() {
-    server.stop(function() { });
-    RemoteDirectoryLinksProvider.reset();
-    Services.prefs.clearUserPref(kLocalePref);
-    Services.prefs.clearUserPref(kSourceUrlPref);
-    Services.prefs.clearUserPref(kPingUrlPref);
-    Services.prefs.clearUserPref(kNewtabEnhancedPref);
-  });
-}
-
-
-function setTimeout(fun, timeout) {
-  let timer = Components.classes["@mozilla.org/timer;1"]
-                        .createInstance(Components.interfaces.nsITimer);
-  var event = {
-    notify: function (timer) {
-      fun();
-    }
-  };
-  timer.initWithCallback(event, timeout,
-                         Components.interfaces.nsITimer.TYPE_ONE_SHOT);
-  return timer;
-}
-
-add_task(function test_shouldUpdateSuggestedTile() {
-  let suggestedLink = {
-    targetedSite: "somesite.com"
-  };
-
-  // RemoteDirectoryLinksProvider has no suggested tile and no top sites => no need to update
-  do_check_eq(RemoteDirectoryLinksProvider._getCurrentTopSiteCount(), 0);
-  isIdentical(RemoteNewTabUtils.getProviderLinks(), []);
-  do_check_eq(RemoteDirectoryLinksProvider._shouldUpdateSuggestedTile(), false);
-
-  // RemoteDirectoryLinksProvider has a suggested tile and no top sites => need to update
-  let origGetProviderLinks = RemoteNewTabUtils.getProviderLinks;
-  RemoteNewTabUtils.getProviderLinks = (provider) => [suggestedLink];
-
-  do_check_eq(RemoteDirectoryLinksProvider._getCurrentTopSiteCount(), 0);
-  isIdentical(RemoteNewTabUtils.getProviderLinks(), [suggestedLink]);
-  do_check_eq(RemoteDirectoryLinksProvider._shouldUpdateSuggestedTile(), true);
-
-  // RemoteDirectoryLinksProvider has a suggested tile and 8 top sites => no need to update
-  let origCurrentTopSiteCount = RemoteDirectoryLinksProvider._getCurrentTopSiteCount;
-  RemoteDirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
-
-  do_check_eq(RemoteDirectoryLinksProvider._getCurrentTopSiteCount(), 8);
-  isIdentical(RemoteNewTabUtils.getProviderLinks(), [suggestedLink]);
-  do_check_eq(RemoteDirectoryLinksProvider._shouldUpdateSuggestedTile(), false);
-
-  // RemoteDirectoryLinksProvider has no suggested tile and 8 top sites => need to update
-  RemoteNewTabUtils.getProviderLinks = origGetProviderLinks;
-  do_check_eq(RemoteDirectoryLinksProvider._getCurrentTopSiteCount(), 8);
-  isIdentical(RemoteNewTabUtils.getProviderLinks(), []);
-  do_check_eq(RemoteDirectoryLinksProvider._shouldUpdateSuggestedTile(), true);
-
-  // Cleanup
-  RemoteDirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
-});
-
-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();
-  RemoteDirectoryLinksProvider.addObserver(testObserver);
-
-  yield promiseSetupRemoteDirectoryLinksProvider({linksURL: dataURI});
-  let links = yield fetchData();
-
-  let origIsTopPlacesSite = RemoteNewTabUtils.isTopPlacesSite;
-  RemoteNewTabUtils.isTopPlacesSite = function(site) {
-    return topSites.indexOf(site) >= 0;
-  }
-
-  let origGetProviderLinks = RemoteNewTabUtils.getProviderLinks;
-  RemoteNewTabUtils.getProviderLinks = function(provider) {
-    return links;
-  }
-
-  let origCurrentTopSiteCount = RemoteDirectoryLinksProvider._getCurrentTopSiteCount;
-  RemoteDirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
-
-  do_check_eq(RemoteDirectoryLinksProvider._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([...RemoteDirectoryLinksProvider._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([...RemoteDirectoryLinksProvider._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([...RemoteDirectoryLinksProvider._topSitesWithSuggestedLinks], []);
-        resolve();
-      }
-    });
-  }
-
-  // Test first call to '_updateSuggestedTile()', called when fetching directory links.
-  yield testObserver.promise;
-  RemoteDirectoryLinksProvider.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(RemoteNewTabUtils.isTopPlacesSite(removedTopsite));
-  let updateSuggestedTile = RemoteDirectoryLinksProvider._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();
-  RemoteDirectoryLinksProvider.addObserver(testObserver);
-  removedTopsite = topSites.shift();
-  do_check_eq(removedTopsite, "1040.com");
-  do_check_false(RemoteNewTabUtils.isTopPlacesSite(removedTopsite));
-  RemoteDirectoryLinksProvider.onLinkChanged(RemoteDirectoryLinksProvider, {
-    url: "http://" + removedTopsite,
-    type: "history",
-  });
-  yield testObserver.promise;
-  do_check_eq(testObserver.count, 2);
-  RemoteDirectoryLinksProvider.removeObserver(testObserver);
-
-  // Removing all top sites with suggested links should remove
-  // the current suggested link and not replace it.
-  topSites = [];
-  testObserver = new TestRemovingSuggestedTile();
-  RemoteDirectoryLinksProvider.addObserver(testObserver);
-  RemoteDirectoryLinksProvider.onManyLinksChanged();
-  yield testObserver.promise;
-
-  // Cleanup
-  yield promiseCleanRemoteDirectoryLinksProvider();
-  RemoteNewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
-  RemoteNewTabUtils.getProviderLinks = origGetProviderLinks;
-  RemoteDirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
-});
-
-add_task(function test_suggestedLinksMap() {
-  let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3, suggestedTile4], "directory": [someOtherSite]};
-  let dataURI = 'data:application/json,' + JSON.stringify(data);
-
-  yield promiseSetupRemoteDirectoryLinksProvider({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],
-    "sponsoredtarget.com": [suggestedTile4],
-  };
-
-  let suggestedSites = [...RemoteDirectoryLinksProvider._suggestedLinks.keys()];
-  do_check_eq(suggestedSites.indexOf("sponsoredtarget.com"), 5);
-  do_check_eq(suggestedSites.length, Object.keys(expected_data).length);
-
-  RemoteDirectoryLinksProvider._suggestedLinks.forEach((suggestedLinks, site) => {
-    let suggestedLinksItr = suggestedLinks.values();
-    for (let link of expected_data[site]) {
-      let linkCopy = JSON.parse(JSON.stringify(link));
-      linkCopy.targetedName = link.adgroup_name;
-      linkCopy.explanation = "";
-      isIdentical(suggestedLinksItr.next().value, linkCopy);
-    }
-  })
-
-  yield promiseCleanRemoteDirectoryLinksProvider();
-});
-
-add_task(function test_topSitesWithSuggestedLinks() {
-  let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
-  let origIsTopPlacesSite = RemoteNewTabUtils.isTopPlacesSite;
-  RemoteNewTabUtils.isTopPlacesSite = function(site) {
-    return topSites.indexOf(site) >= 0;
-  }
-
-  // Mock out getProviderLinks() so we don't have to populate cache in RemoteNewTabUtils
-  let origGetProviderLinks = RemoteNewTabUtils.getProviderLinks;
-  RemoteNewTabUtils.getProviderLinks = function(provider) {
-    return [];
-  }
-
-  // We start off with no top sites with suggested links.
-  do_check_eq(RemoteDirectoryLinksProvider._topSitesWithSuggestedLinks.size, 0);
-
-  let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3], "directory": [someOtherSite]};
-  let dataURI = 'data:application/json,' + JSON.stringify(data);
-
-  yield promiseSetupRemoteDirectoryLinksProvider({linksURL: dataURI});
-  let links = yield fetchData();
-
-  // Check we've populated suggested links as expected.
-  do_check_eq(RemoteDirectoryLinksProvider._suggestedLinks.size, 5);
-
-  // When many sites change, we update _topSitesWithSuggestedLinks as expected.
-  let expectedTopSitesWithSuggestedLinks = ["hrblock.com", "1040.com", "freetaxusa.com"];
-  RemoteDirectoryLinksProvider._handleManyLinksChanged();
-  isIdentical([...RemoteDirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks);
-
-  // Removing site6.com as a topsite has no impact on _topSitesWithSuggestedLinks.
-  let popped = topSites.pop();
-  RemoteDirectoryLinksProvider._handleLinkChanged({url: "http://" + popped});
-  isIdentical([...RemoteDirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks);
-
-  // Removing freetaxusa.com as a topsite will remove it from _topSitesWithSuggestedLinks.
-  popped = topSites.pop();
-  expectedTopSitesWithSuggestedLinks.pop();
-  RemoteDirectoryLinksProvider._handleLinkChanged({url: "http://" + popped});
-  isIdentical([...RemoteDirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks);
-
-  // Re-adding freetaxusa.com as a topsite will add it to _topSitesWithSuggestedLinks.
-  topSites.push(popped);
-  expectedTopSitesWithSuggestedLinks.push(popped);
-  RemoteDirectoryLinksProvider._handleLinkChanged({url: "http://" + popped});
-  isIdentical([...RemoteDirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks);
-
-  // Cleanup.
-  RemoteNewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
-  RemoteNewTabUtils.getProviderLinks = origGetProviderLinks;
-});
-
-add_task(function test_fetchAndCacheLinks_local() {
-  yield RemoteDirectoryLinksProvider.init();
-  yield cleanJsonFile();
-  // Trigger cache of data or chrome uri files in profD
-  yield RemoteDirectoryLinksProvider._fetchAndCacheLinks(kTestURL);
-  let data = yield readJsonFile();
-  isIdentical(data, kURLData);
-});
-
-add_task(function test_fetchAndCacheLinks_remote() {
-  yield RemoteDirectoryLinksProvider.init();
-  yield cleanJsonFile();
-  // this must trigger directory links json download and save it to cache file
-  yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, kExampleURL + "%LOCALE%");
-  do_check_eq(gLastRequestPath, kExamplePath + "en-US");
-  let data = yield readJsonFile();
-  isIdentical(data, kHttpHandlerData[kExamplePath]);
-});
-
-add_task(function test_fetchAndCacheLinks_malformedURI() {
-  yield RemoteDirectoryLinksProvider.init();
-  yield cleanJsonFile();
-  let someJunk = "some junk";
-  try {
-    yield RemoteDirectoryLinksProvider._fetchAndCacheLinks(someJunk);
-    do_throw("Malformed URIs should fail")
-  } catch (e) {
-    do_check_eq(e, "Error fetching " + someJunk)
-  }
-
-  // File should be empty.
-  let data = yield readJsonFile();
-  isIdentical(data, "");
-});
-
-add_task(function test_fetchAndCacheLinks_unknownHost() {
-  yield RemoteDirectoryLinksProvider.init();
-  yield cleanJsonFile();
-  let nonExistentServer = "http://localhost:56789/";
-  try {
-    yield RemoteDirectoryLinksProvider._fetchAndCacheLinks(nonExistentServer);
-    do_throw("BAD URIs should fail");
-  } catch (e) {
-    do_check_true(e.startsWith("Fetching " + nonExistentServer + " results in error code: "))
-  }
-
-  // File should be empty.
-  let data = yield readJsonFile();
-  isIdentical(data, "");
-});
-
-add_task(function test_fetchAndCacheLinks_non200Status() {
-  yield RemoteDirectoryLinksProvider.init();
-  yield cleanJsonFile();
-  yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, kFailURL);
-  do_check_eq(gLastRequestPath, kFailPath);
-  let data = yield readJsonFile();
-  isIdentical(data, {});
-});
-
-// To test onManyLinksChanged observer, trigger a fetch
-add_task(function test_RemoteDirectoryLinksProvider__linkObservers() {
-  yield RemoteDirectoryLinksProvider.init();
-
-  let testObserver = new LinksChangeObserver();
-  RemoteDirectoryLinksProvider.addObserver(testObserver);
-  do_check_eq(RemoteDirectoryLinksProvider._observers.size, 1);
-  RemoteDirectoryLinksProvider._fetchAndCacheLinksIfNecessary(true);
-
-  yield testObserver.deferred.promise;
-  RemoteDirectoryLinksProvider._removeObservers();
-  do_check_eq(RemoteDirectoryLinksProvider._observers.size, 0);
-
-  yield promiseCleanRemoteDirectoryLinksProvider();
-});
-
-add_task(function test_RemoteDirectoryLinksProvider__prefObserver_url() {
-  yield promiseSetupRemoteDirectoryLinksProvider({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);
-
-  // tests these 2 things:
-  // 1. _linksURL is properly set after the pref change
-  // 2. invalid source url is correctly handled
-  let exampleUrl = 'http://localhost:56789/bad';
-  yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, exampleUrl);
-  do_check_eq(RemoteDirectoryLinksProvider._linksURL, exampleUrl);
-
-  // since the download fail, the directory file must remain the same
-  let newLinks = yield fetchData();
-  isIdentical(newLinks, expectedData);
-
-  // now remove the file, and re-download
-  yield cleanJsonFile();
-  yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, exampleUrl + " ");
-  // we now should see empty links
-  newLinks = yield fetchData();
-  isIdentical(newLinks, []);
-
-  yield promiseCleanRemoteDirectoryLinksProvider();
-});
-
-add_task(function test_RemoteDirectoryLinksProvider_getLinks_noDirectoryData() {
-  let data = {
-    "directory": [],
-  };
-  let dataURI = 'data:application/json,' + JSON.stringify(data);
-  yield promiseSetupRemoteDirectoryLinksProvider({linksURL: dataURI});
-
-  let links = yield fetchData();
-  do_check_eq(links.length, 0);
-  yield promiseCleanRemoteDirectoryLinksProvider();
-});
-
-add_task(function test_RemoteDirectoryLinksProvider_getLinks_badData() {
-  let data = {
-    "en-US": {
-      "en-US": [{url: "http://example.com", title: "US"}],
-    },
-  };
-  let dataURI = 'data:application/json,' + JSON.stringify(data);
-  yield promiseSetupRemoteDirectoryLinksProvider({linksURL: dataURI});
-
-  // Make sure we get nothing for incorrectly formatted data
-  let links = yield fetchData();
-  do_check_eq(links.length, 0);
-  yield promiseCleanRemoteDirectoryLinksProvider();
-});
-
-add_task(function test_RemoteDirectoryLinksProvider_needsDownload() {
-  // test timestamping
-  RemoteDirectoryLinksProvider._lastDownloadMS = 0;
-  do_check_true(RemoteDirectoryLinksProvider._needsDownload);
-  RemoteDirectoryLinksProvider._lastDownloadMS = Date.now();
-  do_check_false(RemoteDirectoryLinksProvider._needsDownload);
-  RemoteDirectoryLinksProvider._lastDownloadMS = Date.now() - (60*60*24 + 1)*1000;
-  do_check_true(RemoteDirectoryLinksProvider._needsDownload);
-  RemoteDirectoryLinksProvider._lastDownloadMS = 0;
-});
-
-add_task(function test_RemoteDirectoryLinksProvider_fetchAndCacheLinksIfNecessary() {
-  yield RemoteDirectoryLinksProvider.init();
-  yield cleanJsonFile();
-  // explicitly change source url to cause the download during setup
-  yield promiseSetupRemoteDirectoryLinksProvider({linksURL: kTestURL+" "});
-  yield RemoteDirectoryLinksProvider._fetchAndCacheLinksIfNecessary();
-
-  // inspect lastDownloadMS timestamp which should be 5 seconds less then now()
-  let lastDownloadMS = RemoteDirectoryLinksProvider._lastDownloadMS;
-  do_check_true((Date.now() - lastDownloadMS) < 5000);
-
-  // we should have fetched a new file during setup
-  let data = yield readJsonFile();
-  isIdentical(data, kURLData);
-
-  // attempt to download again - the timestamp should not change
-  yield RemoteDirectoryLinksProvider._fetchAndCacheLinksIfNecessary();
-  do_check_eq(RemoteDirectoryLinksProvider._lastDownloadMS, lastDownloadMS);
-
-  // clean the file and force the download
-  yield cleanJsonFile();
-  yield RemoteDirectoryLinksProvider._fetchAndCacheLinksIfNecessary(true);
-  data = yield readJsonFile();
-  isIdentical(data, kURLData);
-
-  // make sure that failed download does not corrupt the file, nor changes lastDownloadMS
-  lastDownloadMS = RemoteDirectoryLinksProvider._lastDownloadMS;
-  yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, "http://");
-  yield RemoteDirectoryLinksProvider._fetchAndCacheLinksIfNecessary(true);
-  data = yield readJsonFile();
-  isIdentical(data, kURLData);
-  do_check_eq(RemoteDirectoryLinksProvider._lastDownloadMS, lastDownloadMS);
-
-  // _fetchAndCacheLinksIfNecessary must return same promise if download is in progress
-  let downloadPromise = RemoteDirectoryLinksProvider._fetchAndCacheLinksIfNecessary(true);
-  let anotherPromise = RemoteDirectoryLinksProvider._fetchAndCacheLinksIfNecessary(true);
-  do_check_true(downloadPromise === anotherPromise);
-  yield downloadPromise;
-
-  yield promiseCleanRemoteDirectoryLinksProvider();
-});
-
-add_task(function test_RemoteDirectoryLinksProvider_fetchDirectoryOnPrefChange() {
-  yield RemoteDirectoryLinksProvider.init();
-
-  let testObserver = new LinksChangeObserver();
-  RemoteDirectoryLinksProvider.addObserver(testObserver);
-
-  yield cleanJsonFile();
-  // ensure that provider does not think it needs to download
-  do_check_false(RemoteDirectoryLinksProvider._needsDownload);
-
-  // change the source URL, which should force directory download
-  yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, kExampleURL);
-  // then wait for testObserver to fire and test that json is downloaded
-  yield testObserver.deferred.promise;
-  do_check_eq(gLastRequestPath, kExamplePath);
-  let data = yield readJsonFile();
-  isIdentical(data, kHttpHandlerData[kExamplePath]);
-
-  yield promiseCleanRemoteDirectoryLinksProvider();
-});
-
-add_task(function test_RemoteDirectoryLinksProvider_fetchDirectoryOnInit() {
-  // ensure preferences are set to defaults
-  yield promiseSetupRemoteDirectoryLinksProvider();
-  // now clean to provider, so we can init it again
-  yield promiseCleanRemoteDirectoryLinksProvider();
-
-  yield cleanJsonFile();
-  yield RemoteDirectoryLinksProvider.init();
-  let data = yield readJsonFile();
-  isIdentical(data, kURLData);
-
-  yield promiseCleanRemoteDirectoryLinksProvider();
-});
-
-add_task(function test_RemoteDirectoryLinksProvider_getLinksFromCorruptedFile() {
-  yield promiseSetupRemoteDirectoryLinksProvider();
-
-  // write bogus json to a file and attempt to fetch from it
-  let directoryLinksFilePath = OS.Path.join(OS.Constants.Path.profileDir, DIRECTORY_LINKS_FILE);
-  yield OS.File.writeAtomic(directoryLinksFilePath, '{"en-US":');
-  let data = yield fetchData();
-  isIdentical(data, []);
-
-  yield promiseCleanRemoteDirectoryLinksProvider();
-});
-
-add_task(function test_RemoteDirectoryLinksProvider_getAllowedLinks() {
-  let data = {"directory": [
-    {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 promiseSetupRemoteDirectoryLinksProvider({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);
-});
-
-add_task(function test_RemoteDirectoryLinksProvider_getAllowedImages() {
-  let data = {"directory": [
-    {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 promiseSetupRemoteDirectoryLinksProvider({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);
-});
-
-add_task(function test_RemoteDirectoryLinksProvider_getAllowedImages_base() {
-  let data = {"directory": [
-    {url: "http://example1.com", imageURI: "https://example.com"},
-    {url: "http://example2.com", imageURI: "https://tiles.cdn.mozilla.net"},
-    {url: "http://example3.com", imageURI: "https://tiles2.cdn.mozilla.net"},
-    {url: "http://example4.com", enhancedImageURI: "https://mozilla.net"},
-    {url: "http://example5.com", imageURI: "data:text/plain,hi"},
-  ]};
-  let dataURI = 'data:application/json,' + JSON.stringify(data);
-  yield promiseSetupRemoteDirectoryLinksProvider({linksURL: dataURI});
-
-  // Pretend we're using the default pref to trigger base matching
-  RemoteDirectoryLinksProvider.__linksURLModified = false;
-
-  let links = yield fetchData();
-  do_check_eq(links.length, 4);
-
-  // The only remaining images should be https with mozilla.net or data URI
-  do_check_eq(links[0].url, data["directory"][1].url);
-  do_check_eq(links[1].url, data["directory"][2].url);
-  do_check_eq(links[2].url, data["directory"][3].url);
-  do_check_eq(links[3].url, data["directory"][4].url);
-});
-
-add_task(function test_RemoteDirectoryLinksProvider_getAllowedEnhancedImages() {
-  let data = {"directory": [
-    {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 promiseSetupRemoteDirectoryLinksProvider({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);
-});
-
-add_task(function test_RemoteDirectoryLinksProvider_getEnhancedLink() {
-  let data = {"enhanced": [
-    {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 promiseSetupRemoteDirectoryLinksProvider({linksURL: dataURI});
-
-  let links = yield fetchData();
-  do_check_eq(links.length, 0); // There are no directory links.
-
-  function checkEnhanced(url, image) {
-    let enhanced = RemoteDirectoryLinksProvider.getEnhancedLink({url: url});
-    do_check_eq(enhanced && enhanced.enhancedImageURI, image);
-  }
-
-  // Get the expected image for the same site
-  checkEnhanced("http://example.net/", "data:,net1");
-  checkEnhanced("http://example.net/path", "data:,net1");
-  checkEnhanced("https://www.example.net/", "data:,net1");
-  checkEnhanced("https://www3.example.net/", "data:,net1");
-
-  // Get the image of the last entry
-  checkEnhanced("http://example.com", "data:,com2");
-
-  // Get the inline enhanced image
-  let inline = RemoteDirectoryLinksProvider.getEnhancedLink({
-    url: "http://example.com/echo",
-    enhancedImageURI: "data:,echo",
-  });
-  do_check_eq(inline.enhancedImageURI, "data:,echo");
-  do_check_eq(inline.url, "http://example.com/echo");
-
-  // 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": [
-    {url: "http://example.com", enhancedImageURI: "data:,fresh"},
-  ]};
-  dataURI = 'data:application/json,' + JSON.stringify(data);
-  yield promiseSetupRemoteDirectoryLinksProvider({linksURL: dataURI});
-  links = yield fetchData();
-  do_check_eq(links.length, 0); // There are no directory links.
-  checkEnhanced("http://example.net", undefined);
-  checkEnhanced("http://example.com", "data:,fresh");
-});
-
-add_task(function test_RemoteDirectoryLinksProvider_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");
-  checkDefault(true);
-
-  // Turn on DNT - no track
-  Services.prefs.setBoolPref("privacy.donottrackheader.enabled", true);
-  checkDefault(false);
-
-  // Turn off DNT header
-  Services.prefs.clearUserPref("privacy.donottrackheader.enabled");
-  checkDefault(true);
-
-  // Clean up
-  Services.prefs.clearUserPref("privacy.donottrackheader.value");
-});
-
-add_task(function test_timeSensetiveSuggestedTiles() {
-  // make tile json with start and end dates
-  let testStartTime = Date.now();
-  // start date is now + 1 seconds
-  let startDate = new Date(testStartTime + 1000);
-  // end date is now + 3 seconds
-  let endDate = new Date(testStartTime + 3000);
-  let suggestedTile = Object.assign({
-    time_limits: {
-      start: startDate.toISOString(),
-      end: endDate.toISOString(),
-    }
-  }, suggestedTile1);
-
-  // Initial setup
-  let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
-  let data = {"suggested": [suggestedTile], "directory": [someOtherSite]};
-  let dataURI = 'data:application/json,' + JSON.stringify(data);
-
-  let testObserver = new TestTimingRun();
-  RemoteDirectoryLinksProvider.addObserver(testObserver);
-
-  yield promiseSetupRemoteDirectoryLinksProvider({linksURL: dataURI});
-  let links = yield fetchData();
-
-  let origIsTopPlacesSite = RemoteNewTabUtils.isTopPlacesSite;
-  RemoteNewTabUtils.isTopPlacesSite = function(site) {
-    return topSites.indexOf(site) >= 0;
-  }
-
-  let origGetProviderLinks = RemoteNewTabUtils.getProviderLinks;
-  RemoteNewTabUtils.getProviderLinks = function(provider) {
-    return links;
-  }
-
-  let origCurrentTopSiteCount = RemoteDirectoryLinksProvider._getCurrentTopSiteCount;
-  RemoteDirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
-
-  do_check_eq(RemoteDirectoryLinksProvider._updateSuggestedTile(), undefined);
-
-  // this tester will fire twice: when start limit is reached and when tile link
-  // is removed upon end of the campaign, in which case deleteFlag will be set
-  function TestTimingRun() {
-    this.promise = new Promise(resolve => {
-      this.onLinkChanged = (directoryLinksProvider, link, ignoreFlag, deleteFlag) => {
-        // if we are not deleting, add link to links, so we can catch it's removal
-        if (!deleteFlag) {
-          links.unshift(link);
-        }
-
-        isIdentical([...RemoteDirectoryLinksProvider._topSitesWithSuggestedLinks], ["hrblock.com", "1040.com"]);
-        do_check_eq(link.frecency, SUGGESTED_FRECENCY);
-        do_check_eq(link.type, "affiliate");
-        do_check_eq(link.url, suggestedTile.url);
-        let timeDelta = Date.now() - testStartTime;
-        if (!deleteFlag) {
-          // this is start timeout corresponding to campaign start
-          // a seconds must pass and targetedSite must be set
-          do_print("TESTING START timeDelta: " + timeDelta);
-          do_check_true(timeDelta >= 1000 / 2); // check for at least half time
-          do_check_eq(link.targetedSite, "hrblock.com");
-          do_check_true(RemoteDirectoryLinksProvider._campaignTimeoutID);
-        }
-        else {
-          // this is the campaign end timeout, so 3 seconds must pass
-          // and timeout should be cleared
-          do_print("TESTING END timeDelta: " + timeDelta);
-          do_check_true(timeDelta >= 3000 / 2); // check for at least half time
-          do_check_false(link.targetedSite);
-          do_check_false(RemoteDirectoryLinksProvider._campaignTimeoutID);
-          resolve();
-        }
-      };
-    });
-  }
-
-  // _updateSuggestedTile() is called when fetching directory links.
-  yield testObserver.promise;
-  RemoteDirectoryLinksProvider.removeObserver(testObserver);
-
-  // shoudl suggest nothing
-  do_check_eq(RemoteDirectoryLinksProvider._updateSuggestedTile(), undefined);
-
-  // set links back to contain directory tile only
-  links.shift();
-
-  // drop the end time - we should pick up the tile
-  suggestedTile.time_limits.end = null;
-  data = {"suggested": [suggestedTile], "directory": [someOtherSite]};
-  dataURI = 'data:application/json,' + JSON.stringify(data);
-
-  // redownload json and getLinks to force time recomputation
-  yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, dataURI);
-
-  // ensure that there's a link returned by _updateSuggestedTile and no timeout
-  let deferred = Promise.defer();
-  RemoteDirectoryLinksProvider.getLinks(() => {
-    let link = RemoteDirectoryLinksProvider._updateSuggestedTile();
-    // we should have a suggested tile and no timeout
-    do_check_eq(link.type, "affiliate");
-    do_check_eq(link.url, suggestedTile.url);
-    do_check_false(RemoteDirectoryLinksProvider._campaignTimeoutID);
-    deferred.resolve();
-  });
-  yield deferred.promise;
-
-  // repeat the test for end time only
-  suggestedTile.time_limits.start = null;
-  suggestedTile.time_limits.end = (new Date(Date.now() + 3000)).toISOString();
-
-  data = {"suggested": [suggestedTile], "directory": [someOtherSite]};
-  dataURI = 'data:application/json,' + JSON.stringify(data);
-
-  // redownload json and call getLinks() to force time recomputation
-  yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, dataURI);
-
-  // ensure that there's a link returned by _updateSuggestedTile and timeout set
-  deferred = Promise.defer();
-  RemoteDirectoryLinksProvider.getLinks(() => {
-    let link = RemoteDirectoryLinksProvider._updateSuggestedTile();
-    // we should have a suggested tile and timeout set
-    do_check_eq(link.type, "affiliate");
-    do_check_eq(link.url, suggestedTile.url);
-    do_check_true(RemoteDirectoryLinksProvider._campaignTimeoutID);
-    RemoteDirectoryLinksProvider._clearCampaignTimeout();
-    deferred.resolve();
-  });
-  yield deferred.promise;
-
-  // Cleanup
-  yield promiseCleanRemoteDirectoryLinksProvider();
-  RemoteNewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
-  RemoteNewTabUtils.getProviderLinks = origGetProviderLinks;
-  RemoteDirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
-});
-
-add_task(function test_setupStartEndTime() {
-  let currentTime = Date.now();
-  let dt = new Date(currentTime);
-  let link = {
-    time_limits: {
-      start: dt.toISOString()
-    }
-  };
-
-  // test ISO translation
-  RemoteDirectoryLinksProvider._setupStartEndTime(link);
-  do_check_eq(link.startTime, currentTime);
-
-  // test localtime translation
-  let shiftedDate = new Date(currentTime - dt.getTimezoneOffset()*60*1000);
-  link.time_limits.start = shiftedDate.toISOString().replace(/Z$/, "");
-
-  RemoteDirectoryLinksProvider._setupStartEndTime(link);
-  do_check_eq(link.startTime, currentTime);
-
-  // throw some garbage into date string
-  delete link.startTime;
-  link.time_limits.start = "no date"
-  RemoteDirectoryLinksProvider._setupStartEndTime(link);
-  do_check_false(link.startTime);
-
-  link.time_limits.start = "2015-99999-01T00:00:00"
-  RemoteDirectoryLinksProvider._setupStartEndTime(link);
-  do_check_false(link.startTime);
-
-  link.time_limits.start = "20150501T00:00:00"
-  RemoteDirectoryLinksProvider._setupStartEndTime(link);
-  do_check_false(link.startTime);
-});
-
-add_task(function test_RemoteDirectoryLinksProvider_frequencyCapSetup() {
-  yield promiseSetupRemoteDirectoryLinksProvider();
-  yield RemoteDirectoryLinksProvider.init();
-
-  yield promiseCleanRemoteDirectoryLinksProvider();
-  yield RemoteDirectoryLinksProvider._readFrequencyCapFile();
-  isIdentical(RemoteDirectoryLinksProvider._frequencyCaps, {});
-
-  // setup few links
-  RemoteDirectoryLinksProvider._updateFrequencyCapSettings({
-      url: "1",
-  });
-  RemoteDirectoryLinksProvider._updateFrequencyCapSettings({
-      url: "2",
-      frequency_caps: {daily: 1, total: 2}
-  });
-  RemoteDirectoryLinksProvider._updateFrequencyCapSettings({
-      url: "3",
-      frequency_caps: {total: 2}
-  });
-  RemoteDirectoryLinksProvider._updateFrequencyCapSettings({
-      url: "4",
-      frequency_caps: {daily: 1}
-  });
-  let freqCapsObject = RemoteDirectoryLinksProvider._frequencyCaps;
-  let capObject = freqCapsObject["1"];
-  let defaultDaily = capObject.dailyCap;
-  let defaultTotal = capObject.totalCap;
-  // check if we have defaults set
-  do_check_true(capObject.dailyCap > 0);
-  do_check_true(capObject.totalCap > 0);
-  // check if defaults are properly handled
-  do_check_eq(freqCapsObject["2"].dailyCap, 1);
-  do_check_eq(freqCapsObject["2"].totalCap, 2);
-  do_check_eq(freqCapsObject["3"].dailyCap, defaultDaily);
-  do_check_eq(freqCapsObject["3"].totalCap, 2);
-  do_check_eq(freqCapsObject["4"].dailyCap, 1);
-  do_check_eq(freqCapsObject["4"].totalCap, defaultTotal);
-
-  // write object to file
-  yield RemoteDirectoryLinksProvider._writeFrequencyCapFile();
-  // empty out freqCapsObject and read file back
-  RemoteDirectoryLinksProvider._frequencyCaps = {};
-  yield RemoteDirectoryLinksProvider._readFrequencyCapFile();
-  // re-ran tests - they should all pass
-  do_check_eq(freqCapsObject["2"].dailyCap, 1);
-  do_check_eq(freqCapsObject["2"].totalCap, 2);
-  do_check_eq(freqCapsObject["3"].dailyCap, defaultDaily);
-  do_check_eq(freqCapsObject["3"].totalCap, 2);
-  do_check_eq(freqCapsObject["4"].dailyCap, 1);
-  do_check_eq(freqCapsObject["4"].totalCap, defaultTotal);
-
-  // wait a second and prune frequency caps
-  yield new Promise(resolve => {
-    setTimeout(resolve, 1100);
-  });
-
-  // update one link and create another
-  RemoteDirectoryLinksProvider._updateFrequencyCapSettings({
-      url: "3",
-      frequency_caps: {daily: 1, total: 2}
-  });
-  RemoteDirectoryLinksProvider._updateFrequencyCapSettings({
-      url: "7",
-      frequency_caps: {daily: 1, total: 2}
-  });
-  // now prune the ones that have been in the object longer than 1 second
-  RemoteDirectoryLinksProvider._pruneFrequencyCapUrls(1000);
-  // make sure all keys but "3" and "7" are deleted
-  Object.keys(RemoteDirectoryLinksProvider._frequencyCaps).forEach(key => {
-    do_check_true(key == "3" || key == "7");
-  });
-
-  yield promiseCleanRemoteDirectoryLinksProvider();
-});
-
-add_task(function test_RemoteDirectoryLinksProvider_getFrequencyCapLogic() {
-  yield promiseSetupRemoteDirectoryLinksProvider();
-  yield RemoteDirectoryLinksProvider.init();
-
-  // setup suggested links
-  RemoteDirectoryLinksProvider._updateFrequencyCapSettings({
-    url: "1",
-    frequency_caps: {daily: 2, total: 4}
-  });
-
-  do_check_true(RemoteDirectoryLinksProvider._testFrequencyCapLimits("1"));
-  // exhaust daily views
-  RemoteDirectoryLinksProvider._addFrequencyCapView("1")
-  do_check_true(RemoteDirectoryLinksProvider._testFrequencyCapLimits("1"));
-  RemoteDirectoryLinksProvider._addFrequencyCapView("1")
-  do_check_false(RemoteDirectoryLinksProvider._testFrequencyCapLimits("1"));
-
-  // now step into the furture
-  let _wasTodayOrig = RemoteDirectoryLinksProvider._wasToday;
-  RemoteDirectoryLinksProvider._wasToday = function () {return false;}
-  // exhaust total views
-  RemoteDirectoryLinksProvider._addFrequencyCapView("1")
-  do_check_true(RemoteDirectoryLinksProvider._testFrequencyCapLimits("1"));
-  RemoteDirectoryLinksProvider._addFrequencyCapView("1")
-  // reached totalViews 4, should return false
-  do_check_false(RemoteDirectoryLinksProvider._testFrequencyCapLimits("1"));
-
-  // add more views by updating configuration
-  RemoteDirectoryLinksProvider._updateFrequencyCapSettings({
-    url: "1",
-    frequency_caps: {daily: 5, total: 10}
-  });
-  // should be true, since we have more total views
-  do_check_true(RemoteDirectoryLinksProvider._testFrequencyCapLimits("1"));
-
-  // set click flag
-  RemoteDirectoryLinksProvider._setFrequencyCapClick("1");
-  // always false after click
-  do_check_false(RemoteDirectoryLinksProvider._testFrequencyCapLimits("1"));
-
-  // use unknown urls and ensure nothing breaks
-  RemoteDirectoryLinksProvider._addFrequencyCapView("nosuch.url");
-  RemoteDirectoryLinksProvider._setFrequencyCapClick("nosuch.url");
-  // testing unknown url should always return false
-  do_check_false(RemoteDirectoryLinksProvider._testFrequencyCapLimits("nosuch.url"));
-
-  // reset _wasToday back to original function
-  RemoteDirectoryLinksProvider._wasToday = _wasTodayOrig;
-  yield promiseCleanRemoteDirectoryLinksProvider();
-});
-
-add_task(function test_RemoteDirectoryLinksProvider_ClickRemoval() {
-  yield promiseSetupRemoteDirectoryLinksProvider();
-  yield RemoteDirectoryLinksProvider.init();
-  let landingUrl = "http://foo.com";
-
-  // setup suggested links
-  RemoteDirectoryLinksProvider._updateFrequencyCapSettings({
-    url: landingUrl,
-    frequency_caps: {daily: 2, total: 4}
-  });
-
-  // add views
-  RemoteDirectoryLinksProvider._addFrequencyCapView(landingUrl)
-  RemoteDirectoryLinksProvider._addFrequencyCapView(landingUrl)
-  // make a click
-  RemoteDirectoryLinksProvider._setFrequencyCapClick(landingUrl);
-
-  // views must be 2 and click must be set
-  do_check_eq(RemoteDirectoryLinksProvider._frequencyCaps[landingUrl].totalViews, 2);
-  do_check_true(RemoteDirectoryLinksProvider._frequencyCaps[landingUrl].clicked);
-
-  // now insert a visit into places
-  yield new Promise(resolve => {
-    PlacesUtils.asyncHistory.updatePlaces(
-      {
-        uri: NetUtil.newURI(landingUrl),
-        title: "HELLO",
-        visits: [{
-          visitDate: Date.now()*1000,
-          transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
-        }]
-      },
-      {
-        handleError: function () {do_check_true(false);},
-        handleResult: function () {},
-        handleCompletion: function () {resolve();}
-      }
-    );
-  });
-
-  function UrlDeletionTester() {
-    this.promise = new Promise(resolve => {
-      this.onDeleteURI = (directoryLinksProvider, link) => {
-        resolve();
-      };
-      this.onClearHistory = (directoryLinksProvider) => {
-        resolve();
-      };
-    });
-  };
-
-  let testObserver = new UrlDeletionTester();
-  RemoteDirectoryLinksProvider.addObserver(testObserver);
-
-  PlacesUtils.bhistory.removePage(NetUtil.newURI(landingUrl));
-  yield testObserver.promise;
-  RemoteDirectoryLinksProvider.removeObserver(testObserver);
-  // views must be 2 and click should not exist
-  do_check_eq(RemoteDirectoryLinksProvider._frequencyCaps[landingUrl].totalViews, 2);
-  do_check_false(RemoteDirectoryLinksProvider._frequencyCaps[landingUrl].hasOwnProperty("clicked"));
-
-  // verify that disk written data is kosher
-  let data = yield readJsonFile(RemoteDirectoryLinksProvider._frequencyCapFilePath);
-  do_check_eq(data[landingUrl].totalViews, 2);
-  do_check_false(data[landingUrl].hasOwnProperty("clicked"));
-
-  // now test clear history
-  RemoteDirectoryLinksProvider._updateFrequencyCapSettings({
-    url: landingUrl,
-    frequency_caps: {daily: 2, total: 4}
-  });
-  RemoteDirectoryLinksProvider._updateFrequencyCapSettings({
-    url: "http://bar.com",
-    frequency_caps: {daily: 2, total: 4}
-  });
-
-  RemoteDirectoryLinksProvider._setFrequencyCapClick(landingUrl);
-  RemoteDirectoryLinksProvider._setFrequencyCapClick("http://bar.com");
-  // both tiles must have clicked
-  do_check_true(RemoteDirectoryLinksProvider._frequencyCaps[landingUrl].clicked);
-  do_check_true(RemoteDirectoryLinksProvider._frequencyCaps["http://bar.com"].clicked);
-
-  testObserver = new UrlDeletionTester();
-  RemoteDirectoryLinksProvider.addObserver(testObserver);
-  yield PlacesTestUtils.clearHistory();
-
-  yield testObserver.promise;
-  RemoteDirectoryLinksProvider.removeObserver(testObserver);
-  // no clicks should remain in the cap object
-  do_check_false(RemoteDirectoryLinksProvider._frequencyCaps[landingUrl].hasOwnProperty("clicked"));
-  do_check_false(RemoteDirectoryLinksProvider._frequencyCaps["http://bar.com"].hasOwnProperty("clicked"));
-
-  // verify that disk written data is kosher
-  data = yield readJsonFile(RemoteDirectoryLinksProvider._frequencyCapFilePath);
-  do_check_false(data[landingUrl].hasOwnProperty("clicked"));
-  do_check_false(data["http://bar.com"].hasOwnProperty("clicked"));
-
-  yield promiseCleanRemoteDirectoryLinksProvider();
-});
-
-add_task(function test_RemoteDirectoryLinksProvider_anonymous() {
-  do_check_true(RemoteDirectoryLinksProvider._newXHR().mozAnon);
-});
-
-add_task(function test_sanitizeExplanation() {
-  // Note: this is a basic test to ensure we applied sanitization to the link explanation.
-  // Full testing for appropriate sanitization is done in parser/xml/test/unit/test_sanitizer.js.
-  let data = {"suggested": [suggestedTile5]};
-  let dataURI = 'data:application/json,' + encodeURIComponent(JSON.stringify(data));
-
-  yield promiseSetupRemoteDirectoryLinksProvider({linksURL: dataURI});
-  let links = yield fetchData();
-
-  let suggestedSites = [...RemoteDirectoryLinksProvider._suggestedLinks.keys()];
-  do_check_eq(suggestedSites.indexOf("eviltarget.com"), 0);
-  do_check_eq(suggestedSites.length, 1);
-
-  let suggestedLink = [...RemoteDirectoryLinksProvider._suggestedLinks.get(suggestedSites[0]).values()][0];
-  do_check_eq(suggestedLink.explanation, "This is an evil tile X muhahaha");
-  do_check_eq(suggestedLink.targetedName, "WE ARE EVIL ");
-});
--- a/browser/components/newtab/tests/xpcshell/xpcshell.ini
+++ b/browser/components/newtab/tests/xpcshell/xpcshell.ini
@@ -2,11 +2,10 @@
 head =
 tail =
 firefox-appdir = browser
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 
 [test_AboutNewTabService.js]
 [test_NewTabURL.js]
 [test_PlacesProvider.js]
-[test_RemoteDirectoryLinksProvider.js]
 [test_RemoteNewTabLocation.js]
 [test_RemoteNewTabUtils.js]
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -24,19 +24,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource:///modules/DirectoryLinksProvider.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
                                   "resource://gre/modules/NewTabUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "RemoteAboutNewTab",
                                   "resource:///modules/RemoteAboutNewTab.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "RemoteDirectoryLinksProvider",
-                                  "resource:///modules/RemoteDirectoryLinksProvider.jsm");
-
 XPCOMUtils.defineLazyModuleGetter(this, "RemoteNewTabUtils",
                                   "resource:///modules/RemoteNewTabUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "UITour",
                                   "resource:///modules/UITour.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
@@ -837,26 +834,25 @@ BrowserGlue.prototype = {
 #ifdef NIGHTLY_BUILD
     if (Services.prefs.getBoolPref("dom.identity.enabled")) {
       SignInToWebsiteUX.init();
     }
 #endif
     webrtcUI.init();
     AboutHome.init();
 
-    RemoteDirectoryLinksProvider.init();
-    RemoteNewTabUtils.init();
-    RemoteNewTabUtils.links.addProvider(RemoteDirectoryLinksProvider);
-    RemoteAboutNewTab.init();
-
     DirectoryLinksProvider.init();
     NewTabUtils.init();
     NewTabUtils.links.addProvider(DirectoryLinksProvider);
     AboutNewTab.init();
 
+    RemoteNewTabUtils.init();
+    RemoteNewTabUtils.links.addProvider(DirectoryLinksProvider);
+    RemoteAboutNewTab.init();
+
     SessionStore.init();
     BrowserUITelemetry.init();
     ContentSearch.init();
     FormValidationHandler.init();
 
     ContentClick.init();
     RemotePrompt.init();
     Feeds.init();
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -26,16 +26,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/osfile.jsm")
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
   "resource://gre/modules/UpdateUtils.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "eTLD",
   "@mozilla.org/network/effective-tld-service;1",
   "nsIEffectiveTLDService");
+XPCOMUtils.defineLazyModuleGetter(this, "RemoteNewTabUtils",
+  "resource:///modules/RemoteNewTabUtils.jsm");
 XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => {
   return new TextDecoder();
 });
 XPCOMUtils.defineLazyGetter(this, "gCryptoHash", function () {
   return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
 });
 XPCOMUtils.defineLazyGetter(this, "gUnicodeConverter", function () {
   let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
@@ -752,16 +754,18 @@ var DirectoryLinksProvider = {
 
     // setup frequency cap file path
     this._frequencyCapFilePath = OS.Path.join(OS.Constants.Path.localProfileDir, FREQUENCY_CAP_FILE);
     // setup inadjacent sites URL
     this._inadjacentSitesUrl = INADJACENCY_SOURCE;
 
     NewTabUtils.placesProvider.addObserver(this);
     NewTabUtils.links.addObserver(this);
+    RemoteNewTabUtils.placesProvider.addObserver(this);
+    RemoteNewTabUtils.links.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);
       }