browser_asrouter_cfr.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. const {CFRPageActions} =
  2. ChromeUtils.import("resource://activity-stream/lib/CFRPageActions.jsm");
  3. const {ASRouterTriggerListeners} =
  4. ChromeUtils.import("resource://activity-stream/lib/ASRouterTriggerListeners.jsm");
  5. const {ASRouter} =
  6. ChromeUtils.import("resource://activity-stream/lib/ASRouter.jsm");
  7. const createDummyRecommendation = ({action, category, heading_text}) => ({
  8. content: {
  9. category,
  10. notification_text: "Mochitest",
  11. heading_text: heading_text || "Mochitest",
  12. info_icon: {
  13. label: {attributes: {tooltiptext: "Why am I seeing this"}},
  14. sumo_path: "extensionrecommendations",
  15. },
  16. addon: {
  17. id: "addon-id",
  18. title: "Addon name",
  19. icon: "foo",
  20. author: "Author name",
  21. amo_url: "https://example.com",
  22. },
  23. descriptionDetails: {steps: []},
  24. text: "Mochitest",
  25. buttons: {
  26. primary: {
  27. label: {
  28. value: "OK",
  29. attributes: {accesskey: "O"},
  30. },
  31. action: {
  32. type: action.type,
  33. data: {},
  34. },
  35. },
  36. secondary: [{
  37. label: {
  38. value: "Cancel",
  39. attributes: {accesskey: "C"},
  40. },
  41. }, {
  42. label: {
  43. value: "Cancel 1",
  44. attributes: {accesskey: "A"},
  45. },
  46. }, {
  47. label: {
  48. value: "Cancel 2",
  49. attributes: {accesskey: "B"},
  50. },
  51. }],
  52. },
  53. },
  54. });
  55. function checkCFRFeaturesElements(notification) {
  56. Assert.ok(notification.hidden === false, "Panel should be visible");
  57. Assert.ok(notification.getAttribute("data-notification-category") === "cfrFeatures", "Panel have corret data attribute");
  58. Assert.ok(notification.querySelector("#cfr-notification-footer-pintab-animation-container"), "Pin tab animation exists");
  59. Assert.ok(notification.querySelector("#cfr-notification-feature-steps"), "Pin tab steps");
  60. }
  61. function checkCFRAddonsElements(notification) {
  62. Assert.ok(notification.hidden === false, "Panel should be visible");
  63. Assert.ok(notification.getAttribute("data-notification-category") === "cfrAddons", "Panel have corret data attribute");
  64. Assert.ok(notification.querySelector("#cfr-notification-footer-text-and-addon-info"), "Panel should have addon info container");
  65. Assert.ok(notification.querySelector("#cfr-notification-footer-filled-stars"), "Panel should have addon rating info");
  66. Assert.ok(notification.querySelector("#cfr-notification-author"), "Panel should have author info");
  67. }
  68. function clearNotifications() {
  69. for (let notification of PopupNotifications._currentNotifications) {
  70. notification.remove();
  71. }
  72. // Clicking the primary action also removes the notification
  73. Assert.equal(PopupNotifications._currentNotifications.length, 0,
  74. "Should have removed the notification");
  75. }
  76. function trigger_cfr_panel(browser, trigger, {action = {type: "FOO"}, heading_text, category = "cfrAddons"} = {}) { // a fake action type will result in the action being ignored
  77. const recommendation = createDummyRecommendation({action, category, heading_text});
  78. if (category !== "cfrAddons") {
  79. delete recommendation.content.addon;
  80. }
  81. clearNotifications();
  82. return CFRPageActions.addRecommendation(
  83. browser,
  84. trigger,
  85. recommendation,
  86. // Use the real AS dispatch method to trigger real notifications
  87. ASRouter.dispatch
  88. );
  89. }
  90. add_task(async function setup() {
  91. // Store it in order to restore to the original value
  92. const {_fetchLatestAddonVersion} = CFRPageActions;
  93. // Prevent fetching the real addon url and making a network request
  94. CFRPageActions._fetchLatestAddonVersion = x => "http://example.com";
  95. registerCleanupFunction(() => {
  96. CFRPageActions._fetchLatestAddonVersion = _fetchLatestAddonVersion;
  97. });
  98. });
  99. add_task(async function test_cfr_notification_show() {
  100. // addRecommendation checks that scheme starts with http and host matches
  101. let browser = gBrowser.selectedBrowser;
  102. await BrowserTestUtils.loadURI(browser, "http://example.com/");
  103. await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");
  104. const response = await trigger_cfr_panel(browser, "example.com");
  105. Assert.ok(response, "Should return true if addRecommendation checks were successful");
  106. const showPanel = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
  107. // Open the panel
  108. document.getElementById("contextual-feature-recommendation").click();
  109. await showPanel;
  110. Assert.ok(document.getElementById("contextual-feature-recommendation-notification").hidden === false,
  111. "Panel should be visible");
  112. // Check there is a primary button and click it. It will trigger the callback.
  113. Assert.ok(document.getElementById("contextual-feature-recommendation-notification").button);
  114. let hidePanel = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
  115. document.getElementById("contextual-feature-recommendation-notification").button.click();
  116. await hidePanel;
  117. // Clicking the primary action also removes the notification
  118. Assert.equal(PopupNotifications._currentNotifications.length, 0,
  119. "Should have removed the notification");
  120. });
  121. add_task(async function test_cfr_notification_show() {
  122. // addRecommendation checks that scheme starts with http and host matches
  123. let browser = gBrowser.selectedBrowser;
  124. await BrowserTestUtils.loadURI(browser, "http://example.com/");
  125. await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");
  126. let response = await trigger_cfr_panel(browser, "example.com", {heading_text: "First Message"});
  127. Assert.ok(response, "Should return true if addRecommendation checks were successful");
  128. const showPanel = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
  129. // Try adding another message
  130. response = await trigger_cfr_panel(browser, "example.com", {heading_text: "Second Message"});
  131. Assert.equal(response, false, "Should return false if second call did not add the message");
  132. // Open the panel
  133. document.getElementById("contextual-feature-recommendation").click();
  134. await showPanel;
  135. Assert.ok(document.getElementById("contextual-feature-recommendation-notification").hidden === false,
  136. "Panel should be visible");
  137. Assert.equal(document.getElementById("cfr-notification-header-label").value, "First Message",
  138. "The first message should be visible");
  139. // Check there is a primary button and click it. It will trigger the callback.
  140. Assert.ok(document.getElementById("contextual-feature-recommendation-notification").button);
  141. let hidePanel = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
  142. document.getElementById("contextual-feature-recommendation-notification").button.click();
  143. await hidePanel;
  144. // Clicking the primary action also removes the notification
  145. Assert.equal(PopupNotifications._currentNotifications.length, 0,
  146. "Should have removed the notification");
  147. });
  148. add_task(async function test_cfr_addon_install() {
  149. // addRecommendation checks that scheme starts with http and host matches
  150. const browser = gBrowser.selectedBrowser;
  151. await BrowserTestUtils.loadURI(browser, "http://example.com/");
  152. await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");
  153. const response = await trigger_cfr_panel(browser, "example.com", {action: {type: "INSTALL_ADDON_FROM_URL"}});
  154. Assert.ok(response, "Should return true if addRecommendation checks were successful");
  155. const showPanel = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
  156. // Open the panel
  157. document.getElementById("contextual-feature-recommendation").click();
  158. await showPanel;
  159. Assert.ok(document.getElementById("contextual-feature-recommendation-notification").hidden === false,
  160. "Panel should be visible");
  161. checkCFRAddonsElements(document.getElementById("contextual-feature-recommendation-notification"));
  162. // Check there is a primary button and click it. It will trigger the callback.
  163. Assert.ok(document.getElementById("contextual-feature-recommendation-notification").button);
  164. const hidePanel = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
  165. document.getElementById("contextual-feature-recommendation-notification").button.click();
  166. await hidePanel;
  167. await BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
  168. let [notification] = PopupNotifications.panel.childNodes;
  169. // Trying to install the addon will trigger a progress popup or an error popup if
  170. // running the test multiple times in a row
  171. Assert.ok(notification.id === "addon-progress-notification" ||
  172. notification.id === "addon-install-failed-notification", "Should try to install the addon");
  173. // This removes the `Addon install failure` notifications
  174. while (PopupNotifications._currentNotifications.length) {
  175. PopupNotifications.remove(PopupNotifications._currentNotifications[0]);
  176. }
  177. // There should be no more notifications left
  178. Assert.equal(PopupNotifications._currentNotifications.length, 0,
  179. "Should have removed the notification");
  180. });
  181. add_task(async function test_cfr_pin_tab_notification_show() {
  182. // addRecommendation checks that scheme starts with http and host matches
  183. let browser = gBrowser.selectedBrowser;
  184. await BrowserTestUtils.loadURI(browser, "http://example.com/");
  185. await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");
  186. const response = await trigger_cfr_panel(browser, "example.com", {action: {type: "PIN_CURRENT_TAB"}, category: "cfrFeatures"});
  187. Assert.ok(response, "Should return true if addRecommendation checks were successful");
  188. const showPanel = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
  189. // Open the panel
  190. document.getElementById("contextual-feature-recommendation").click();
  191. await showPanel;
  192. const notification = document.getElementById("contextual-feature-recommendation-notification");
  193. checkCFRFeaturesElements(notification);
  194. // Check there is a primary button and click it. It will trigger the callback.
  195. Assert.ok(notification.button);
  196. let hidePanel = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
  197. document.getElementById("contextual-feature-recommendation-notification").button.click();
  198. await hidePanel;
  199. await BrowserTestUtils.waitForCondition(() => gBrowser.selectedTab.pinned, "Primary action should pin tab");
  200. Assert.ok(gBrowser.selectedTab.pinned, "Current tab should be pinned");
  201. gBrowser.unpinTab(gBrowser.selectedTab);
  202. // Clicking the primary action also removes the notification
  203. Assert.equal(PopupNotifications._currentNotifications.length, 0,
  204. "Should have removed the notification");
  205. });
  206. add_task(async function test_cfr_features_and_addon_show() {
  207. // addRecommendation checks that scheme starts with http and host matches
  208. let browser = gBrowser.selectedBrowser;
  209. await BrowserTestUtils.loadURI(browser, "http://example.com/");
  210. await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");
  211. // Trigger Feature CFR
  212. let response = await trigger_cfr_panel(browser, "example.com", {action: {type: "PIN_CURRENT_TAB"}, category: "cfrFeatures"});
  213. Assert.ok(response, "Should return true if addRecommendation checks were successful");
  214. let showPanel = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
  215. // Open the panel
  216. document.getElementById("contextual-feature-recommendation").click();
  217. await showPanel;
  218. const notification = document.getElementById("contextual-feature-recommendation-notification");
  219. checkCFRFeaturesElements(notification);
  220. // Check there is a primary button and click it. It will trigger the callback.
  221. Assert.ok(notification.button);
  222. let hidePanel = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
  223. document.getElementById("contextual-feature-recommendation-notification").button.click();
  224. await hidePanel;
  225. // Clicking the primary action also removes the notification
  226. Assert.equal(PopupNotifications._currentNotifications.length, 0,
  227. "Should have removed the notification");
  228. // Trigger Addon CFR
  229. response = await trigger_cfr_panel(browser, "example.com");
  230. Assert.ok(response, "Should return true if addRecommendation checks were successful");
  231. showPanel = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
  232. // Open the panel
  233. document.getElementById("contextual-feature-recommendation").click();
  234. await showPanel;
  235. Assert.ok(document.getElementById("contextual-feature-recommendation-notification").hidden === false,
  236. "Panel should be visible");
  237. checkCFRAddonsElements(document.getElementById("contextual-feature-recommendation-notification"));
  238. // Check there is a primary button and click it. It will trigger the callback.
  239. Assert.ok(notification.button);
  240. hidePanel = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
  241. document.getElementById("contextual-feature-recommendation-notification").button.click();
  242. await hidePanel;
  243. // Clicking the primary action also removes the notification
  244. Assert.equal(PopupNotifications._currentNotifications.length, 0,
  245. "Should have removed the notification");
  246. });
  247. add_task(async function test_cfr_addon_and_features_show() {
  248. // addRecommendation checks that scheme starts with http and host matches
  249. let browser = gBrowser.selectedBrowser;
  250. await BrowserTestUtils.loadURI(browser, "http://example.com/");
  251. await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");
  252. // Trigger Feature CFR
  253. let response = await trigger_cfr_panel(browser, "example.com");
  254. Assert.ok(response, "Should return true if addRecommendation checks were successful");
  255. let showPanel = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
  256. // Open the panel
  257. document.getElementById("contextual-feature-recommendation").click();
  258. await showPanel;
  259. const notification = document.getElementById("contextual-feature-recommendation-notification");
  260. checkCFRAddonsElements(notification);
  261. // Check there is a primary button and click it. It will trigger the callback.
  262. Assert.ok(notification.button);
  263. let hidePanel = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
  264. document.getElementById("contextual-feature-recommendation-notification").button.click();
  265. await hidePanel;
  266. // Clicking the primary action also removes the notification
  267. Assert.equal(PopupNotifications._currentNotifications.length, 0,
  268. "Should have removed the notification");
  269. // Trigger Addon CFR
  270. response = await trigger_cfr_panel(browser, "example.com", {action: {type: "PIN_CURRENT_TAB"}, category: "cfrFeatures"});
  271. Assert.ok(response, "Should return true if addRecommendation checks were successful");
  272. showPanel = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
  273. // Open the panel
  274. document.getElementById("contextual-feature-recommendation").click();
  275. await showPanel;
  276. Assert.ok(document.getElementById("contextual-feature-recommendation-notification").hidden === false,
  277. "Panel should be visible");
  278. checkCFRFeaturesElements(document.getElementById("contextual-feature-recommendation-notification"));
  279. // Check there is a primary button and click it. It will trigger the callback.
  280. Assert.ok(notification.button);
  281. hidePanel = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
  282. document.getElementById("contextual-feature-recommendation-notification").button.click();
  283. await hidePanel;
  284. // Clicking the primary action also removes the notification
  285. Assert.equal(PopupNotifications._currentNotifications.length, 0,
  286. "Should have removed the notification");
  287. });
  288. add_task(async function test_onLocationChange_cb() {
  289. let count = 0;
  290. const triggerHandler = () => ++count;
  291. const TEST_URL = "https://example.com/browser/browser/components/newtab/test/browser/blue_page.html";
  292. ASRouterTriggerListeners.get("openURL").init(triggerHandler, ["example.com"]);
  293. const browser = gBrowser.selectedBrowser;
  294. await BrowserTestUtils.loadURI(browser, "http://example.com/");
  295. await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");
  296. Assert.equal(count, 1, "Count navigation to example.com");
  297. // Anchor scroll triggers a location change event with the same document
  298. // https://searchfox.org/mozilla-central/rev/8848b9741fc4ee4e9bc3ae83ea0fc048da39979f/uriloader/base/nsIWebProgressListener.idl#400-403
  299. await BrowserTestUtils.loadURI(browser, "http://example.com/#foo");
  300. await BrowserTestUtils.waitForLocationChange(gBrowser, "http://example.com/#foo");
  301. Assert.equal(count, 1, "It should ignore same page navigation");
  302. await BrowserTestUtils.loadURI(browser, TEST_URL);
  303. await BrowserTestUtils.browserLoaded(browser, false, TEST_URL);
  304. Assert.equal(count, 2, "We moved to a new document");
  305. });
  306. add_task(async function test_matchPattern() {
  307. let count = 0;
  308. const triggerHandler = () => ++count;
  309. const frequentVisitsTrigger = ASRouterTriggerListeners.get("frequentVisits");
  310. frequentVisitsTrigger.init(triggerHandler, [], ["*://*.example.com/"]);
  311. const browser = gBrowser.selectedBrowser;
  312. await BrowserTestUtils.loadURI(browser, "http://example.com/");
  313. await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");
  314. await BrowserTestUtils.waitForCondition(() => frequentVisitsTrigger._visits.get("example.com").length === 1, "Registered pattern matched the current location");
  315. await BrowserTestUtils.loadURI(browser, "about:config");
  316. await BrowserTestUtils.browserLoaded(browser, false, "about:config");
  317. await BrowserTestUtils.waitForCondition(() => frequentVisitsTrigger._visits.get("example.com").length === 1, "Navigated to a new page but not a match");
  318. await BrowserTestUtils.loadURI(browser, "http://example.com/");
  319. await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");
  320. await BrowserTestUtils.waitForCondition(() => frequentVisitsTrigger._visits.get("example.com").length === 1, "Navigated to a location that matches the pattern but within 15 mins");
  321. await BrowserTestUtils.loadURI(browser, "http://www.example.com/");
  322. await BrowserTestUtils.browserLoaded(browser, false, "http://www.example.com/");
  323. await BrowserTestUtils.waitForCondition(() => frequentVisitsTrigger._visits.get("www.example.com").length === 1, "www.example.com is a different host that also matches the pattern.");
  324. await BrowserTestUtils.waitForCondition(() => frequentVisitsTrigger._visits.get("example.com").length === 1, "www.example.com is a different host that also matches the pattern.");
  325. });
  326. add_task(async function test_providerNames() {
  327. const providersBranch = "browser.newtabpage.activity-stream.asrouter.providers.";
  328. const cfrProviderPrefs = Services.prefs.getChildList(providersBranch);
  329. for (const prefName of cfrProviderPrefs) {
  330. const prefValue = JSON.parse(Services.prefs.getStringPref(prefName));
  331. if (prefValue.id) { // Snippets are disabled in tests and value is set to []
  332. Assert.equal(prefValue.id, prefName.slice(providersBranch.length), "Provider id and pref name do not match");
  333. }
  334. }
  335. });