diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 39bb89bfa43..a02a258704f 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1464,7 +1464,7 @@ pref("browser.newtabpage.rows", 3); // number of columns of newtab grid pref("browser.newtabpage.columns", 3); -pref("browser.newtabpage.directorySource", "chrome://global/content/directoryLinks.json"); +pref("browser.newtabpage.directory.source", "chrome://global/content/directoryLinks.json"); // Enable the DOM fullscreen API. pref("full-screen-api.enabled", true); diff --git a/browser/base/content/newtab/page.js b/browser/base/content/newtab/page.js index 758eb0fa94b..8a81780fd02 100644 --- a/browser/base/content/newtab/page.js +++ b/browser/base/content/newtab/page.js @@ -217,6 +217,7 @@ let gPage = { } } + DirectoryLinksProvider.reportShownCount(directoryCount); // Record how many directory sites were shown, but place counts over the // default 9 in the same bucket for (let type of Object.keys(directoryCount)) { diff --git a/browser/base/content/test/general/browser_tabopen_reflows.js b/browser/base/content/test/general/browser_tabopen_reflows.js index aeaf168019e..945670052b8 100644 --- a/browser/base/content/test/general/browser_tabopen_reflows.js +++ b/browser/base/content/test/general/browser_tabopen_reflows.js @@ -56,7 +56,7 @@ const EXPECTED_REFLOWS = [ ]; const PREF_PRELOAD = "browser.newtab.preload"; -const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directorySource"; +const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source"; /* * This test ensures that there are no unexpected @@ -64,26 +64,47 @@ const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directorySource"; */ function test() { waitForExplicitFinish(); + let DirectoryLinksProvider = Cu.import("resource://gre/modules/DirectoryLinksProvider.jsm", {}).DirectoryLinksProvider; + let Promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise; + + // resolves promise when directory links are downloaded and written to disk + function watchLinksChangeOnce() { + let deferred = Promise.defer(); + let observer = { + onManyLinksChanged: () => { + DirectoryLinksProvider.removeObserver(observer); + deferred.resolve(); + } + }; + observer.onDownloadFail = observer.onManyLinksChanged; + DirectoryLinksProvider.addObserver(observer); + return deferred.promise; + }; - Services.prefs.setBoolPref(PREF_PRELOAD, false); - Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, "data:application/json,{}"); registerCleanupFunction(() => { Services.prefs.clearUserPref(PREF_PRELOAD); Services.prefs.clearUserPref(PREF_NEWTAB_DIRECTORYSOURCE); + return watchLinksChangeOnce(); }); - // Add a reflow observer and open a new tab. - docShell.addWeakReflowObserver(observer); - BrowserOpenTab(); + // run tests when directory source change completes + watchLinksChangeOnce().then(() => { + // Add a reflow observer and open a new tab. + docShell.addWeakReflowObserver(observer); + BrowserOpenTab(); - // Wait until the tabopen animation has finished. - waitForTransitionEnd(function () { - // Remove reflow observer and clean up. - docShell.removeWeakReflowObserver(observer); - gBrowser.removeCurrentTab(); - - finish(); + // Wait until the tabopen animation has finished. + waitForTransitionEnd(function () { + // Remove reflow observer and clean up. + docShell.removeWeakReflowObserver(observer); + gBrowser.removeCurrentTab(); + finish(); + }); }); + + Services.prefs.setBoolPref(PREF_PRELOAD, false); + // set directory source to empty links + Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, "data:application/json,{}"); } let observer = { diff --git a/browser/base/content/test/newtab/head.js b/browser/base/content/test/newtab/head.js index 6699b4ab0d3..88223f9b028 100644 --- a/browser/base/content/test/newtab/head.js +++ b/browser/base/content/test/newtab/head.js @@ -2,20 +2,19 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled"; -const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directorySource"; +const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source"; Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true); -// start with no directory links by default -Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, "data:application/json,{}"); let tmp = {}; Cu.import("resource://gre/modules/Promise.jsm", tmp); Cu.import("resource://gre/modules/NewTabUtils.jsm", tmp); +Cu.import("resource://gre/modules/DirectoryLinksProvider.jsm", tmp); Cc["@mozilla.org/moz/jssubscript-loader;1"] .getService(Ci.mozIJSSubScriptLoader) .loadSubScript("chrome://browser/content/sanitize.js", tmp); Cu.import("resource://gre/modules/Timer.jsm", tmp); -let {Promise, NewTabUtils, Sanitizer, clearTimeout} = tmp; +let {Promise, NewTabUtils, Sanitizer, clearTimeout, DirectoryLinksProvider} = tmp; let uri = Services.io.newURI("about:newtab", null, null); let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri); @@ -60,22 +59,45 @@ registerCleanupFunction(function () { if (oldInnerHeight) gBrowser.contentWindow.innerHeight = oldInnerHeight; - Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED); - Services.prefs.clearUserPref(PREF_NEWTAB_DIRECTORYSOURCE); - // Stop any update timers to prevent unexpected updates in later tests let timer = NewTabUtils.allPages._scheduleUpdateTimeout; if (timer) { clearTimeout(timer); delete NewTabUtils.allPages._scheduleUpdateTimeout; } + + Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED); + Services.prefs.clearUserPref(PREF_NEWTAB_DIRECTORYSOURCE); + + return watchLinksChangeOnce(); }); +/** + * Resolves promise when directory links are downloaded and written to disk + */ +function watchLinksChangeOnce() { + let deferred = Promise.defer(); + let observer = { + onManyLinksChanged: () => { + DirectoryLinksProvider.removeObserver(observer); + deferred.resolve(); + } + }; + observer.onDownloadFail = observer.onManyLinksChanged; + DirectoryLinksProvider.addObserver(observer); + return deferred.promise; +}; + /** * Provide the default test function to start our test runner. */ function test() { - TestRunner.run(); + waitForExplicitFinish(); + // start TestRunner.run() after directory links is downloaded and written to disk + watchLinksChangeOnce().then(() => { + TestRunner.run(); + }); + Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, "data:application/json,{}"); } /** @@ -86,8 +108,6 @@ let TestRunner = { * Starts the test runner. */ run: function () { - waitForExplicitFinish(); - this._iter = runTests(); this.next(); }, diff --git a/toolkit/modules/DirectoryLinksProvider.jsm b/toolkit/modules/DirectoryLinksProvider.jsm index 8dff9a10a59..e8d8ce13ece 100644 --- a/toolkit/modules/DirectoryLinksProvider.jsm +++ b/toolkit/modules/DirectoryLinksProvider.jsm @@ -14,6 +14,7 @@ const XMLHttpRequest = Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); @@ -32,7 +33,7 @@ const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; const PREF_SELECTED_LOCALE = "general.useragent.locale"; // The preference that tells where to obtain directory links -const PREF_DIRECTORY_SOURCE = "browser.newtabpage.directorySource"; +const PREF_DIRECTORY_SOURCE = "browser.newtabpage.directory.source"; // The frecency of a directory link const DIRECTORY_FRECENCY = 1000; @@ -52,7 +53,13 @@ let DirectoryLinksProvider = { __linksURL: null, - _observers: [], + _observers: new Set(), + + // links download deferred, resolved upon download completion + _downloadDeferred: null, + + // download default interval is 24 hours in milliseconds + _downloadIntervalMS: 86400000, get _observedPrefs() Object.freeze({ linksURL: PREF_DIRECTORY_SOURCE, @@ -111,8 +118,9 @@ let DirectoryLinksProvider = { if (aData == this._observedPrefs["linksURL"]) { delete this.__linksURL; } - this._callObservers("onManyLinksChanged"); } + // force directory download on changes to any of the observed prefs + this._fetchAndCacheLinksIfNecessary(true); }, _addPrefsObserver: function DirectoryLinksProvider_addObserver() { @@ -172,11 +180,9 @@ let DirectoryLinksProvider = { if (this.status && this.status != 200) { json = "{}"; } - let directoryLinksFilePath = OS.Path.join(OS.Constants.Path.localProfileDir, DIRECTORY_LINKS_FILE); - OS.File.writeAtomic(directoryLinksFilePath, json, {tmpPath: directoryLinksFilePath + ".tmp"}) + OS.File.writeAtomic(self._directoryFilePath, json, {tmpPath: self._directoryFilePath + ".tmp"}) .then(() => { deferred.resolve(); - self._callObservers("onManyLinksChanged"); }, () => { deferred.reject("Error writing uri data in profD."); @@ -197,6 +203,64 @@ let DirectoryLinksProvider = { return deferred.promise; }, + /** + * Downloads directory links if needed + * @return promise resolved immediately if no download needed, or upon completion + */ + _fetchAndCacheLinksIfNecessary: function DirectoryLinksProvider_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; + }, + + /** + * Submits counts of shown directory links for each type and + * triggers directory download if sponsored link was shown + * + * @param object keyed on types containing counts + * @return download promise + */ + reportShownCount: function DirectoryLinksProvider_reportShownCount(directoryCount) { + if (directoryCount.sponsored > 0 + || directoryCount.affiliate > 0 + || directoryCount.organic > 0) { + return this._fetchAndCacheLinksIfNecessary(); + } + return Promise.resolve(); + }, + /** * Gets the current set of directory links. * @param aCallback The function that the array of links is passed to. @@ -214,6 +278,19 @@ let DirectoryLinksProvider = { init: function DirectoryLinksProvider_init() { 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; + return Task.spawn(function() { + // get the last modified time of the links file if it exists + let doesFileExists = yield OS.File.exists(this._directoryFilePath); + if (doesFileExists) { + let fileInfo = yield OS.File.stat(this._directoryFilePath); + this._lastDownloadMS = Date.parse(fileInfo.lastModificationDate); + } + // fetch directory on startup without force + yield this._fetchAndCacheLinksIfNecessary(); + }.bind(this)); }, /** @@ -226,7 +303,11 @@ let DirectoryLinksProvider = { }, addObserver: function DirectoryLinksProvider_addObserver(aObserver) { - this._observers.push(aObserver); + this._observers.add(aObserver); + }, + + removeObserver: function DirectoryLinksProvider_removeObserver(aObserver) { + this._observers.delete(aObserver); }, _callObservers: function DirectoryLinksProvider__callObservers(aMethodName, aArg) { @@ -242,8 +323,6 @@ let DirectoryLinksProvider = { }, _removeObservers: function() { - while (this._observers.length) { - this._observers.pop(); - } + this._observers.clear(); } }; diff --git a/toolkit/modules/tests/xpcshell/test_DirectoryLinksProvider.js b/toolkit/modules/tests/xpcshell/test_DirectoryLinksProvider.js index e77ad87e40b..26ef2aba2e5 100644 --- a/toolkit/modules/tests/xpcshell/test_DirectoryLinksProvider.js +++ b/toolkit/modules/tests/xpcshell/test_DirectoryLinksProvider.js @@ -14,7 +14,9 @@ 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"); + XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); @@ -29,6 +31,10 @@ const kTestURL = 'data:application/json,' + JSON.stringify(kURLData); const kLocalePref = DirectoryLinksProvider._observedPrefs.prefSelectedLocale; const kSourceUrlPref = DirectoryLinksProvider._observedPrefs.linksURL; +// app/profile/firefox.js are not avaialble in xpcshell: hence, preset them +Services.prefs.setCharPref(kLocalePref, "en-US"); +Services.prefs.setCharPref(kSourceUrlPref, kTestURL); + // httpd settings var server; const kDefaultServerPort = 9000; @@ -103,19 +109,44 @@ function cleanJsonFile(jsonFile = DIRECTORY_LINKS_FILE) { return OS.File.remove(directoryLinksFilePath); } -// All tests that call setupDirectoryLinksProvider() must also call cleanDirectoryLinksProvider(). -function setupDirectoryLinksProvider(options = {}) { - let linksURL = options.linksURL || kTestURL; - DirectoryLinksProvider.init(); - Services.prefs.setCharPref(kLocalePref, options.locale || "en-US"); - Services.prefs.setCharPref(kSourceUrlPref, linksURL); - do_check_eq(DirectoryLinksProvider._linksURL, linksURL); +function LinksChangeObserver() { + this.deferred = Promise.defer(); + this.onManyLinksChanged = () => this.deferred.resolve(); + this.onDownloadFail = this.onManyLinksChanged; } -function cleanDirectoryLinksProvider() { - DirectoryLinksProvider.reset(); - Services.prefs.clearUserPref(kLocalePref); - Services.prefs.clearUserPref(kSourceUrlPref); +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(); + DirectoryLinksProvider.addObserver(observer); + Services.prefs.setCharPref(pref, newValue); + return observer.deferred.promise; + } + return Promise.resolve(); +} + +function promiseSetupDirectoryLinksProvider(options = {}) { + return Task.spawn(function() { + let linksURL = options.linksURL || kTestURL; + yield DirectoryLinksProvider.init(); + yield promiseDirectoryDownloadOnPrefChange(kLocalePref, options.locale || "en-US"); + yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, linksURL); + do_check_eq(DirectoryLinksProvider._linksURL, linksURL); + DirectoryLinksProvider._lastDownloadMS = options.lastDownloadMS || 0; + }); +} + +function promiseCleanDirectoryLinksProvider() { + return Task.spawn(function() { + yield promiseDirectoryDownloadOnPrefChange(kLocalePref, "en-US"); + yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, kTestURL); + DirectoryLinksProvider._lastDownloadMS = 0; + DirectoryLinksProvider.reset(); + }); } function run_test() { @@ -130,10 +161,14 @@ function run_test() { // Teardown. do_register_cleanup(function() { server.stop(function() { }); + DirectoryLinksProvider.reset(); + Services.prefs.clearUserPref(kLocalePref); + Services.prefs.clearUserPref(kSourceUrlPref); }); } add_task(function test_fetchAndCacheLinks_local() { + yield DirectoryLinksProvider.init(); yield cleanJsonFile(); // Trigger cache of data or chrome uri files in profD yield DirectoryLinksProvider._fetchAndCacheLinks(kTestURL); @@ -142,6 +177,7 @@ add_task(function test_fetchAndCacheLinks_local() { }); add_task(function test_fetchAndCacheLinks_remote() { + yield DirectoryLinksProvider.init(); yield cleanJsonFile(); // this must trigger directory links json download and save it to cache file yield DirectoryLinksProvider._fetchAndCacheLinks(kExampleURL); @@ -150,6 +186,7 @@ add_task(function test_fetchAndCacheLinks_remote() { }); add_task(function test_fetchAndCacheLinks_malformedURI() { + yield DirectoryLinksProvider.init(); yield cleanJsonFile(); let someJunk = "some junk"; try { @@ -165,6 +202,7 @@ add_task(function test_fetchAndCacheLinks_malformedURI() { }); add_task(function test_fetchAndCacheLinks_unknownHost() { + yield DirectoryLinksProvider.init(); yield cleanJsonFile(); let nonExistentServer = "http://nosuchhost"; try { @@ -180,6 +218,7 @@ add_task(function test_fetchAndCacheLinks_unknownHost() { }); add_task(function test_fetchAndCacheLinks_non200Status() { + yield DirectoryLinksProvider.init(); yield cleanJsonFile(); yield DirectoryLinksProvider._fetchAndCacheLinks(kFailURL); let data = yield readJsonFile(); @@ -187,24 +226,19 @@ add_task(function test_fetchAndCacheLinks_non200Status() { }); // To test onManyLinksChanged observer, trigger a fetch -add_task(function test_linkObservers() { - let deferred = Promise.defer(); - let testObserver = { - onManyLinksChanged: function() { - deferred.resolve(); - } - } +add_task(function test_DirectoryLinksProvider__linkObservers() { + yield DirectoryLinksProvider.init(); - DirectoryLinksProvider.init(); + let testObserver = new LinksChangeObserver(); DirectoryLinksProvider.addObserver(testObserver); - do_check_eq(DirectoryLinksProvider._observers.length, 1); - DirectoryLinksProvider._fetchAndCacheLinks(kTestURL); + do_check_eq(DirectoryLinksProvider._observers.size, 1); + DirectoryLinksProvider._fetchAndCacheLinksIfNecessary(true); - yield deferred.promise; + yield testObserver.deferred.promise; DirectoryLinksProvider._removeObservers(); - do_check_eq(DirectoryLinksProvider._observers.length, 0); + do_check_eq(DirectoryLinksProvider._observers.size, 0); - cleanDirectoryLinksProvider(); + yield promiseCleanDirectoryLinksProvider(); }); add_task(function test_linksURL_locale() { @@ -217,7 +251,7 @@ add_task(function test_linksURL_locale() { }; let dataURI = 'data:application/json,' + JSON.stringify(data); - setupDirectoryLinksProvider({linksURL: dataURI}); + yield promiseSetupDirectoryLinksProvider({linksURL: dataURI}); let links; let expected_data; @@ -227,7 +261,7 @@ add_task(function test_linksURL_locale() { expected_data = [{url: "http://example.com", title: "US", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}]; isIdentical(links, expected_data); - Services.prefs.setCharPref('general.useragent.locale', 'zh-CN'); + yield promiseDirectoryDownloadOnPrefChange("general.useragent.locale", "zh-CN"); links = yield fetchData(); do_check_eq(links.length, 2) @@ -237,11 +271,11 @@ add_task(function test_linksURL_locale() { ]; isIdentical(links, expected_data); - cleanDirectoryLinksProvider(); + yield promiseCleanDirectoryLinksProvider(); }); -add_task(function test_prefObserver_url() { - setupDirectoryLinksProvider({linksURL: kTestURL}); +add_task(function test_DirectoryLinksProvider__prefObserver_url() { + yield promiseSetupDirectoryLinksProvider({linksURL: kTestURL}); let links = yield fetchData(); do_check_eq(links.length, 1); @@ -252,18 +286,126 @@ add_task(function test_prefObserver_url() { // 1. _linksURL is properly set after the pref change // 2. invalid source url is correctly handled let exampleUrl = 'http://nosuchhost/bad'; - Services.prefs.setCharPref(kSourceUrlPref, exampleUrl); + yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, exampleUrl); do_check_eq(DirectoryLinksProvider._linksURL, exampleUrl); let newLinks = yield fetchData(); isIdentical(newLinks, []); - cleanDirectoryLinksProvider(); + yield promiseCleanDirectoryLinksProvider(); }); -add_task(function test_getLinks_noLocaleData() { - setupDirectoryLinksProvider({locale: 'zh-CN'}); +add_task(function test_DirectoryLinksProvider_getLinks_noLocaleData() { + yield promiseSetupDirectoryLinksProvider({locale: 'zh-CN'}); let links = yield fetchData(); do_check_eq(links.length, 0); - cleanDirectoryLinksProvider(); + yield promiseCleanDirectoryLinksProvider(); +}); + +add_task(function test_DirectoryLinksProvider_needsDownload() { + // test timestamping + DirectoryLinksProvider._lastDownloadMS = 0; + do_check_true(DirectoryLinksProvider._needsDownload); + DirectoryLinksProvider._lastDownloadMS = Date.now(); + do_check_false(DirectoryLinksProvider._needsDownload); + DirectoryLinksProvider._lastDownloadMS = Date.now() - (60*60*24 + 1)*1000; + do_check_true(DirectoryLinksProvider._needsDownload); + DirectoryLinksProvider._lastDownloadMS = 0; +}); + +add_task(function test_DirectoryLinksProvider_fetchAndCacheLinksIfNecessary() { + yield DirectoryLinksProvider.init(); + yield cleanJsonFile(); + // explicitly change source url to cause the download during setup + yield promiseSetupDirectoryLinksProvider({linksURL: kTestURL+" "}); + yield DirectoryLinksProvider._fetchAndCacheLinksIfNecessary(); + + // inspect lastDownloadMS timestamp which should be 5 seconds less then now() + let lastDownloadMS = DirectoryLinksProvider._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 DirectoryLinksProvider._fetchAndCacheLinksIfNecessary(); + do_check_eq(DirectoryLinksProvider._lastDownloadMS, lastDownloadMS); + + // clean the file and force the download + yield cleanJsonFile(); + yield DirectoryLinksProvider._fetchAndCacheLinksIfNecessary(true); + data = yield readJsonFile(); + isIdentical(data, kURLData); + + // make sure that failed download does not corrupt the file, nor changes lastDownloadMS + lastDownloadMS = DirectoryLinksProvider._lastDownloadMS; + yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, "http://"); + yield DirectoryLinksProvider._fetchAndCacheLinksIfNecessary(true); + data = yield readJsonFile(); + isIdentical(data, kURLData); + do_check_eq(DirectoryLinksProvider._lastDownloadMS, lastDownloadMS); + + // _fetchAndCacheLinksIfNecessary must return same promise if download is in progress + let downloadPromise = DirectoryLinksProvider._fetchAndCacheLinksIfNecessary(true); + let anotherPromise = DirectoryLinksProvider._fetchAndCacheLinksIfNecessary(true); + do_check_true(downloadPromise === anotherPromise); + yield downloadPromise; + + yield promiseCleanDirectoryLinksProvider(); +}); + +add_task(function test_DirectoryLinksProvider_fetchDirectoryOnPrefChange() { + yield DirectoryLinksProvider.init(); + + let testObserver = new LinksChangeObserver(); + DirectoryLinksProvider.addObserver(testObserver); + + yield cleanJsonFile(); + // ensure that provider does not think it needs to download + do_check_false(DirectoryLinksProvider._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; + let data = yield readJsonFile(); + isIdentical(data, kHttpHandlerData[kExamplePath]); + + yield promiseCleanDirectoryLinksProvider(); +}); + +add_task(function test_DirectoryLinksProvider_fetchDirectoryOnShowCount() { + yield promiseSetupDirectoryLinksProvider(); + + // set lastdownload to 0 to make DirectoryLinksProvider want to download + DirectoryLinksProvider._lastDownloadMS = 0; + do_check_true(DirectoryLinksProvider._needsDownload); + + // Tell DirectoryLinksProvider that newtab has no room for sponsored links + let directoryCount = {sponsored: 0}; + yield DirectoryLinksProvider.reportShownCount(directoryCount); + // the provider must skip download, hence that lastdownload is still 0 + do_check_eq(DirectoryLinksProvider._lastDownloadMS, 0); + + // make room for sponsored links and repeat, download should happen + directoryCount.sponsored = 1; + yield DirectoryLinksProvider.reportShownCount(directoryCount); + do_check_true(DirectoryLinksProvider._lastDownloadMS != 0); + + yield promiseCleanDirectoryLinksProvider(); +}); + +add_task(function test_DirectoryLinksProvider_fetchDirectoryOnInit() { + // ensure preferences are set to defaults + yield promiseSetupDirectoryLinksProvider(); + // now clean to provider, so we can init it again + yield promiseCleanDirectoryLinksProvider(); + + yield cleanJsonFile(); + yield DirectoryLinksProvider.init(); + let data = yield readJsonFile(); + isIdentical(data, kURLData); + + yield promiseCleanDirectoryLinksProvider(); });