mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1159571 - Use server provided frequency caps for daily and lifetime totals [r=adw]
This commit is contained in:
parent
e9b1d39b38
commit
af25cb0b4a
@ -148,6 +148,7 @@ Below is an example directory source file::
|
||||
"treeherder.mozilla.org",
|
||||
"wiki.mozilla.org"
|
||||
],
|
||||
"frequency_caps": {"daily": 3, "total": 10},
|
||||
"imageURI": "https://tiles.cdn.mozilla.net/images/9ee2b265678f2775de2e4bf680df600b502e6038.3875.png",
|
||||
"time_limits": {"start": "2014-01-01T00:00:00.000Z", "end": "2014-02-01T00:00:00.000Z"},
|
||||
"title": "Thanks for testing!",
|
||||
@ -183,6 +184,9 @@ A suggested link has additional values:
|
||||
Suggested Tile if the user has the site in one of the top 100 most-frecent
|
||||
pages. Only preapproved array of strings that are hardcoded into the
|
||||
DirectoryLinksProvider module are allowed.
|
||||
- ``frequency_caps`` - an object consisting of daily and total frequency caps
|
||||
that limit the number of times a Suggested Tile can be shown in the new tab
|
||||
per day and overall.
|
||||
- ``time_limits`` - an object consisting of start and end timestamps specifying
|
||||
when a Suggested Tile may start and has to stop showing in the newtab.
|
||||
The timestamp is expected in ISO_8601 format: '2014-01-10T20:00:00.000Z'
|
||||
|
@ -90,8 +90,16 @@ const DIRECTORY_FRECENCY = 1000;
|
||||
// The frecency of a suggested link
|
||||
const SUGGESTED_FRECENCY = Infinity;
|
||||
|
||||
// Default number of times to show a link
|
||||
const DEFAULT_FREQUENCY_CAP = 5;
|
||||
// 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;
|
||||
@ -124,16 +132,16 @@ let DirectoryLinksProvider = {
|
||||
*/
|
||||
_enhancedLinks: new Map(),
|
||||
|
||||
/**
|
||||
* A mapping from site to remaining number of views
|
||||
*/
|
||||
_frequencyCaps: new Map(),
|
||||
|
||||
/**
|
||||
* A mapping from site to a list of suggested link objects
|
||||
*/
|
||||
_suggestedLinks: new Map(),
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@ -470,7 +478,7 @@ let DirectoryLinksProvider = {
|
||||
sites.slice(0, triggeringSiteIndex + 1).forEach(site => {
|
||||
let {targetedSite, url} = site.link;
|
||||
if (targetedSite) {
|
||||
this._decreaseFrequencyCap(url, 1);
|
||||
this._addFrequencyCapView(url);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -478,7 +486,7 @@ let DirectoryLinksProvider = {
|
||||
else if (action == "click") {
|
||||
let {targetedSite, url} = sites[triggeringSiteIndex].link;
|
||||
if (targetedSite) {
|
||||
this._decreaseFrequencyCap(url, DEFAULT_FREQUENCY_CAP);
|
||||
this._setFrequencyCapClick(url);
|
||||
}
|
||||
}
|
||||
|
||||
@ -533,8 +541,12 @@ let DirectoryLinksProvider = {
|
||||
ping.open("POST", pingEndPoint + (action == "view" ? "view" : "click"));
|
||||
ping.send(JSON.stringify(data));
|
||||
|
||||
// Use this as an opportunity to potentially fetch new links
|
||||
return this._fetchAndCacheLinksIfNecessary();
|
||||
return Task.spawn(function* () {
|
||||
// since we updated views/clicks we need write _frequencyCaps to disk
|
||||
yield this._writeFrequencyCapFile();
|
||||
// Use this as an opportunity to potentially fetch new links
|
||||
yield this._fetchAndCacheLinksIfNecessary();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -580,7 +592,6 @@ let DirectoryLinksProvider = {
|
||||
this._readDirectoryLinksFile().then(rawLinks => {
|
||||
// Reset the cache of suggested tiles and enhanced images for this new set of links
|
||||
this._enhancedLinks.clear();
|
||||
this._frequencyCaps.clear();
|
||||
this._suggestedLinks.clear();
|
||||
this._clearCampaignTimeout();
|
||||
|
||||
@ -604,7 +615,7 @@ let DirectoryLinksProvider = {
|
||||
// We cache suggested tiles here but do not push any of them in the links list yet.
|
||||
// The decision for which suggested tile to include will be made separately.
|
||||
this._cacheSuggestedLinks(link);
|
||||
this._frequencyCaps.set(link.url, DEFAULT_FREQUENCY_CAP);
|
||||
this._updateFrequencyCapSettings(link);
|
||||
});
|
||||
|
||||
rawLinks.enhanced.filter(validityFilter).forEach((link, position) => {
|
||||
@ -625,6 +636,11 @@ let DirectoryLinksProvider = {
|
||||
// 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);
|
||||
@ -642,6 +658,9 @@ let DirectoryLinksProvider = {
|
||||
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);
|
||||
|
||||
NewTabUtils.placesProvider.addObserver(this);
|
||||
NewTabUtils.links.addObserver(this);
|
||||
|
||||
@ -652,6 +671,8 @@ let DirectoryLinksProvider = {
|
||||
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();
|
||||
}.bind(this));
|
||||
@ -695,6 +716,22 @@ let DirectoryLinksProvider = {
|
||||
});
|
||||
},
|
||||
|
||||
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 NewTabUtils.links handles the notification first.
|
||||
setTimeout(() => {
|
||||
@ -711,21 +748,6 @@ let DirectoryLinksProvider = {
|
||||
}, 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Record for a url that some number of views have been used
|
||||
* @param url String url of the suggested link
|
||||
* @param amount Number of equivalent views to decrease
|
||||
*/
|
||||
_decreaseFrequencyCap(url, amount) {
|
||||
let remainingViews = this._frequencyCaps.get(url) - amount;
|
||||
this._frequencyCaps.set(url, remainingViews);
|
||||
|
||||
// Reached the number of views, so pick a new one.
|
||||
if (remainingViews <= 0) {
|
||||
this._updateSuggestedTile();
|
||||
}
|
||||
},
|
||||
|
||||
_getCurrentTopSiteCount: function() {
|
||||
let visibleTopSiteCount = 0;
|
||||
for (let link of NewTabUtils.links.getLinks().slice(0, MIN_VISIBLE_HISTORY_TILES)) {
|
||||
@ -805,7 +827,7 @@ let DirectoryLinksProvider = {
|
||||
let suggestedLinksMap = this._suggestedLinks.get(topSiteWithSuggestedLink);
|
||||
suggestedLinksMap.forEach((suggestedLink, url) => {
|
||||
// Skip this link if we've shown it too many times already
|
||||
if (this._frequencyCaps.get(url) <= 0) {
|
||||
if (!this._testFrequencyCapLimits(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -862,6 +884,209 @@ let DirectoryLinksProvider = {
|
||||
return chosenSuggestedLink;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 DirectoryLinksProvider_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 DirectoryLinksProvider_clearFrequencyCap() {
|
||||
this._frequencyCaps = {};
|
||||
return this._writeFrequencyCapFile();
|
||||
},
|
||||
|
||||
/**
|
||||
* updates frequency cap configuration for a link
|
||||
*/
|
||||
_updateFrequencyCapSettings: function DirectoryLinksProvider_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 DirectoryLinksProvider_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 DirectoryLinksProvider_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 DirectoryLinksProvider_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 dialy 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: function DirectoryLinksProvider_reportFrequencyCapClick(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 DirectoryLinksProvider_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 DirectoryLinksProvider_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 DirectoryLinksProvider_removeAllTileClicks() {
|
||||
Object.keys(this._frequencyCaps).forEach(url => {
|
||||
delete this._frequencyCaps[url].clicked;
|
||||
});
|
||||
return this._writeFrequencyCapFile();
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the object to its pre-init state
|
||||
*/
|
||||
|
@ -16,6 +16,7 @@ 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");
|
||||
@ -197,6 +198,7 @@ function promiseCleanDirectoryLinksProvider() {
|
||||
return Task.spawn(function() {
|
||||
yield promiseDirectoryDownloadOnPrefChange(kLocalePref, "en-US");
|
||||
yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, kTestURL);
|
||||
yield DirectoryLinksProvider._clearFrequencyCap();
|
||||
DirectoryLinksProvider._lastDownloadMS = 0;
|
||||
DirectoryLinksProvider.reset();
|
||||
});
|
||||
@ -223,6 +225,20 @@ function run_test() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
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"
|
||||
@ -551,7 +567,8 @@ add_task(function test_frequencyCappedSites_views() {
|
||||
suggested: [{
|
||||
type: "affiliate",
|
||||
frecent_sites: targets,
|
||||
url: testUrl
|
||||
url: testUrl,
|
||||
frequency_caps: {daily: 5}
|
||||
}],
|
||||
directory: [{
|
||||
type: "organic",
|
||||
@ -1399,3 +1416,259 @@ add_task(function test_setupStartEndTime() {
|
||||
DirectoryLinksProvider._setupStartEndTime(link);
|
||||
do_check_false(link.startTime);
|
||||
});
|
||||
|
||||
add_task(function test_DirectoryLinksProvider_frequencyCapSetup() {
|
||||
yield promiseSetupDirectoryLinksProvider();
|
||||
yield DirectoryLinksProvider.init();
|
||||
|
||||
yield promiseCleanDirectoryLinksProvider();
|
||||
yield DirectoryLinksProvider._readFrequencyCapFile();
|
||||
isIdentical(DirectoryLinksProvider._frequencyCaps, {});
|
||||
|
||||
// setup few links
|
||||
DirectoryLinksProvider._updateFrequencyCapSettings({
|
||||
url: "1",
|
||||
});
|
||||
DirectoryLinksProvider._updateFrequencyCapSettings({
|
||||
url: "2",
|
||||
frequency_caps: {daily: 1, total: 2}
|
||||
});
|
||||
DirectoryLinksProvider._updateFrequencyCapSettings({
|
||||
url: "3",
|
||||
frequency_caps: {total: 2}
|
||||
});
|
||||
DirectoryLinksProvider._updateFrequencyCapSettings({
|
||||
url: "4",
|
||||
frequency_caps: {daily: 1}
|
||||
});
|
||||
let freqCapsObject = DirectoryLinksProvider._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 DirectoryLinksProvider._writeFrequencyCapFile();
|
||||
// empty out freqCapsObject and read file back
|
||||
DirectoryLinksProvider._frequencyCaps = {};
|
||||
yield DirectoryLinksProvider._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
|
||||
DirectoryLinksProvider._updateFrequencyCapSettings({
|
||||
url: "3",
|
||||
frequency_caps: {daily: 1, total: 2}
|
||||
});
|
||||
DirectoryLinksProvider._updateFrequencyCapSettings({
|
||||
url: "7",
|
||||
frequency_caps: {daily: 1, total: 2}
|
||||
});
|
||||
// now prune the ones that have been in the object longer than 1 second
|
||||
DirectoryLinksProvider._pruneFrequencyCapUrls(1000);
|
||||
// make sure all keys but "3" and "7" are deleted
|
||||
Object.keys(DirectoryLinksProvider._frequencyCaps).forEach(key => {
|
||||
do_check_true(key == "3" || key == "7");
|
||||
});
|
||||
|
||||
yield promiseCleanDirectoryLinksProvider();
|
||||
});
|
||||
|
||||
add_task(function test_DirectoryLinksProvider_getFrequencyCapLogic() {
|
||||
yield promiseSetupDirectoryLinksProvider();
|
||||
yield DirectoryLinksProvider.init();
|
||||
|
||||
// setup suggested links
|
||||
DirectoryLinksProvider._updateFrequencyCapSettings({
|
||||
url: "1",
|
||||
frequency_caps: {daily: 2, total: 4}
|
||||
});
|
||||
|
||||
do_check_true(DirectoryLinksProvider._testFrequencyCapLimits("1"));
|
||||
// exhaust daily views
|
||||
DirectoryLinksProvider._addFrequencyCapView("1")
|
||||
do_check_true(DirectoryLinksProvider._testFrequencyCapLimits("1"));
|
||||
DirectoryLinksProvider._addFrequencyCapView("1")
|
||||
do_check_false(DirectoryLinksProvider._testFrequencyCapLimits("1"));
|
||||
|
||||
// now step into the furture
|
||||
let _wasTodayOrig = DirectoryLinksProvider._wasToday;
|
||||
DirectoryLinksProvider._wasToday = function () {return false;}
|
||||
// exhaust total views
|
||||
DirectoryLinksProvider._addFrequencyCapView("1")
|
||||
do_check_true(DirectoryLinksProvider._testFrequencyCapLimits("1"));
|
||||
DirectoryLinksProvider._addFrequencyCapView("1")
|
||||
// reached totalViews 4, should return false
|
||||
do_check_false(DirectoryLinksProvider._testFrequencyCapLimits("1"));
|
||||
|
||||
// add more views by updating configuration
|
||||
DirectoryLinksProvider._updateFrequencyCapSettings({
|
||||
url: "1",
|
||||
frequency_caps: {daily: 5, total: 10}
|
||||
});
|
||||
// should be true, since we have more total views
|
||||
do_check_true(DirectoryLinksProvider._testFrequencyCapLimits("1"));
|
||||
|
||||
// set click flag
|
||||
DirectoryLinksProvider._setFrequencyCapClick("1");
|
||||
// always false after click
|
||||
do_check_false(DirectoryLinksProvider._testFrequencyCapLimits("1"));
|
||||
|
||||
// use unknown urls and ensure nothing breaks
|
||||
DirectoryLinksProvider._addFrequencyCapView("nosuch.url");
|
||||
DirectoryLinksProvider._setFrequencyCapClick("nosuch.url");
|
||||
// testing unknown url should always return false
|
||||
do_check_false(DirectoryLinksProvider._testFrequencyCapLimits("nosuch.url"));
|
||||
|
||||
// reset _wasToday back to original function
|
||||
DirectoryLinksProvider._wasToday = _wasTodayOrig;
|
||||
yield promiseCleanDirectoryLinksProvider();
|
||||
});
|
||||
|
||||
add_task(function test_DirectoryLinksProvider_getFrequencyCapReportSiteAction() {
|
||||
yield promiseSetupDirectoryLinksProvider();
|
||||
yield DirectoryLinksProvider.init();
|
||||
|
||||
// setup suggested links
|
||||
DirectoryLinksProvider._updateFrequencyCapSettings({
|
||||
url: "bar.com",
|
||||
frequency_caps: {daily: 2, total: 4}
|
||||
});
|
||||
|
||||
do_check_true(DirectoryLinksProvider._testFrequencyCapLimits("bar.com"));
|
||||
// report site action
|
||||
yield DirectoryLinksProvider.reportSitesAction([{
|
||||
link: {
|
||||
targetedSite: "foo.com",
|
||||
url: "bar.com"
|
||||
},
|
||||
isPinned: function() {return false;},
|
||||
}], "view", 0);
|
||||
|
||||
// read file content and ensure that view counters are updated
|
||||
let data = yield readJsonFile(DirectoryLinksProvider._frequencyCapFilePath);
|
||||
do_check_eq(data["bar.com"].dailyViews, 1);
|
||||
do_check_eq(data["bar.com"].totalViews, 1);
|
||||
|
||||
yield promiseCleanDirectoryLinksProvider();
|
||||
});
|
||||
|
||||
add_task(function test_DirectoryLinksProvider_ClickRemoval() {
|
||||
yield promiseSetupDirectoryLinksProvider();
|
||||
yield DirectoryLinksProvider.init();
|
||||
let landingUrl = "http://foo.com";
|
||||
|
||||
// setup suggested links
|
||||
DirectoryLinksProvider._updateFrequencyCapSettings({
|
||||
url: landingUrl,
|
||||
frequency_caps: {daily: 2, total: 4}
|
||||
});
|
||||
|
||||
// add views
|
||||
DirectoryLinksProvider._addFrequencyCapView(landingUrl)
|
||||
DirectoryLinksProvider._addFrequencyCapView(landingUrl)
|
||||
// make a click
|
||||
DirectoryLinksProvider._setFrequencyCapClick(landingUrl);
|
||||
|
||||
// views must be 2 and click must be set
|
||||
do_check_eq(DirectoryLinksProvider._frequencyCaps[landingUrl].totalViews, 2);
|
||||
do_check_true(DirectoryLinksProvider._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();
|
||||
DirectoryLinksProvider.addObserver(testObserver);
|
||||
|
||||
PlacesUtils.bhistory.removePage(NetUtil.newURI(landingUrl));
|
||||
yield testObserver.promise;
|
||||
DirectoryLinksProvider.removeObserver(testObserver);
|
||||
// views must be 2 and click should not exist
|
||||
do_check_eq(DirectoryLinksProvider._frequencyCaps[landingUrl].totalViews, 2);
|
||||
do_check_false(DirectoryLinksProvider._frequencyCaps[landingUrl].hasOwnProperty("clicked"));
|
||||
|
||||
// verify that disk written data is kosher
|
||||
let data = yield readJsonFile(DirectoryLinksProvider._frequencyCapFilePath);
|
||||
do_check_eq(data[landingUrl].totalViews, 2);
|
||||
do_check_false(data[landingUrl].hasOwnProperty("clicked"));
|
||||
|
||||
// now test clear history
|
||||
DirectoryLinksProvider._updateFrequencyCapSettings({
|
||||
url: landingUrl,
|
||||
frequency_caps: {daily: 2, total: 4}
|
||||
});
|
||||
DirectoryLinksProvider._updateFrequencyCapSettings({
|
||||
url: "http://bar.com",
|
||||
frequency_caps: {daily: 2, total: 4}
|
||||
});
|
||||
|
||||
DirectoryLinksProvider._setFrequencyCapClick(landingUrl);
|
||||
DirectoryLinksProvider._setFrequencyCapClick("http://bar.com");
|
||||
// both tiles must have clicked
|
||||
do_check_true(DirectoryLinksProvider._frequencyCaps[landingUrl].clicked);
|
||||
do_check_true(DirectoryLinksProvider._frequencyCaps["http://bar.com"].clicked);
|
||||
|
||||
testObserver = new UrlDeletionTester();
|
||||
DirectoryLinksProvider.addObserver(testObserver);
|
||||
// remove all hostory
|
||||
PlacesUtils.bhistory.removeAllPages();
|
||||
|
||||
yield testObserver.promise;
|
||||
DirectoryLinksProvider.removeObserver(testObserver);
|
||||
// no clicks should remain in the cap object
|
||||
do_check_false(DirectoryLinksProvider._frequencyCaps[landingUrl].hasOwnProperty("clicked"));
|
||||
do_check_false(DirectoryLinksProvider._frequencyCaps["http://bar.com"].hasOwnProperty("clicked"));
|
||||
|
||||
// verify that disk written data is kosher
|
||||
data = yield readJsonFile(DirectoryLinksProvider._frequencyCapFilePath);
|
||||
do_check_false(data[landingUrl].hasOwnProperty("clicked"));
|
||||
do_check_false(data["http://bar.com"].hasOwnProperty("clicked"));
|
||||
|
||||
yield promiseCleanDirectoryLinksProvider();
|
||||
});
|
||||
|
@ -674,6 +674,20 @@ let PlacesProvider = {
|
||||
|
||||
_observers: [],
|
||||
|
||||
/**
|
||||
* Called by the history service.
|
||||
*/
|
||||
onDeleteURI: function PlacesProvider_onDeleteURI(aURI, aGUID, aReason) {
|
||||
// let observers remove sensetive data associated with deleted visit
|
||||
this._callObservers("onDeleteURI", {
|
||||
url: aURI.spec,
|
||||
});
|
||||
},
|
||||
|
||||
onClearHistory: function() {
|
||||
this._callObservers("onClearHistory")
|
||||
},
|
||||
|
||||
/**
|
||||
* Called by the history service.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user