Bug 1159571 - Use server provided frequency caps for daily and lifetime totals [r=adw]

This commit is contained in:
Maxim Zhilyaev 2015-05-08 09:20:18 -07:00
parent e9b1d39b38
commit af25cb0b4a
4 changed files with 546 additions and 30 deletions

View File

@ -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'

View File

@ -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
*/

View File

@ -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();
});

View File

@ -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.
*/