browser_asrouter_targeting.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. const {ASRouterTargeting, QueryCache} =
  2. ChromeUtils.import("resource://activity-stream/lib/ASRouterTargeting.jsm");
  3. const {AddonTestUtils} =
  4. ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm");
  5. const {CFRMessageProvider} = ChromeUtils.import("resource://activity-stream/lib/CFRMessageProvider.jsm");
  6. ChromeUtils.defineModuleGetter(this, "ProfileAge",
  7. "resource://gre/modules/ProfileAge.jsm");
  8. ChromeUtils.defineModuleGetter(this, "AddonManager",
  9. "resource://gre/modules/AddonManager.jsm");
  10. ChromeUtils.defineModuleGetter(this, "ShellService",
  11. "resource:///modules/ShellService.jsm");
  12. ChromeUtils.defineModuleGetter(this, "NewTabUtils",
  13. "resource://gre/modules/NewTabUtils.jsm");
  14. ChromeUtils.defineModuleGetter(this, "PlacesTestUtils",
  15. "resource://testing-common/PlacesTestUtils.jsm");
  16. ChromeUtils.defineModuleGetter(this, "TelemetryEnvironment",
  17. "resource://gre/modules/TelemetryEnvironment.jsm");
  18. // ASRouterTargeting.isMatch
  19. add_task(async function should_do_correct_targeting() {
  20. is(await ASRouterTargeting.isMatch("FOO", {FOO: true}), true, "should return true for a matching value");
  21. is(await ASRouterTargeting.isMatch("!FOO", {FOO: true}), false, "should return false for a non-matching value");
  22. });
  23. add_task(async function should_handle_async_getters() {
  24. const context = {get FOO() { return Promise.resolve(true); }};
  25. is(await ASRouterTargeting.isMatch("FOO", context), true, "should return true for a matching async value");
  26. });
  27. // ASRouterTargeting.findMatchingMessage
  28. add_task(async function find_matching_message() {
  29. const messages = [
  30. {id: "foo", targeting: "FOO"},
  31. {id: "bar", targeting: "!FOO"},
  32. ];
  33. const context = {FOO: true};
  34. const match = await ASRouterTargeting.findMatchingMessage({messages, context});
  35. is(match, messages[0], "should match and return the correct message");
  36. });
  37. add_task(async function return_nothing_for_no_matching_message() {
  38. const messages = [{id: "bar", targeting: "!FOO"}];
  39. const context = {FOO: true};
  40. const match = await ASRouterTargeting.findMatchingMessage({messages, context});
  41. is(match, undefined, "should return nothing since no matching message exists");
  42. });
  43. add_task(async function check_syntax_error_handling() {
  44. let result;
  45. function onError(...args) {
  46. result = args;
  47. }
  48. const messages = [{id: "foo", targeting: "foo === 0"}];
  49. const match = await ASRouterTargeting.findMatchingMessage({messages, onError});
  50. is(match, undefined, "should return nothing since no valid matching message exists");
  51. // Note that in order for the following test to pass, we are expecting a particular filepath for mozjexl.
  52. // If the location of this file has changed, the MOZ_JEXL_FILEPATH constant should be updated om ASRouterTargeting.jsm
  53. is(result[0], ASRouterTargeting.ERROR_TYPES.MALFORMED_EXPRESSION,
  54. "should recognize the error as coming from mozjexl and call onError with the MALFORMED_EXPRESSION error type");
  55. ok(result[1].message,
  56. "should call onError with the error from mozjexl");
  57. is(result[2], messages[0],
  58. "should call onError with the invalid message");
  59. });
  60. add_task(async function check_other_error_handling() {
  61. let result;
  62. function onError(...args) {
  63. result = args;
  64. }
  65. const messages = [{id: "foo", targeting: "foo"}];
  66. const context = {get foo() { throw new Error("test error"); }};
  67. const match = await ASRouterTargeting.findMatchingMessage({messages, context, onError});
  68. is(match, undefined, "should return nothing since no valid matching message exists");
  69. // Note that in order for the following test to pass, we are expecting a particular filepath for mozjexl.
  70. // If the location of this file has changed, the MOZ_JEXL_FILEPATH constant should be updated om ASRouterTargeting.jsm
  71. is(result[0], ASRouterTargeting.ERROR_TYPES.OTHER_ERROR,
  72. "should not recognize the error as being an other error, not a mozjexl one");
  73. is(result[1].message, "test error",
  74. "should call onError with the error thrown in the context");
  75. is(result[2], messages[0],
  76. "should call onError with the invalid message");
  77. });
  78. // ASRouterTargeting.Environment
  79. add_task(async function check_locale() {
  80. ok(Services.locale.appLocaleAsLangTag, "Services.locale.appLocaleAsLangTag exists");
  81. const message = {id: "foo", targeting: `locale == "${Services.locale.appLocaleAsLangTag}"`};
  82. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  83. "should select correct item when filtering by locale");
  84. });
  85. add_task(async function check_localeLanguageCode() {
  86. const currentLanguageCode = Services.locale.appLocaleAsLangTag.substr(0, 2);
  87. is(
  88. Services.locale.negotiateLanguages([currentLanguageCode], [Services.locale.appLocaleAsLangTag])[0],
  89. Services.locale.appLocaleAsLangTag,
  90. "currentLanguageCode should resolve to the current locale (e.g en => en-US)"
  91. );
  92. const message = {id: "foo", targeting: `localeLanguageCode == "${currentLanguageCode}"`};
  93. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  94. "should select correct item when filtering by localeLanguageCode");
  95. });
  96. add_task(async function checkProfileAgeCreated() {
  97. let profileAccessor = await ProfileAge();
  98. is(await ASRouterTargeting.Environment.profileAgeCreated, await profileAccessor.created,
  99. "should return correct profile age creation date");
  100. const message = {id: "foo", targeting: `profileAgeCreated > ${await profileAccessor.created - 100}`};
  101. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  102. "should select correct item by profile age created");
  103. });
  104. add_task(async function checkProfileAgeReset() {
  105. let profileAccessor = await ProfileAge();
  106. is(await ASRouterTargeting.Environment.profileAgeReset, await profileAccessor.reset,
  107. "should return correct profile age reset");
  108. const message = {id: "foo", targeting: `profileAgeReset == ${await profileAccessor.reset}`};
  109. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  110. "should select correct item by profile age reset");
  111. });
  112. add_task(async function checkCurrentDate() {
  113. let message = {id: "foo", targeting: `currentDate < '${new Date(Date.now() + 5000)}'|date`};
  114. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  115. "should select message based on currentDate < timestamp");
  116. message = {id: "foo", targeting: `currentDate > '${new Date(Date.now() - 5000)}'|date`};
  117. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  118. "should select message based on currentDate > timestamp");
  119. });
  120. add_task(async function check_usesFirefoxSync() {
  121. await pushPrefs(["services.sync.username", "someone@foo.com"]);
  122. is(await ASRouterTargeting.Environment.usesFirefoxSync, true,
  123. "should return true if a fx account is set");
  124. const message = {id: "foo", targeting: "usesFirefoxSync"};
  125. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  126. "should select correct item by usesFirefoxSync");
  127. });
  128. add_task(async function check_isFxAEnabled() {
  129. await pushPrefs(["identity.fxaccounts.enabled", false]);
  130. is(await ASRouterTargeting.Environment.isFxAEnabled, false,
  131. "should return false if fxa is disabled");
  132. const message = {id: "foo", targeting: "isFxAEnabled"};
  133. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), undefined,
  134. "should not select a message if fxa is disabled");
  135. });
  136. add_task(async function check_isFxAEnabled() {
  137. await pushPrefs(["identity.fxaccounts.enabled", true]);
  138. is(await ASRouterTargeting.Environment.isFxAEnabled, true,
  139. "should return true if fxa is enabled");
  140. const message = {id: "foo", targeting: "isFxAEnabled"};
  141. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  142. "should select the correct message");
  143. });
  144. add_task(async function check_totalBookmarksCount() {
  145. // Make sure we remove default bookmarks so they don't interfere
  146. await clearHistoryAndBookmarks();
  147. const message = {id: "foo", targeting: "totalBookmarksCount > 0"};
  148. const results = await ASRouterTargeting.findMatchingMessage({messages: [message]});
  149. is(results ? JSON.stringify(results) : results, undefined,
  150. "Should not select any message because bookmarks count is not 0");
  151. const bookmark = await PlacesUtils.bookmarks.insert({
  152. parentGuid: PlacesUtils.bookmarks.unfiledGuid,
  153. title: "foo",
  154. url: "https://mozilla1.com/nowNew",
  155. });
  156. QueryCache.queries.TotalBookmarksCount.expire();
  157. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  158. "Should select correct item after bookmarks are added.");
  159. // Cleanup
  160. await PlacesUtils.bookmarks.remove(bookmark.guid);
  161. });
  162. add_task(async function check_needsUpdate() {
  163. QueryCache.queries.CheckBrowserNeedsUpdate.setUp(true);
  164. const message = {id: "foo", targeting: "needsUpdate"};
  165. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  166. "Should select message because update count > 0");
  167. QueryCache.queries.CheckBrowserNeedsUpdate.setUp(false);
  168. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), null,
  169. "Should not select message because update count == 0");
  170. });
  171. add_task(async function checksearchEngines() {
  172. const result = await ASRouterTargeting.Environment.searchEngines;
  173. const expectedInstalled = (await Services.search.getVisibleEngines())
  174. .map(engine => engine.identifier)
  175. .sort()
  176. .join(",");
  177. ok(result.installed.length,
  178. "searchEngines.installed should be a non-empty array");
  179. is(result.installed.sort().join(","), expectedInstalled,
  180. "searchEngines.installed should be an array of visible search engines");
  181. ok(result.current && typeof result.current === "string",
  182. "searchEngines.current should be a truthy string");
  183. is(result.current, (await Services.search.getDefault()).identifier,
  184. "searchEngines.current should be the current engine name");
  185. const message = {id: "foo", targeting: `searchEngines[.current == ${(await Services.search.getDefault()).identifier}]`};
  186. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  187. "should select correct item by searchEngines.current");
  188. const message2 = {id: "foo", targeting: `searchEngines[${(await Services.search.getVisibleEngines())[0].identifier} in .installed]`};
  189. is(await ASRouterTargeting.findMatchingMessage({messages: [message2]}), message2,
  190. "should select correct item by searchEngines.installed");
  191. });
  192. add_task(async function checkisDefaultBrowser() {
  193. const expected = ShellService.isDefaultBrowser();
  194. const result = ASRouterTargeting.Environment.isDefaultBrowser;
  195. is(typeof result, "boolean",
  196. "isDefaultBrowser should be a boolean value");
  197. is(result, expected,
  198. "isDefaultBrowser should be equal to ShellService.isDefaultBrowser()");
  199. const message = {id: "foo", targeting: `isDefaultBrowser == ${expected.toString()}`};
  200. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  201. "should select correct item by isDefaultBrowser");
  202. });
  203. add_task(async function checkdevToolsOpenedCount() {
  204. await pushPrefs(["devtools.selfxss.count", 5]);
  205. is(ASRouterTargeting.Environment.devToolsOpenedCount, 5,
  206. "devToolsOpenedCount should be equal to devtools.selfxss.count pref value");
  207. const message = {id: "foo", targeting: "devToolsOpenedCount >= 5"};
  208. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  209. "should select correct item by devToolsOpenedCount");
  210. });
  211. AddonTestUtils.initMochitest(this);
  212. add_task(async function checkAddonsInfo() {
  213. const FAKE_ID = "testaddon@tests.mozilla.org";
  214. const FAKE_NAME = "Test Addon";
  215. const FAKE_VERSION = "0.5.7";
  216. const xpi = AddonTestUtils.createTempWebExtensionFile({
  217. manifest: {
  218. applications: {gecko: {id: FAKE_ID}},
  219. name: FAKE_NAME,
  220. version: FAKE_VERSION,
  221. },
  222. });
  223. await Promise.all([
  224. AddonTestUtils.promiseWebExtensionStartup(FAKE_ID),
  225. AddonManager.installTemporaryAddon(xpi),
  226. ]);
  227. const {addons} = await AddonManager.getActiveAddons(["extension", "service"]);
  228. const {addons: asRouterAddons, isFullData} = await ASRouterTargeting.Environment.addonsInfo;
  229. ok(addons.every(({id}) => asRouterAddons[id]), "should contain every addon");
  230. ok(Object.getOwnPropertyNames(asRouterAddons).every(id => addons.some(addon => addon.id === id)),
  231. "should contain no incorrect addons");
  232. const testAddon = asRouterAddons[FAKE_ID];
  233. ok(Object.prototype.hasOwnProperty.call(testAddon, "version") && testAddon.version === FAKE_VERSION,
  234. "should correctly provide `version` property");
  235. ok(Object.prototype.hasOwnProperty.call(testAddon, "type") && testAddon.type === "extension",
  236. "should correctly provide `type` property");
  237. ok(Object.prototype.hasOwnProperty.call(testAddon, "isSystem") && testAddon.isSystem === false,
  238. "should correctly provide `isSystem` property");
  239. ok(Object.prototype.hasOwnProperty.call(testAddon, "isWebExtension") && testAddon.isWebExtension === true,
  240. "should correctly provide `isWebExtension` property");
  241. // As we installed our test addon the addons database must be initialised, so
  242. // (in this test environment) we expect to receive "full" data
  243. ok(isFullData, "should receive full data");
  244. ok(Object.prototype.hasOwnProperty.call(testAddon, "name") && testAddon.name === FAKE_NAME,
  245. "should correctly provide `name` property from full data");
  246. ok(Object.prototype.hasOwnProperty.call(testAddon, "userDisabled") && testAddon.userDisabled === false,
  247. "should correctly provide `userDisabled` property from full data");
  248. ok(Object.prototype.hasOwnProperty.call(testAddon, "installDate") &&
  249. (Math.abs(Date.now() - new Date(testAddon.installDate)) < 60 * 1000),
  250. "should correctly provide `installDate` property from full data");
  251. });
  252. add_task(async function checkFrecentSites() {
  253. const now = Date.now();
  254. const timeDaysAgo = numDays => now - numDays * 24 * 60 * 60 * 1000;
  255. const visits = [];
  256. for (const [uri, count, visitDate] of [
  257. ["https://mozilla1.com/", 10, timeDaysAgo(0)], // frecency 1000
  258. ["https://mozilla2.com/", 5, timeDaysAgo(1)], // frecency 500
  259. ["https://mozilla3.com/", 1, timeDaysAgo(2)], // frecency 100
  260. ]) {
  261. [...Array(count).keys()].forEach(() => visits.push({
  262. uri,
  263. visitDate: visitDate * 1000, // Places expects microseconds
  264. }));
  265. }
  266. await PlacesTestUtils.addVisits(visits);
  267. let message = {id: "foo", targeting: "'mozilla3.com' in topFrecentSites|mapToProperty('host')"};
  268. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  269. "should select correct item by host in topFrecentSites");
  270. message = {id: "foo", targeting: "'non-existent.com' in topFrecentSites|mapToProperty('host')"};
  271. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), undefined,
  272. "should not select incorrect item by host in topFrecentSites");
  273. message = {id: "foo", targeting: "'mozilla2.com' in topFrecentSites[.frecency >= 400]|mapToProperty('host')"};
  274. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  275. "should select correct item when filtering by frecency");
  276. message = {id: "foo", targeting: "'mozilla2.com' in topFrecentSites[.frecency >= 600]|mapToProperty('host')"};
  277. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), undefined,
  278. "should not select incorrect item when filtering by frecency");
  279. message = {id: "foo", targeting: `'mozilla2.com' in topFrecentSites[.lastVisitDate >= ${timeDaysAgo(1) - 1}]|mapToProperty('host')`};
  280. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  281. "should select correct item when filtering by lastVisitDate");
  282. message = {id: "foo", targeting: `'mozilla2.com' in topFrecentSites[.lastVisitDate >= ${timeDaysAgo(0) - 1}]|mapToProperty('host')`};
  283. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), undefined,
  284. "should not select incorrect item when filtering by lastVisitDate");
  285. message = {id: "foo", targeting: `(topFrecentSites[.frecency >= 900 && .lastVisitDate >= ${timeDaysAgo(1) - 1}]|mapToProperty('host') intersect ['mozilla3.com', 'mozilla2.com', 'mozilla1.com'])|length > 0`};
  286. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  287. "should select correct item when filtering by frecency and lastVisitDate with multiple candidate domains");
  288. // Cleanup
  289. await clearHistoryAndBookmarks();
  290. });
  291. add_task(async function check_pinned_sites() {
  292. const originalPin = JSON.stringify(NewTabUtils.pinnedLinks.links);
  293. const sitesToPin = [
  294. {url: "https://foo.com"},
  295. {url: "https://bloo.com"},
  296. {url: "https://floogle.com", searchTopSite: true},
  297. ];
  298. sitesToPin.forEach((site => NewTabUtils.pinnedLinks.pin(site, NewTabUtils.pinnedLinks.links.length)));
  299. // Unpinning adds null to the list of pinned sites, which we should test that we handle gracefully for our targeting
  300. NewTabUtils.pinnedLinks.unpin(sitesToPin[1]);
  301. ok(NewTabUtils.pinnedLinks.links.includes(null),
  302. "should have set an item in pinned links to null via unpinning for testing");
  303. let message;
  304. message = {id: "foo", targeting: "'https://foo.com' in pinnedSites|mapToProperty('url')"};
  305. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  306. "should select correct item by url in pinnedSites");
  307. message = {id: "foo", targeting: "'foo.com' in pinnedSites|mapToProperty('host')"};
  308. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  309. "should select correct item by host in pinnedSites");
  310. message = {id: "foo", targeting: "'floogle.com' in pinnedSites[.searchTopSite == true]|mapToProperty('host')"};
  311. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  312. "should select correct item by host and searchTopSite in pinnedSites");
  313. // Cleanup
  314. sitesToPin.forEach(site => NewTabUtils.pinnedLinks.unpin(site));
  315. await clearHistoryAndBookmarks();
  316. is(JSON.stringify(NewTabUtils.pinnedLinks.links), originalPin,
  317. "should restore pinned sites to its original state");
  318. });
  319. add_task(async function check_firefox_version() {
  320. const message = {id: "foo", targeting: "firefoxVersion > 0"};
  321. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  322. "should select correct item when filtering by firefox version");
  323. });
  324. add_task(async function check_region() {
  325. await SpecialPowers.pushPrefEnv({"set": [["browser.search.region", "DE"]]});
  326. const message = {id: "foo", targeting: "region in ['DE']"};
  327. is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
  328. "should select correct item when filtering by firefox geo");
  329. });
  330. add_task(async function check_browserSettings() {
  331. is(await JSON.stringify(ASRouterTargeting.Environment.browserSettings.update), JSON.stringify(TelemetryEnvironment.currentEnvironment.settings.update),
  332. "should return correct update info");
  333. });
  334. add_task(async function check_sync() {
  335. is(await ASRouterTargeting.Environment.sync.desktopDevices, Services.prefs.getIntPref("services.sync.clients.devices.desktop", 0),
  336. "should return correct desktopDevices info");
  337. is(await ASRouterTargeting.Environment.sync.mobileDevices, Services.prefs.getIntPref("services.sync.clients.devices.mobile", 0),
  338. "should return correct mobileDevices info");
  339. is(await ASRouterTargeting.Environment.sync.totalDevices, Services.prefs.getIntPref("services.sync.numClients", 0),
  340. "should return correct mobileDevices info");
  341. });
  342. add_task(async function check_provider_cohorts() {
  343. await pushPrefs(["browser.newtabpage.activity-stream.asrouter.providers.onboarding", JSON.stringify({id: "onboarding", messages: [], enabled: true, cohort: "foo"})]);
  344. await pushPrefs(["browser.newtabpage.activity-stream.asrouter.providers.cfr", JSON.stringify({id: "cfr", enabled: true, cohort: "bar"})]);
  345. is(await ASRouterTargeting.Environment.providerCohorts.onboarding, "foo",
  346. "should have cohort foo for onboarding");
  347. is(await ASRouterTargeting.Environment.providerCohorts.cfr, "bar",
  348. "should have cohort bar for cfr");
  349. });
  350. add_task(async function check_xpinstall_enabled() {
  351. // should default to true if pref doesn't exist
  352. is(await ASRouterTargeting.Environment.xpinstallEnabled, true);
  353. // flip to false, check targeting reflects that
  354. await pushPrefs(["xpinstall.enabled", false]);
  355. is(await ASRouterTargeting.Environment.xpinstallEnabled, false);
  356. // flip to true, check targeting reflects that
  357. await pushPrefs(["xpinstall.enabled", true]);
  358. is(await ASRouterTargeting.Environment.xpinstallEnabled, true);
  359. });
  360. add_task(async function check_pinned_tabs() {
  361. await BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, async browser => {
  362. is(await ASRouterTargeting.Environment.hasPinnedTabs, false, "No pin tabs yet");
  363. let tab = gBrowser.getTabForBrowser(browser);
  364. gBrowser.pinTab(tab);
  365. is(await ASRouterTargeting.Environment.hasPinnedTabs, true, "Should detect pinned tab");
  366. gBrowser.unpinTab(tab);
  367. });
  368. });
  369. add_task(async function checkCFRPinnedTabsTargetting() {
  370. const now = Date.now();
  371. const timeMinutesAgo = numMinutes => now - numMinutes * 60 * 1000;
  372. const messages = CFRMessageProvider.getMessages();
  373. const trigger = {
  374. id: "frequentVisits",
  375. context: {
  376. recentVisits: [
  377. {timestamp: timeMinutesAgo(61)},
  378. {timestamp: timeMinutesAgo(30)},
  379. {timestamp: timeMinutesAgo(1)},
  380. ],
  381. },
  382. param: {host: "github.com", url: "https://google.com"},
  383. };
  384. is(await ASRouterTargeting.findMatchingMessage({messages, trigger}), undefined,
  385. "should not select PIN_TAB mesage with only 2 visits in past hour");
  386. trigger.context.recentVisits.push({timestamp: timeMinutesAgo(59)});
  387. is((await ASRouterTargeting.findMatchingMessage({messages, trigger})).id, "PIN_TAB",
  388. "should select PIN_TAB mesage");
  389. await BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, async browser => {
  390. let tab = gBrowser.getTabForBrowser(browser);
  391. gBrowser.pinTab(tab);
  392. is(await ASRouterTargeting.findMatchingMessage({messages, trigger}), undefined,
  393. "should not select PIN_TAB mesage if there is a pinned tab already");
  394. gBrowser.unpinTab(tab);
  395. });
  396. trigger.param = {host: "foo.bar", url: "https://foo.bar"};
  397. is(await ASRouterTargeting.findMatchingMessage({messages, trigger}), undefined,
  398. "should not select PIN_TAB mesage with a trigger param/host not in our hostlist");
  399. });
  400. add_task(async function checkPatternMatches() {
  401. const now = Date.now();
  402. const timeMinutesAgo = numMinutes => now - numMinutes * 60 * 1000;
  403. const messages = [{id: "message_with_pattern", targeting: "true", trigger: {id: "frequentVisits", patterns: ["*://*.github.com/"]}}];
  404. const trigger = {
  405. id: "frequentVisits",
  406. context: {
  407. recentVisits: [
  408. {timestamp: timeMinutesAgo(33)},
  409. {timestamp: timeMinutesAgo(17)},
  410. {timestamp: timeMinutesAgo(1)},
  411. ],
  412. },
  413. param: {host: "github.com", url: "https://gist.github.com"},
  414. };
  415. is((await ASRouterTargeting.findMatchingMessage({messages, trigger})).id, "message_with_pattern", "should select PIN_TAB mesage");
  416. });
  417. add_task(async function checkPatternsValid() {
  418. const messages = CFRMessageProvider.getMessages().filter(m => m.trigger.patterns);
  419. for (const message of messages) {
  420. Assert.ok(new MatchPatternSet(message.trigger.patterns));
  421. }
  422. });