test_TopSitesFeed.js (103472B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 ChromeUtils.defineESModuleGetters(this, { 7 actionCreators: "resource://newtab/common/Actions.mjs", 8 actionTypes: "resource://newtab/common/Actions.mjs", 9 ContileIntegration: "resource://newtab/lib/TopSitesFeed.sys.mjs", 10 DEFAULT_TOP_SITES: "resource://newtab/lib/TopSitesFeed.sys.mjs", 11 FilterAdult: "resource:///modules/FilterAdult.sys.mjs", 12 NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs", 13 NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", 14 ObliviousHTTP: "resource://gre/modules/ObliviousHTTP.sys.mjs", 15 PageThumbs: "resource://gre/modules/PageThumbs.sys.mjs", 16 PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", 17 sinon: "resource://testing-common/Sinon.sys.mjs", 18 Screenshots: "resource://newtab/lib/Screenshots.sys.mjs", 19 Sampling: "resource://gre/modules/components-utils/Sampling.sys.mjs", 20 SearchService: "resource://gre/modules/SearchService.sys.mjs", 21 TOP_SITES_DEFAULT_ROWS: "resource:///modules/topsites/constants.mjs", 22 TOP_SITES_MAX_SITES_PER_ROW: "resource:///modules/topsites/constants.mjs", 23 TopSitesFeed: "resource://newtab/lib/TopSitesFeed.sys.mjs", 24 }); 25 26 const FAKE_FAVICON = "data987"; 27 const FAKE_FAVICON_SIZE = 128; 28 const FAKE_FRECENCY = PlacesUtils.history.pageFrecencyThreshold(0, 2, false); 29 const FAKE_LINKS = new Array(2 * TOP_SITES_MAX_SITES_PER_ROW) 30 .fill(null) 31 .map((v, i) => ({ 32 frecency: FAKE_FRECENCY, 33 url: `http://www.site${i}.com`, 34 })); 35 const FAKE_SCREENSHOT = "data123"; 36 const SEARCH_SHORTCUTS_EXPERIMENT_PREF = "improvesearch.topSiteSearchShortcuts"; 37 const SEARCH_SHORTCUTS_SEARCH_ENGINES_PREF = 38 "improvesearch.topSiteSearchShortcuts.searchEngines"; 39 const SEARCH_SHORTCUTS_HAVE_PINNED_PREF = 40 "improvesearch.topSiteSearchShortcuts.havePinned"; 41 const SHOWN_ON_NEWTAB_PREF = "feeds.topsites"; 42 const SHOW_SPONSORED_PREF = "showSponsoredTopSites"; 43 const TOP_SITES_BLOCKED_SPONSORS_PREF = "browser.topsites.blockedSponsors"; 44 45 // This pref controls how long the contile cache is valid for in seconds. 46 const CONTILE_CACHE_VALID_FOR_SECONDS_PREF = 47 "browser.topsites.contile.cacheValidFor"; 48 // This pref records when the last contile fetch occurred, as a UNIX timestamp 49 // in seconds. 50 const CONTILE_CACHE_LAST_FETCH_PREF = "browser.topsites.contile.lastFetch"; 51 52 function FakeTippyTopProvider() {} 53 FakeTippyTopProvider.prototype = { 54 async init() { 55 this.initialized = true; 56 }, 57 processSite(site) { 58 return site; 59 }, 60 }; 61 62 let gSearchServiceInitStub; 63 let gGetTopSitesStub; 64 65 function getTopSitesFeedForTest(sandbox) { 66 sandbox.stub(ContileIntegration.prototype, "PersistentCache").returns({ 67 set: sandbox.stub(), 68 get: sandbox.stub(), 69 }); 70 71 let feed = new TopSitesFeed(); 72 73 feed.store = { 74 dispatch: sinon.spy(), 75 getState() { 76 return this.state; 77 }, 78 state: { 79 Prefs: { values: { topSitesRows: 2 } }, 80 TopSites: { rows: Array(12).fill("site") }, 81 }, 82 }; 83 84 return feed; 85 } 86 87 add_setup(async () => { 88 let sandbox = sinon.createSandbox(); 89 sandbox.stub(SearchService.prototype, "defaultEngine").get(() => { 90 return { identifier: "ddg", searchUrlDomain: "duckduckgo.com" }; 91 }); 92 93 gGetTopSitesStub = sandbox 94 .stub(NewTabUtils.activityStreamLinks, "getTopSites") 95 .resolves(FAKE_LINKS); 96 97 gSearchServiceInitStub = sandbox 98 .stub(SearchService.prototype, "init") 99 .resolves(); 100 101 sandbox.stub(NewTabUtils.activityStreamProvider, "_faviconBytesToDataURI"); 102 103 sandbox 104 .stub(NewTabUtils.activityStreamProvider, "_addFavicons") 105 .callsFake(l => { 106 return Promise.resolve( 107 l.map(link => { 108 link.favicon = FAKE_FAVICON; 109 link.faviconSize = FAKE_FAVICON_SIZE; 110 return link; 111 }) 112 ); 113 }); 114 115 sandbox.stub(Screenshots, "getScreenshotForURL").resolves(FAKE_SCREENSHOT); 116 sandbox.spy(Screenshots, "maybeCacheScreenshot"); 117 sandbox.stub(Screenshots, "_shouldGetScreenshots").returns(true); 118 119 registerCleanupFunction(() => { 120 sandbox.restore(); 121 }); 122 }); 123 124 add_task(async function test_construction() { 125 let sandbox = sinon.createSandbox(); 126 sandbox.stub(ContileIntegration.prototype, "PersistentCache").returns({ 127 set: sandbox.stub(), 128 get: sandbox.stub(), 129 }); 130 let feed = new TopSitesFeed(); 131 Assert.ok(feed, "Could construct a TopSitesFeed"); 132 Assert.ok(feed._currentSearchHostname, "_currentSearchHostname defined"); 133 sandbox.restore(); 134 }); 135 136 add_task(async function test_refreshDefaults() { 137 let sandbox = sinon.createSandbox(); 138 sandbox.stub(ContileIntegration.prototype, "PersistentCache").returns({ 139 set: sandbox.stub(), 140 get: sandbox.stub(), 141 }); 142 let feed = new TopSitesFeed(); 143 Assert.ok( 144 !DEFAULT_TOP_SITES.length, 145 "Should have 0 DEFAULT_TOP_SITES initially." 146 ); 147 148 info("refreshDefaults should add defaults on PREFS_INITIAL_VALUES"); 149 feed.onAction({ 150 type: actionTypes.PREFS_INITIAL_VALUES, 151 data: { "default.sites": "https://foo.com" }, 152 }); 153 154 Assert.equal( 155 DEFAULT_TOP_SITES.length, 156 1, 157 "Should have 1 DEFAULT_TOP_SITES now." 158 ); 159 160 // Reset the DEFAULT_TOP_SITES; 161 DEFAULT_TOP_SITES.length = 0; 162 163 info("refreshDefaults should add defaults on default.sites PREF_CHANGED"); 164 feed.onAction({ 165 type: actionTypes.PREF_CHANGED, 166 data: { name: "default.sites", value: "https://foo.com" }, 167 }); 168 169 Assert.equal( 170 DEFAULT_TOP_SITES.length, 171 1, 172 "Should have 1 DEFAULT_TOP_SITES now." 173 ); 174 175 // Reset the DEFAULT_TOP_SITES; 176 DEFAULT_TOP_SITES.length = 0; 177 178 info("refreshDefaults should refresh on topSiteRows PREF_CHANGED"); 179 let refreshStub = sandbox.stub(feed, "refresh"); 180 feed.onAction({ 181 type: actionTypes.PREF_CHANGED, 182 data: { name: "topSitesRows" }, 183 }); 184 Assert.ok(feed.refresh.calledOnce, "refresh called"); 185 refreshStub.restore(); 186 187 // Reset the DEFAULT_TOP_SITES; 188 DEFAULT_TOP_SITES.length = 0; 189 190 info("refreshDefaults should have default sites with .isDefault = true"); 191 feed.refreshDefaults("https://foo.com"); 192 Assert.equal( 193 DEFAULT_TOP_SITES.length, 194 1, 195 "Should have a DEFAULT_TOP_SITES now." 196 ); 197 Assert.ok( 198 DEFAULT_TOP_SITES[0].isDefault, 199 "Lone top site should be the default." 200 ); 201 202 // Reset the DEFAULT_TOP_SITES; 203 DEFAULT_TOP_SITES.length = 0; 204 205 info("refreshDefaults should have default sites with appropriate hostname"); 206 feed.refreshDefaults("https://foo.com"); 207 Assert.equal( 208 DEFAULT_TOP_SITES.length, 209 1, 210 "Should have a DEFAULT_TOP_SITES now." 211 ); 212 let [site] = DEFAULT_TOP_SITES; 213 Assert.equal( 214 site.hostname, 215 NewTabUtils.shortURL(site), 216 "Lone top site should have the right hostname." 217 ); 218 219 // Reset the DEFAULT_TOP_SITES; 220 DEFAULT_TOP_SITES.length = 0; 221 222 info("refreshDefaults should add no defaults on empty pref"); 223 feed.refreshDefaults(""); 224 Assert.equal( 225 DEFAULT_TOP_SITES.length, 226 0, 227 "Should have 0 DEFAULT_TOP_SITES now." 228 ); 229 230 info("refreshDefaults should be able to clear defaults"); 231 feed.refreshDefaults("https://foo.com"); 232 feed.refreshDefaults(""); 233 234 Assert.equal( 235 DEFAULT_TOP_SITES.length, 236 0, 237 "Should have 0 DEFAULT_TOP_SITES now." 238 ); 239 240 sandbox.restore(); 241 }); 242 243 add_task(async function test_filterForThumbnailExpiration() { 244 let sandbox = sinon.createSandbox(); 245 let feed = getTopSitesFeedForTest(sandbox); 246 247 info( 248 "filterForThumbnailExpiration should pass rows.urls to the callback provided" 249 ); 250 const rows = [ 251 { url: "foo.com" }, 252 { url: "bar.com", customScreenshotURL: "custom" }, 253 ]; 254 feed.store.state.TopSites = { rows }; 255 const stub = sandbox.stub(); 256 feed.filterForThumbnailExpiration(stub); 257 Assert.ok(stub.calledOnce); 258 Assert.ok(stub.calledWithExactly(["foo.com", "bar.com", "custom"])); 259 260 sandbox.restore(); 261 }); 262 263 add_task( 264 async function test_getLinksWithDefaults_on_SearchService_init_failure() { 265 let sandbox = sinon.createSandbox(); 266 let feed = getTopSitesFeedForTest(sandbox); 267 268 feed.refreshDefaults("https://foo.com"); 269 270 gSearchServiceInitStub.rejects(new Error("Simulating search init failure")); 271 272 const result = await feed.getLinksWithDefaults(); 273 Assert.ok(result); 274 275 gSearchServiceInitStub.resolves(); 276 277 sandbox.restore(); 278 } 279 ); 280 281 add_task(async function test_getLinksWithDefaults() { 282 NewTabUtils.activityStreamLinks.getTopSites.resetHistory(); 283 284 let sandbox = sinon.createSandbox(); 285 let feed = getTopSitesFeedForTest(sandbox); 286 287 feed.refreshDefaults("https://foo.com"); 288 289 info("getLinksWithDefaults should get the links from NewTabUtils"); 290 let result = await feed.getLinksWithDefaults(); 291 292 const reference = FAKE_LINKS.map(site => 293 Object.assign({}, site, { 294 hostname: NewTabUtils.shortURL(site), 295 typedBonus: true, 296 }) 297 ); 298 299 Assert.deepEqual(result, reference); 300 Assert.ok(NewTabUtils.activityStreamLinks.getTopSites.calledOnce); 301 302 info("getLinksWithDefaults should indicate the links get typed bonus"); 303 Assert.ok(result[0].typedBonus, "Expected typed bonus property to be true."); 304 305 sandbox.restore(); 306 }); 307 308 add_task(async function test_getLinksWithDefaults_filterAdult() { 309 let sandbox = sinon.createSandbox(); 310 info("getLinksWithDefaults should filter out non-pinned adult sites"); 311 312 sandbox.stub(FilterAdult, "filter").returns([]); 313 const TEST_URL = "https://foo.com/"; 314 sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [{ url: TEST_URL }]); 315 316 let feed = getTopSitesFeedForTest(sandbox); 317 feed.refreshDefaults("https://foo.com"); 318 319 const result = await feed.getLinksWithDefaults(); 320 Assert.ok(FilterAdult.filter.calledOnce); 321 Assert.equal(result.length, 1); 322 Assert.equal(result[0].url, TEST_URL); 323 324 sandbox.restore(); 325 }); 326 327 add_task(async function test_getLinksWithDefaults_caching() { 328 let sandbox = sinon.createSandbox(); 329 330 info( 331 "getLinksWithDefaults should filter out the defaults that have been blocked" 332 ); 333 // make sure we only have one top site, and we block the only default site we have to show 334 const url = "www.myonlytopsite.com"; 335 const topsite = { 336 frecency: FAKE_FRECENCY, 337 hostname: NewTabUtils.shortURL({ url }), 338 typedBonus: true, 339 url, 340 }; 341 342 const blockedDefaultSite = { url: "https://foo.com" }; 343 gGetTopSitesStub.resolves([topsite]); 344 sandbox.stub(NewTabUtils.blockedLinks, "isBlocked").callsFake(site => { 345 return site.url === blockedDefaultSite.url; 346 }); 347 348 let feed = getTopSitesFeedForTest(sandbox); 349 feed.refreshDefaults("https://foo.com"); 350 const result = await feed.getLinksWithDefaults(); 351 352 // what we should be left with is just the top site we added, and not the default site we blocked 353 Assert.equal(result.length, 1); 354 Assert.deepEqual(result[0], topsite); 355 let foundBlocked = result.find(site => site.url === blockedDefaultSite.url); 356 Assert.ok(!foundBlocked, "Should not have found blocked site."); 357 358 gGetTopSitesStub.resolves(FAKE_LINKS); 359 sandbox.restore(); 360 }); 361 362 add_task(async function test_getLinksWithDefaults_dedupe() { 363 let sandbox = sinon.createSandbox(); 364 365 info("getLinksWithDefaults should call dedupe.group on the links"); 366 let feed = getTopSitesFeedForTest(sandbox); 367 feed.refreshDefaults("https://foo.com"); 368 369 let stub = sandbox.stub(feed.dedupe, "group").callsFake((...id) => id); 370 await feed.getLinksWithDefaults(); 371 372 Assert.ok(stub.calledOnce, "dedupe.group was called once"); 373 sandbox.restore(); 374 }); 375 376 add_task(async function test__dedupe_key() { 377 let sandbox = sinon.createSandbox(); 378 379 info("_dedupeKey should dedupe on hostname instead of url"); 380 let feed = getTopSitesFeedForTest(sandbox); 381 feed.refreshDefaults("https://foo.com"); 382 383 let site = { url: "foo", hostname: "bar" }; 384 let result = feed._dedupeKey(site); 385 386 Assert.equal(result, site.hostname, "deduped on hostname"); 387 sandbox.restore(); 388 }); 389 390 add_task(async function test_getLinksWithDefaults_adds_defaults() { 391 let sandbox = sinon.createSandbox(); 392 393 info( 394 "getLinksWithDefaults should add defaults if there are are not enough links" 395 ); 396 const TEST_LINKS = [{ frecency: FAKE_FRECENCY, url: "foo.com" }]; 397 gGetTopSitesStub.resolves(TEST_LINKS); 398 let feed = getTopSitesFeedForTest(sandbox); 399 feed.refreshDefaults("https://foo.com"); 400 401 let result = await feed.getLinksWithDefaults(); 402 403 let reference = [...TEST_LINKS, ...DEFAULT_TOP_SITES].map(s => 404 Object.assign({}, s, { 405 hostname: NewTabUtils.shortURL(s), 406 typedBonus: true, 407 }) 408 ); 409 410 Assert.deepEqual(result, reference); 411 412 gGetTopSitesStub.resolves(FAKE_LINKS); 413 sandbox.restore(); 414 }); 415 416 add_task( 417 async function test_getLinksWithDefaults_adds_defaults_for_visible_slots() { 418 let sandbox = sinon.createSandbox(); 419 420 info( 421 "getLinksWithDefaults should only add defaults up to the number of visible slots" 422 ); 423 const numVisible = TOP_SITES_DEFAULT_ROWS * TOP_SITES_MAX_SITES_PER_ROW; 424 let testLinks = []; 425 for (let i = 0; i < numVisible - 1; i++) { 426 testLinks.push({ frecency: FAKE_FRECENCY, url: `foo${i}.com` }); 427 } 428 gGetTopSitesStub.resolves(testLinks); 429 430 let feed = getTopSitesFeedForTest(sandbox); 431 feed.refreshDefaults("https://foo.com"); 432 433 let result = await feed.getLinksWithDefaults(); 434 435 let reference = [...testLinks, DEFAULT_TOP_SITES[0]].map(s => 436 Object.assign({}, s, { 437 hostname: NewTabUtils.shortURL(s), 438 typedBonus: true, 439 }) 440 ); 441 442 Assert.equal(result.length, numVisible); 443 Assert.deepEqual(result, reference); 444 445 gGetTopSitesStub.resolves(FAKE_LINKS); 446 sandbox.restore(); 447 } 448 ); 449 450 add_task(async function test_getLinksWithDefaults_no_throw_on_no_links() { 451 let sandbox = sinon.createSandbox(); 452 453 info("getLinksWithDefaults should not throw if NewTabUtils returns null"); 454 gGetTopSitesStub.resolves(null); 455 456 let feed = getTopSitesFeedForTest(sandbox); 457 feed.refreshDefaults("https://foo.com"); 458 459 feed.getLinksWithDefaults(); 460 Assert.ok(true, "getLinksWithDefaults did not throw"); 461 462 gGetTopSitesStub.resolves(FAKE_LINKS); 463 sandbox.restore(); 464 }); 465 466 add_task(async function test_getLinksWithDefaults_get_more_on_request() { 467 let sandbox = sinon.createSandbox(); 468 469 info("getLinksWithDefaults should get more if the user has asked for more"); 470 let testLinks = new Array(4 * TOP_SITES_MAX_SITES_PER_ROW) 471 .fill(null) 472 .map((v, i) => ({ 473 frecency: FAKE_FRECENCY, 474 url: `http://www.site${i}.com`, 475 })); 476 gGetTopSitesStub.resolves(testLinks); 477 478 let feed = getTopSitesFeedForTest(sandbox); 479 feed.refreshDefaults("https://foo.com"); 480 481 const TEST_ROWS = 3; 482 feed.store.state.Prefs.values.topSitesRows = TEST_ROWS; 483 484 let result = await feed.getLinksWithDefaults(); 485 Assert.equal(result.length, TEST_ROWS * TOP_SITES_MAX_SITES_PER_ROW); 486 487 gGetTopSitesStub.resolves(FAKE_LINKS); 488 sandbox.restore(); 489 }); 490 491 add_task(async function test_getLinksWithDefaults_reuse_cache() { 492 let sandbox = sinon.createSandbox(); 493 info("getLinksWithDefaults should reuse the cache on subsequent calls"); 494 495 let feed = getTopSitesFeedForTest(sandbox); 496 feed.refreshDefaults("https://foo.com"); 497 498 gGetTopSitesStub.resetHistory(); 499 500 await feed.getLinksWithDefaults(); 501 await feed.getLinksWithDefaults(); 502 503 Assert.ok( 504 NewTabUtils.activityStreamLinks.getTopSites.calledOnce, 505 "getTopSites only called once" 506 ); 507 508 sandbox.restore(); 509 }); 510 511 add_task( 512 async function test_getLinksWithDefaults_ignore_cache_on_requesting_more() { 513 let sandbox = sinon.createSandbox(); 514 info("getLinksWithDefaults should ignore the cache when requesting more"); 515 516 let feed = getTopSitesFeedForTest(sandbox); 517 feed.refreshDefaults("https://foo.com"); 518 519 gGetTopSitesStub.resetHistory(); 520 521 await feed.getLinksWithDefaults(); 522 feed.store.state.Prefs.values.topSitesRows *= 100; 523 await feed.getLinksWithDefaults(); 524 525 Assert.ok( 526 NewTabUtils.activityStreamLinks.getTopSites.calledTwice, 527 "getTopSites called twice" 528 ); 529 530 sandbox.restore(); 531 } 532 ); 533 534 add_task( 535 async function test_getLinksWithDefaults_migrate_frecent_screenshot_data() { 536 let sandbox = sinon.createSandbox(); 537 info( 538 "getLinksWithDefaults should migrate frecent screenshot data without getting screenshots again" 539 ); 540 541 let feed = getTopSitesFeedForTest(sandbox); 542 feed.refreshDefaults("https://foo.com"); 543 544 gGetTopSitesStub.resetHistory(); 545 546 feed.store.state.Prefs.values[SHOWN_ON_NEWTAB_PREF] = true; 547 await feed.getLinksWithDefaults(); 548 549 let originalCallCount = Screenshots.getScreenshotForURL.callCount; 550 feed.frecentCache.expire(); 551 552 let result = await feed.getLinksWithDefaults(); 553 554 Assert.ok( 555 NewTabUtils.activityStreamLinks.getTopSites.calledTwice, 556 "getTopSites called twice" 557 ); 558 Assert.equal( 559 Screenshots.getScreenshotForURL.callCount, 560 originalCallCount, 561 "getScreenshotForURL was not called again." 562 ); 563 Assert.equal(result[0].screenshot, FAKE_SCREENSHOT); 564 565 sandbox.restore(); 566 } 567 ); 568 569 add_task( 570 async function test_getLinksWithDefaults_migrate_pinned_favicon_data() { 571 let sandbox = sinon.createSandbox(); 572 info( 573 "getLinksWithDefaults should migrate pinned favicon data without getting favicons again" 574 ); 575 576 let feed = getTopSitesFeedForTest(sandbox); 577 feed.refreshDefaults("https://foo.com"); 578 579 gGetTopSitesStub.resetHistory(); 580 581 sandbox 582 .stub(NewTabUtils.pinnedLinks, "links") 583 .get(() => [{ url: "https://foo.com/" }]); 584 585 await feed.getLinksWithDefaults(); 586 587 let originalCallCount = 588 NewTabUtils.activityStreamProvider._addFavicons.callCount; 589 feed.pinnedCache.expire(); 590 591 let result = await feed.getLinksWithDefaults(); 592 593 Assert.equal( 594 NewTabUtils.activityStreamProvider._addFavicons.callCount, 595 originalCallCount, 596 "_addFavicons was not called again." 597 ); 598 Assert.equal(result[0].favicon, FAKE_FAVICON); 599 Assert.equal(result[0].faviconSize, FAKE_FAVICON_SIZE); 600 601 sandbox.restore(); 602 } 603 ); 604 605 add_task(async function test_getLinksWithDefaults_no_internal_properties() { 606 let sandbox = sinon.createSandbox(); 607 info("getLinksWithDefaults should not expose internal link properties"); 608 609 let feed = getTopSitesFeedForTest(sandbox); 610 feed.refreshDefaults("https://foo.com"); 611 612 let result = await feed.getLinksWithDefaults(); 613 614 let internal = Object.keys(result[0]).filter(key => key.startsWith("__")); 615 Assert.equal(internal.join(""), ""); 616 617 sandbox.restore(); 618 }); 619 620 add_task(async function test_getLinksWithDefaults_copy_frecent_screenshot() { 621 let sandbox = sinon.createSandbox(); 622 info( 623 "getLinksWithDefaults should copy the screenshot of the frecent site if " + 624 "pinned site doesn't have customScreenshotURL" 625 ); 626 627 let feed = getTopSitesFeedForTest(sandbox); 628 feed.refreshDefaults("https://foo.com"); 629 630 const TEST_SCREENSHOT = "screenshot"; 631 632 gGetTopSitesStub.resolves([ 633 { url: "https://foo.com/", screenshot: TEST_SCREENSHOT }, 634 ]); 635 sandbox 636 .stub(NewTabUtils.pinnedLinks, "links") 637 .get(() => [{ url: "https://foo.com/" }]); 638 639 let result = await feed.getLinksWithDefaults(); 640 641 Assert.equal(result[0].screenshot, TEST_SCREENSHOT); 642 643 gGetTopSitesStub.resolves(FAKE_LINKS); 644 sandbox.restore(); 645 }); 646 647 add_task(async function test_getLinksWithDefaults_no_copy_frecent_screenshot() { 648 let sandbox = sinon.createSandbox(); 649 info( 650 "getLinksWithDefaults should not copy the frecent screenshot if " + 651 "customScreenshotURL is set" 652 ); 653 654 let feed = getTopSitesFeedForTest(sandbox); 655 feed.refreshDefaults("https://foo.com"); 656 657 gGetTopSitesStub.resolves([ 658 { url: "https://foo.com/", screenshot: "screenshot" }, 659 ]); 660 sandbox 661 .stub(NewTabUtils.pinnedLinks, "links") 662 .get(() => [{ url: "https://foo.com/", customScreenshotURL: "custom" }]); 663 664 let result = await feed.getLinksWithDefaults(); 665 666 Assert.equal(result[0].screenshot, undefined); 667 668 gGetTopSitesStub.resolves(FAKE_LINKS); 669 sandbox.restore(); 670 }); 671 672 add_task(async function test_getLinksWithDefaults_persist_screenshot() { 673 let sandbox = sinon.createSandbox(); 674 info( 675 "getLinksWithDefaults should keep the same screenshot if no frecent site is found" 676 ); 677 678 let feed = getTopSitesFeedForTest(sandbox); 679 feed.refreshDefaults("https://foo.com"); 680 681 const CUSTOM_SCREENSHOT = "custom"; 682 683 gGetTopSitesStub.resolves([]); 684 sandbox 685 .stub(NewTabUtils.pinnedLinks, "links") 686 .get(() => [{ url: "https://foo.com/", screenshot: CUSTOM_SCREENSHOT }]); 687 688 let result = await feed.getLinksWithDefaults(); 689 690 Assert.equal(result[0].screenshot, CUSTOM_SCREENSHOT); 691 692 gGetTopSitesStub.resolves(FAKE_LINKS); 693 sandbox.restore(); 694 }); 695 696 add_task( 697 async function test_getLinksWithDefaults_no_overwrite_pinned_screenshot() { 698 let sandbox = sinon.createSandbox(); 699 info("getLinksWithDefaults should not overwrite pinned site screenshot"); 700 701 let feed = getTopSitesFeedForTest(sandbox); 702 feed.refreshDefaults("https://foo.com"); 703 704 const EXISTING_SCREENSHOT = "some-screenshot"; 705 706 gGetTopSitesStub.resolves([{ url: "https://foo.com/", screenshot: "foo" }]); 707 sandbox 708 .stub(NewTabUtils.pinnedLinks, "links") 709 .get(() => [ 710 { url: "https://foo.com/", screenshot: EXISTING_SCREENSHOT }, 711 ]); 712 713 let result = await feed.getLinksWithDefaults(); 714 715 Assert.equal(result[0].screenshot, EXISTING_SCREENSHOT); 716 717 gGetTopSitesStub.resolves(FAKE_LINKS); 718 sandbox.restore(); 719 } 720 ); 721 722 add_task( 723 async function test_getLinksWithDefaults_no_searchTopSite_from_frecent() { 724 let sandbox = sinon.createSandbox(); 725 info("getLinksWithDefaults should not set searchTopSite from frecent site"); 726 727 let feed = getTopSitesFeedForTest(sandbox); 728 feed.refreshDefaults("https://foo.com"); 729 730 const EXISTING_SCREENSHOT = "some-screenshot"; 731 732 gGetTopSitesStub.resolves([ 733 { 734 url: "https://foo.com/", 735 searchTopSite: true, 736 screenshot: EXISTING_SCREENSHOT, 737 }, 738 ]); 739 sandbox 740 .stub(NewTabUtils.pinnedLinks, "links") 741 .get(() => [{ url: "https://foo.com/" }]); 742 743 let result = await feed.getLinksWithDefaults(); 744 745 Assert.ok(!result[0].searchTopSite); 746 // But it should copy over other properties 747 Assert.equal(result[0].screenshot, EXISTING_SCREENSHOT); 748 749 gGetTopSitesStub.resolves(FAKE_LINKS); 750 sandbox.restore(); 751 } 752 ); 753 754 add_task(async function test_getLinksWithDefaults_concurrency_getTopSites() { 755 let sandbox = sinon.createSandbox(); 756 info( 757 "getLinksWithDefaults concurrent calls should call the backing data once" 758 ); 759 760 let feed = getTopSitesFeedForTest(sandbox); 761 feed.refreshDefaults("https://foo.com"); 762 763 NewTabUtils.activityStreamLinks.getTopSites.resetHistory(); 764 765 await Promise.all([feed.getLinksWithDefaults(), feed.getLinksWithDefaults()]); 766 767 Assert.ok( 768 NewTabUtils.activityStreamLinks.getTopSites.calledOnce, 769 "getTopSites only called once" 770 ); 771 772 sandbox.restore(); 773 }); 774 775 add_task( 776 async function test_getLinksWithDefaults_concurrency_getScreenshotForURL() { 777 let sandbox = sinon.createSandbox(); 778 info( 779 "getLinksWithDefaults concurrent calls should call the backing data once" 780 ); 781 782 let feed = getTopSitesFeedForTest(sandbox); 783 feed.refreshDefaults("https://foo.com"); 784 feed.store.state.Prefs.values[SHOWN_ON_NEWTAB_PREF] = true; 785 786 NewTabUtils.activityStreamLinks.getTopSites.resetHistory(); 787 Screenshots.getScreenshotForURL.resetHistory(); 788 789 await Promise.all([ 790 feed.getLinksWithDefaults(), 791 feed.getLinksWithDefaults(), 792 ]); 793 794 Assert.ok( 795 NewTabUtils.activityStreamLinks.getTopSites.calledOnce, 796 "getTopSites only called once" 797 ); 798 799 Assert.equal( 800 Screenshots.getScreenshotForURL.callCount, 801 FAKE_LINKS.length, 802 "getLinksWithDefaults concurrent calls should get screenshots once per link" 803 ); 804 805 sandbox.restore(); 806 feed = getTopSitesFeedForTest(sandbox); 807 feed.store.state.Prefs.values[SHOWN_ON_NEWTAB_PREF] = true; 808 809 feed.refreshDefaults("https://foo.com"); 810 811 sandbox.stub(feed, "_requestRichIcon"); 812 await Promise.all([ 813 feed.getLinksWithDefaults(), 814 feed.getLinksWithDefaults(), 815 ]); 816 817 Assert.equal( 818 feed.store.dispatch.callCount, 819 FAKE_LINKS.length, 820 "getLinksWithDefaults concurrent calls should dispatch once per link screenshot fetched" 821 ); 822 823 sandbox.restore(); 824 } 825 ); 826 827 add_task(async function test_getLinksWithDefaults_deduping_no_dedupe_pinned() { 828 let sandbox = sinon.createSandbox(); 829 info("getLinksWithDefaults should not dedupe pinned sites"); 830 831 let feed = getTopSitesFeedForTest(sandbox); 832 feed.refreshDefaults("https://foo.com"); 833 834 sandbox 835 .stub(NewTabUtils.pinnedLinks, "links") 836 .get(() => [ 837 { url: "https://developer.mozilla.org/en-US/docs/Web" }, 838 { url: "https://developer.mozilla.org/en-US/docs/Learn" }, 839 ]); 840 841 let sites = await feed.getLinksWithDefaults(); 842 Assert.equal(sites.length, 2 * TOP_SITES_MAX_SITES_PER_ROW); 843 Assert.equal(sites[0].url, NewTabUtils.pinnedLinks.links[0].url); 844 Assert.equal(sites[1].url, NewTabUtils.pinnedLinks.links[1].url); 845 Assert.equal(sites[0].hostname, sites[1].hostname); 846 847 sandbox.restore(); 848 }); 849 850 add_task(async function test_getLinksWithDefaults_prefer_pinned_sites() { 851 let sandbox = sinon.createSandbox(); 852 853 info("getLinksWithDefaults should prefer pinned sites over links"); 854 855 let feed = getTopSitesFeedForTest(sandbox); 856 feed.refreshDefaults(); 857 858 sandbox 859 .stub(NewTabUtils.pinnedLinks, "links") 860 .get(() => [ 861 { url: "https://developer.mozilla.org/en-US/docs/Web" }, 862 { url: "https://developer.mozilla.org/en-US/docs/Learn" }, 863 ]); 864 865 const SECOND_TOP_SITE_URL = "https://www.mozilla.org/"; 866 867 gGetTopSitesStub.resolves([ 868 { frecency: FAKE_FRECENCY, url: "https://developer.mozilla.org/" }, 869 { frecency: FAKE_FRECENCY, url: SECOND_TOP_SITE_URL }, 870 ]); 871 872 let sites = await feed.getLinksWithDefaults(); 873 874 // Expecting 3 links where there's 2 pinned and 1 www.mozilla.org, so 875 // the frecent with matching hostname as pinned is removed. 876 Assert.equal(sites.length, 3); 877 Assert.equal(sites[0].url, NewTabUtils.pinnedLinks.links[0].url); 878 Assert.equal(sites[1].url, NewTabUtils.pinnedLinks.links[1].url); 879 Assert.equal(sites[2].url, SECOND_TOP_SITE_URL); 880 881 gGetTopSitesStub.resolves(FAKE_LINKS); 882 sandbox.restore(); 883 }); 884 885 add_task(async function test_getLinksWithDefaults_title_and_null() { 886 let sandbox = sinon.createSandbox(); 887 888 info("getLinksWithDefaults should return sites that have a title"); 889 890 let feed = getTopSitesFeedForTest(sandbox); 891 feed.refreshDefaults(); 892 893 sandbox 894 .stub(NewTabUtils.pinnedLinks, "links") 895 .get(() => [{ url: "https://github.com/mozilla/activity-stream" }]); 896 897 let sites = await feed.getLinksWithDefaults(); 898 for (let site of sites) { 899 Assert.ok(site.hostname); 900 } 901 902 info("getLinksWithDefaults should not throw for null entries"); 903 sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [null]); 904 await feed.getLinksWithDefaults(); 905 Assert.ok(true, "getLinksWithDefaults didn't throw"); 906 907 sandbox.restore(); 908 }); 909 910 add_task(async function test_getLinksWithDefaults_calls__fetchIcon() { 911 let sandbox = sinon.createSandbox(); 912 913 info("getLinksWithDefaults should return sites that have a title"); 914 915 let feed = getTopSitesFeedForTest(sandbox); 916 feed.refreshDefaults(); 917 918 sandbox.spy(feed, "_fetchIcon"); 919 let results = await feed.getLinksWithDefaults(); 920 Assert.ok(results.length, "Got back some results"); 921 Assert.equal(feed._fetchIcon.callCount, results.length); 922 for (let result of results) { 923 Assert.ok(feed._fetchIcon.calledWith(result)); 924 } 925 926 sandbox.restore(); 927 }); 928 929 add_task(async function test_getLinksWithDefaults_calls__fetchScreenshot() { 930 let sandbox = sinon.createSandbox(); 931 932 info( 933 "getLinksWithDefaults should call _fetchScreenshot when customScreenshotURL is set" 934 ); 935 936 gGetTopSitesStub.resolves([]); 937 sandbox 938 .stub(NewTabUtils.pinnedLinks, "links") 939 .get(() => [{ url: "https://foo.com", customScreenshotURL: "custom" }]); 940 941 let feed = getTopSitesFeedForTest(sandbox); 942 feed.refreshDefaults(); 943 944 sandbox.stub(feed, "_fetchScreenshot"); 945 await feed.getLinksWithDefaults(); 946 947 Assert.ok(feed._fetchScreenshot.calledWith(sinon.match.object, "custom")); 948 949 gGetTopSitesStub.resolves(FAKE_LINKS); 950 sandbox.restore(); 951 }); 952 953 add_task(async function test_getLinksWithDefaults_with_DiscoveryStream() { 954 let sandbox = sinon.createSandbox(); 955 info( 956 "getLinksWithDefaults should add a sponsored topsite from discoverystream to all the valid indices" 957 ); 958 959 let makeStreamData = index => ({ 960 layout: [ 961 { 962 components: [ 963 { 964 placement: { 965 name: "sponsored-topsites", 966 }, 967 spocs: { 968 positions: [{ index }], 969 }, 970 }, 971 ], 972 }, 973 ], 974 spocs: { 975 data: { 976 "sponsored-topsites": { 977 items: [{ title: "test spoc", url: "https://test-spoc.com" }], 978 }, 979 }, 980 }, 981 }); 982 983 let feed = getTopSitesFeedForTest(sandbox); 984 feed.refreshDefaults(); 985 986 for (let i = 0; i < FAKE_LINKS.length; i++) { 987 feed.store.state.DiscoveryStream = makeStreamData(i); 988 const result = await feed.getLinksWithDefaults(); 989 const link = result[i]; 990 991 Assert.equal(link.type, "SPOC"); 992 Assert.equal(link.title, "test spoc"); 993 Assert.equal(link.sponsored_position, i + 1); 994 Assert.equal(link.hostname, "test-spoc"); 995 Assert.equal(link.url, "https://test-spoc.com"); 996 } 997 998 sandbox.restore(); 999 }); 1000 1001 add_task(async function test_init() { 1002 let sandbox = sinon.createSandbox(); 1003 1004 sandbox.stub(NimbusFeatures.newtab, "onUpdate"); 1005 1006 let feed = getTopSitesFeedForTest(sandbox); 1007 1008 sandbox.stub(feed, "refresh"); 1009 await feed.init(); 1010 1011 info("TopSitesFeed.init should call refresh (broadcast: true)"); 1012 Assert.ok(feed.refresh.calledOnce, "refresh called once"); 1013 Assert.ok( 1014 feed.refresh.calledWithExactly({ 1015 broadcast: true, 1016 isStartup: true, 1017 }) 1018 ); 1019 1020 info( 1021 "TopSitesFeed.init should call onUpdate to set up Nimbus update listener" 1022 ); 1023 1024 Assert.ok( 1025 NimbusFeatures.newtab.onUpdate.calledOnce, 1026 "NimbusFeatures.newtab.onUpdate called once" 1027 ); 1028 sandbox.restore(); 1029 }); 1030 1031 add_task(async function test_refresh() { 1032 let sandbox = sinon.createSandbox(); 1033 1034 sandbox.stub(NimbusFeatures.newtab, "onUpdate"); 1035 1036 let feed = getTopSitesFeedForTest(sandbox); 1037 1038 sandbox.stub(feed, "_fetchIcon"); 1039 feed._startedUp = true; 1040 1041 info("TopSitesFeed.refresh should wait for tippytop to initialize"); 1042 feed._tippyTopProvider.initialized = false; 1043 sandbox.stub(feed._tippyTopProvider, "init").resolves(); 1044 1045 await feed.refresh(); 1046 1047 Assert.ok( 1048 feed._tippyTopProvider.init.calledOnce, 1049 "feed._tippyTopProvider.init called once" 1050 ); 1051 1052 info( 1053 "TopSitesFeed.refresh should not init the tippyTopProvider if already initialized" 1054 ); 1055 feed._tippyTopProvider.initialized = true; 1056 feed._tippyTopProvider.init.resetHistory(); 1057 1058 await feed.refresh(); 1059 1060 Assert.ok( 1061 feed._tippyTopProvider.init.notCalled, 1062 "tippyTopProvider not initted again" 1063 ); 1064 1065 info("TopSitesFeed.refresh should broadcast TOP_SITES_UPDATED"); 1066 feed.store.dispatch.resetHistory(); 1067 sandbox.stub(feed, "getLinksWithDefaults").resolves([]); 1068 1069 await feed.refresh({ broadcast: true }); 1070 1071 Assert.ok(feed.store.dispatch.calledOnce, "dispatch called once"); 1072 Assert.ok( 1073 feed.store.dispatch.calledWithExactly( 1074 actionCreators.BroadcastToContent({ 1075 type: actionTypes.TOP_SITES_UPDATED, 1076 data: { links: [] }, 1077 }) 1078 ) 1079 ); 1080 1081 sandbox.restore(); 1082 }); 1083 1084 add_task(async function test_refresh_dispatch() { 1085 let sandbox = sinon.createSandbox(); 1086 1087 info( 1088 "TopSitesFeed.refresh should dispatch an action with the links returned" 1089 ); 1090 1091 let feed = getTopSitesFeedForTest(sandbox); 1092 sandbox.stub(feed, "_fetchIcon"); 1093 feed._startedUp = true; 1094 1095 await feed.refresh({ broadcast: true }); 1096 let reference = FAKE_LINKS.map(site => 1097 Object.assign({}, site, { 1098 hostname: NewTabUtils.shortURL(site), 1099 typedBonus: true, 1100 }) 1101 ); 1102 1103 Assert.ok(feed.store.dispatch.calledOnce, "Store.dispatch called once"); 1104 Assert.equal( 1105 feed.store.dispatch.firstCall.args[0].type, 1106 actionTypes.TOP_SITES_UPDATED 1107 ); 1108 Assert.deepEqual(feed.store.dispatch.firstCall.args[0].data.links, reference); 1109 1110 sandbox.restore(); 1111 }); 1112 1113 add_task(async function test_refresh_empty_slots() { 1114 let sandbox = sinon.createSandbox(); 1115 1116 info( 1117 "TopSitesFeed.refresh should handle empty slots in the resulting top sites array" 1118 ); 1119 1120 let feed = getTopSitesFeedForTest(sandbox); 1121 sandbox.stub(feed, "_fetchIcon"); 1122 feed._startedUp = true; 1123 1124 gGetTopSitesStub.resolves([FAKE_LINKS[0]]); 1125 sandbox 1126 .stub(NewTabUtils.pinnedLinks, "links") 1127 .get(() => [ 1128 null, 1129 null, 1130 FAKE_LINKS[1], 1131 null, 1132 null, 1133 null, 1134 null, 1135 null, 1136 FAKE_LINKS[2], 1137 ]); 1138 1139 await feed.refresh({ broadcast: true }); 1140 1141 Assert.ok(feed.store.dispatch.calledOnce, "Store.dispatch called once"); 1142 1143 gGetTopSitesStub.resolves(FAKE_LINKS); 1144 sandbox.restore(); 1145 }); 1146 1147 add_task(async function test_refresh_to_preloaded() { 1148 let sandbox = sinon.createSandbox(); 1149 1150 info( 1151 "TopSitesFeed.refresh should dispatch AlsoToPreloaded when broadcast is false" 1152 ); 1153 1154 let feed = getTopSitesFeedForTest(sandbox); 1155 sandbox.stub(feed, "_fetchIcon"); 1156 feed._startedUp = true; 1157 1158 gGetTopSitesStub.resolves([]); 1159 await feed.refresh({ broadcast: false }); 1160 1161 Assert.ok(feed.store.dispatch.calledOnce, "Store.dispatch called once"); 1162 Assert.ok( 1163 feed.store.dispatch.calledWithExactly( 1164 actionCreators.AlsoToPreloaded({ 1165 type: actionTypes.TOP_SITES_UPDATED, 1166 data: { links: [] }, 1167 }) 1168 ) 1169 ); 1170 gGetTopSitesStub.resolves(FAKE_LINKS); 1171 sandbox.restore(); 1172 }); 1173 1174 add_task(async function test_refresh_handles_indexedDB_errors() { 1175 let sandbox = sinon.createSandbox(); 1176 1177 info( 1178 "TopSitesFeed.refresh should dispatch AlsoToPreloaded when broadcast is false" 1179 ); 1180 1181 let feed = getTopSitesFeedForTest(sandbox); 1182 sandbox.stub(feed, "_fetchIcon"); 1183 feed._startedUp = true; 1184 1185 try { 1186 await feed.refresh({ broadcast: false }); 1187 Assert.ok(true, "refresh should have succeeded"); 1188 } catch (e) { 1189 Assert.ok(false, "Should not have thrown"); 1190 } 1191 1192 sandbox.restore(); 1193 }); 1194 1195 add_task(async function test_allocatePositions() { 1196 let sandbox = sinon.createSandbox(); 1197 1198 info( 1199 "TopSitesFeed.allocationPositions should allocate positions and dispatch" 1200 ); 1201 1202 let feed = getTopSitesFeedForTest(sandbox); 1203 1204 let sov = { 1205 name: "SOV-20230518215316", 1206 allocations: [ 1207 { 1208 position: 1, 1209 allocation: [ 1210 { 1211 partner: "amp", 1212 percentage: 100, 1213 }, 1214 { 1215 partner: "moz-sales", 1216 percentage: 0, 1217 }, 1218 ], 1219 }, 1220 { 1221 position: 2, 1222 allocation: [ 1223 { 1224 partner: "amp", 1225 percentage: 80, 1226 }, 1227 { 1228 partner: "moz-sales", 1229 percentage: 20, 1230 }, 1231 ], 1232 }, 1233 ], 1234 }; 1235 1236 sandbox.stub(feed._contile, "sov").get(() => sov); 1237 1238 sandbox.stub(Sampling, "ratioSample"); 1239 Sampling.ratioSample.onCall(0).resolves(0); 1240 Sampling.ratioSample.onCall(1).resolves(1); 1241 1242 await feed.allocatePositions(); 1243 1244 Assert.ok(feed.store.dispatch.calledOnce, "feed.store.dispatch called once"); 1245 Assert.ok( 1246 feed.store.dispatch.calledWithExactly( 1247 actionCreators.OnlyToMain({ 1248 type: actionTypes.SOV_UPDATED, 1249 data: { 1250 ready: true, 1251 positions: [ 1252 { position: 1, assignedPartner: "amp" }, 1253 { position: 2, assignedPartner: "moz-sales" }, 1254 ], 1255 }, 1256 }) 1257 ) 1258 ); 1259 1260 Sampling.ratioSample.onCall(2).resolves(0); 1261 Sampling.ratioSample.onCall(3).resolves(0); 1262 1263 await feed.allocatePositions(); 1264 1265 Assert.ok( 1266 feed.store.dispatch.calledTwice, 1267 "feed.store.dispatch called twice" 1268 ); 1269 Assert.ok( 1270 feed.store.dispatch.calledWithExactly( 1271 actionCreators.OnlyToMain({ 1272 type: actionTypes.SOV_UPDATED, 1273 data: { 1274 ready: true, 1275 positions: [ 1276 { position: 1, assignedPartner: "amp" }, 1277 { position: 2, assignedPartner: "amp" }, 1278 ], 1279 }, 1280 }) 1281 ) 1282 ); 1283 1284 sandbox.restore(); 1285 }); 1286 1287 add_task(async function test_getScreenshotPreview() { 1288 let sandbox = sinon.createSandbox(); 1289 1290 info( 1291 "TopSitesFeed.getScreenshotPreview should dispatch preview if request is succesful" 1292 ); 1293 1294 let feed = getTopSitesFeedForTest(sandbox); 1295 await feed.getScreenshotPreview("custom", 1234); 1296 1297 Assert.ok(feed.store.dispatch.calledOnce); 1298 Assert.ok( 1299 feed.store.dispatch.calledWithExactly( 1300 actionCreators.OnlyToOneContent( 1301 { 1302 data: { preview: FAKE_SCREENSHOT, url: "custom" }, 1303 type: actionTypes.PREVIEW_RESPONSE, 1304 }, 1305 1234 1306 ) 1307 ) 1308 ); 1309 1310 sandbox.restore(); 1311 }); 1312 1313 add_task(async function test_getScreenshotPreview() { 1314 let sandbox = sinon.createSandbox(); 1315 1316 info( 1317 "TopSitesFeed.getScreenshotPreview should return empty string if request fails" 1318 ); 1319 1320 let feed = getTopSitesFeedForTest(sandbox); 1321 Screenshots.getScreenshotForURL.resolves(Promise.resolve(null)); 1322 await feed.getScreenshotPreview("custom", 1234); 1323 1324 Assert.ok(feed.store.dispatch.calledOnce); 1325 Assert.ok( 1326 feed.store.dispatch.calledWithExactly( 1327 actionCreators.OnlyToOneContent( 1328 { 1329 data: { preview: "", url: "custom" }, 1330 type: actionTypes.PREVIEW_RESPONSE, 1331 }, 1332 1234 1333 ) 1334 ) 1335 ); 1336 1337 Screenshots.getScreenshotForURL.resolves(FAKE_SCREENSHOT); 1338 sandbox.restore(); 1339 }); 1340 1341 add_task(async function test_onAction_part_1() { 1342 let sandbox = sinon.createSandbox(); 1343 1344 info( 1345 "TopSitesFeed.onAction should call getScreenshotPreview on PREVIEW_REQUEST" 1346 ); 1347 1348 let feed = getTopSitesFeedForTest(sandbox); 1349 sandbox.stub(feed, "getScreenshotPreview"); 1350 1351 feed.onAction({ 1352 type: actionTypes.PREVIEW_REQUEST, 1353 data: { url: "foo" }, 1354 meta: { fromTarget: 1234 }, 1355 }); 1356 1357 Assert.ok( 1358 feed.getScreenshotPreview.calledOnce, 1359 "feed.getScreenshotPreview called once" 1360 ); 1361 Assert.ok(feed.getScreenshotPreview.calledWithExactly("foo", 1234)); 1362 1363 info("TopSitesFeed.onAction should refresh on SYSTEM_TICK"); 1364 sandbox.stub(feed, "refresh"); 1365 feed.onAction({ type: actionTypes.SYSTEM_TICK }); 1366 1367 Assert.ok(feed.refresh.calledOnce, "feed.refresh called once"); 1368 Assert.ok(feed.refresh.calledWithExactly({ broadcast: false })); 1369 1370 info( 1371 "TopSitesFeed.onAction should call with correct parameters on TOP_SITES_PIN" 1372 ); 1373 sandbox.stub(NewTabUtils.pinnedLinks, "pin"); 1374 sandbox.spy(feed, "pin"); 1375 1376 let pinAction = { 1377 type: actionTypes.TOP_SITES_PIN, 1378 data: { site: { url: "foo.com" }, index: 7 }, 1379 }; 1380 feed.onAction(pinAction); 1381 Assert.ok( 1382 NewTabUtils.pinnedLinks.pin.calledOnce, 1383 "NewTabUtils.pinnedLinks.pin called once" 1384 ); 1385 Assert.ok( 1386 NewTabUtils.pinnedLinks.pin.calledWithExactly( 1387 pinAction.data.site, 1388 pinAction.data.index 1389 ) 1390 ); 1391 Assert.ok( 1392 feed.pin.calledOnce, 1393 "TopSitesFeed.onAction should call pin on TOP_SITES_PIN" 1394 ); 1395 1396 info( 1397 "TopSitesFeed.onAction should unblock a previously blocked top site if " + 1398 "we are now adding it manually via 'Add a Top Site' option" 1399 ); 1400 sandbox.stub(NewTabUtils.blockedLinks, "unblock"); 1401 pinAction = { 1402 type: actionTypes.TOP_SITES_PIN, 1403 data: { site: { url: "foo.com" }, index: -1 }, 1404 }; 1405 feed.onAction(pinAction); 1406 Assert.ok( 1407 NewTabUtils.blockedLinks.unblock.calledWith({ 1408 url: pinAction.data.site.url, 1409 }) 1410 ); 1411 1412 info("TopSitesFeed.onAction should call insert on TOP_SITES_INSERT"); 1413 sandbox.stub(feed, "insert"); 1414 let addAction = { 1415 type: actionTypes.TOP_SITES_INSERT, 1416 data: { site: { url: "foo.com" } }, 1417 }; 1418 1419 feed.onAction(addAction); 1420 Assert.ok(feed.insert.calledOnce, "TopSitesFeed.insert called once"); 1421 1422 info( 1423 "TopSitesFeed.onAction should call unpin with correct parameters " + 1424 "on TOP_SITES_UNPIN" 1425 ); 1426 1427 sandbox 1428 .stub(NewTabUtils.pinnedLinks, "links") 1429 .get(() => [ 1430 null, 1431 null, 1432 { url: "foo.com" }, 1433 null, 1434 null, 1435 null, 1436 null, 1437 null, 1438 FAKE_LINKS[0], 1439 ]); 1440 sandbox.stub(NewTabUtils.pinnedLinks, "unpin"); 1441 1442 let unpinAction = { 1443 type: actionTypes.TOP_SITES_UNPIN, 1444 data: { site: { url: "foo.com" } }, 1445 }; 1446 feed.onAction(unpinAction); 1447 Assert.ok( 1448 NewTabUtils.pinnedLinks.unpin.calledOnce, 1449 "NewTabUtils.pinnedLinks.unpin called once" 1450 ); 1451 Assert.ok(NewTabUtils.pinnedLinks.unpin.calledWith(unpinAction.data.site)); 1452 1453 sandbox.restore(); 1454 }); 1455 1456 add_task(async function test_onAction_part_2() { 1457 let sandbox = sinon.createSandbox(); 1458 1459 info( 1460 "TopSitesFeed.onAction should call refresh without a target if we clear " + 1461 "history with PLACES_HISTORY_CLEARED" 1462 ); 1463 1464 let feed = getTopSitesFeedForTest(sandbox); 1465 sandbox.stub(feed, "refresh"); 1466 feed.onAction({ type: actionTypes.PLACES_HISTORY_CLEARED }); 1467 1468 Assert.ok(feed.refresh.calledOnce, "TopSitesFeed.refresh called once"); 1469 Assert.ok(feed.refresh.calledWithExactly({ broadcast: true })); 1470 1471 feed.refresh.resetHistory(); 1472 1473 info( 1474 "TopSitesFeed.onAction should call refresh without a target " + 1475 "if we remove a Topsite from history" 1476 ); 1477 feed.onAction({ type: actionTypes.PLACES_LINKS_DELETED }); 1478 1479 Assert.ok(feed.refresh.calledOnce, "TopSitesFeed.refresh called once"); 1480 Assert.ok(feed.refresh.calledWithExactly({ broadcast: true })); 1481 1482 info("TopSitesFeed.onAction should call init on INIT action"); 1483 feed.onAction({ type: actionTypes.PLACES_LINKS_DELETED }); 1484 sandbox.stub(feed, "init"); 1485 feed.onAction({ type: actionTypes.INIT }); 1486 Assert.ok(feed.init.calledOnce, "TopSitesFeed.init called once"); 1487 1488 info( 1489 "TopSitesFeed.onAction should call refresh on PLACES_LINK_BLOCKED action" 1490 ); 1491 feed.refresh.resetHistory(); 1492 await feed.onAction({ type: actionTypes.PLACES_LINK_BLOCKED }); 1493 Assert.ok(feed.refresh.calledOnce, "TopSitesFeed.refresh called once"); 1494 Assert.ok(feed.refresh.calledWithExactly({ broadcast: true })); 1495 1496 info( 1497 "TopSitesFeed.onAction should call refresh on PLACES_LINKS_CHANGED action" 1498 ); 1499 feed.refresh.resetHistory(); 1500 await feed.onAction({ type: actionTypes.PLACES_LINKS_CHANGED }); 1501 Assert.ok(feed.refresh.calledOnce, "TopSitesFeed.refresh called once"); 1502 Assert.ok(feed.refresh.calledWithExactly({ broadcast: false })); 1503 1504 info( 1505 "TopSitesFeed.onAction should call pin with correct args on " + 1506 "TOP_SITES_INSERT without an index specified" 1507 ); 1508 sandbox.stub(NewTabUtils.pinnedLinks, "pin"); 1509 1510 let addAction = { 1511 type: actionTypes.TOP_SITES_INSERT, 1512 data: { site: { url: "foo.bar", label: "foo" } }, 1513 }; 1514 feed.onAction(addAction); 1515 Assert.ok( 1516 NewTabUtils.pinnedLinks.pin.calledOnce, 1517 "NewTabUtils.pinnedLinks.pin called once" 1518 ); 1519 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(addAction.data.site, 0)); 1520 1521 info( 1522 "TopSitesFeed.onAction should call pin with correct args on " + 1523 "TOP_SITES_INSERT" 1524 ); 1525 NewTabUtils.pinnedLinks.pin.resetHistory(); 1526 let dropAction = { 1527 type: actionTypes.TOP_SITES_INSERT, 1528 data: { site: { url: "foo.bar", label: "foo" }, index: 3 }, 1529 }; 1530 feed.onAction(dropAction); 1531 Assert.ok( 1532 NewTabUtils.pinnedLinks.pin.calledOnce, 1533 "NewTabUtils.pinnedLinks.pin called once" 1534 ); 1535 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(dropAction.data.site, 3)); 1536 1537 // feed.init needs to actually run in order to register the observers that'll 1538 // be removed in the following UNINIT test, otherwise uninit will throw. 1539 feed.init.restore(); 1540 feed.init(); 1541 1542 info("TopSitesFeed.onAction should remove the expiration filter on UNINIT"); 1543 sandbox.stub(PageThumbs, "removeExpirationFilter"); 1544 feed.onAction({ type: "UNINIT" }); 1545 Assert.ok( 1546 PageThumbs.removeExpirationFilter.calledOnce, 1547 "PageThumbs.removeExpirationFilter called once" 1548 ); 1549 1550 sandbox.restore(); 1551 }); 1552 1553 add_task(async function test_onAction_part_3() { 1554 let sandbox = sinon.createSandbox(); 1555 1556 let feed = getTopSitesFeedForTest(sandbox); 1557 1558 info( 1559 "TopSitesFeed.onAction should call updatePinnedSearchShortcuts " + 1560 "on UPDATE_PINNED_SEARCH_SHORTCUTS action" 1561 ); 1562 sandbox.stub(feed, "updatePinnedSearchShortcuts"); 1563 let addedShortcuts = [ 1564 { 1565 url: "https://google.com", 1566 searchVendor: "google", 1567 label: "google", 1568 searchTopSite: true, 1569 }, 1570 ]; 1571 await feed.onAction({ 1572 type: actionTypes.UPDATE_PINNED_SEARCH_SHORTCUTS, 1573 data: { addedShortcuts }, 1574 }); 1575 Assert.ok( 1576 feed.updatePinnedSearchShortcuts.calledOnce, 1577 "TopSitesFeed.updatePinnedSearchShortcuts called once" 1578 ); 1579 1580 info( 1581 "TopSitesFeed.onAction should refresh from Contile on " + 1582 "SHOW_SPONSORED_PREF if Contile is enabled" 1583 ); 1584 sandbox.spy(feed._contile, "refresh"); 1585 let prefChangeAction = { 1586 type: actionTypes.PREF_CHANGED, 1587 data: { name: SHOW_SPONSORED_PREF }, 1588 }; 1589 sandbox.stub(NimbusFeatures.newtab, "getVariable").returns(true); 1590 feed.onAction(prefChangeAction); 1591 1592 Assert.ok( 1593 feed._contile.refresh.calledOnce, 1594 "TopSitesFeed._contile.refresh called once" 1595 ); 1596 1597 info( 1598 "TopSitesFeed.onAction should not refresh from Contile on " + 1599 "SHOW_SPONSORED_PREF if Contile is disabled" 1600 ); 1601 NimbusFeatures.newtab.getVariable.returns(false); 1602 feed._contile.refresh.resetHistory(); 1603 feed.onAction(prefChangeAction); 1604 1605 Assert.ok( 1606 !feed._contile.refresh.calledOnce, 1607 "TopSitesFeed._contile.refresh never called" 1608 ); 1609 1610 info( 1611 "TopSitesFeed.onAction should reset Contile cache prefs " + 1612 "when SHOW_SPONSORED_PREF is false" 1613 ); 1614 feed._contile.cache.get.returns({ contile: [] }); 1615 Services.prefs.setIntPref( 1616 CONTILE_CACHE_LAST_FETCH_PREF, 1617 Math.round(Date.now() / 1000) 1618 ); 1619 Services.prefs.setIntPref(CONTILE_CACHE_VALID_FOR_SECONDS_PREF, 15 * 60); 1620 prefChangeAction = { 1621 type: actionTypes.PREF_CHANGED, 1622 data: { name: SHOW_SPONSORED_PREF, value: false }, 1623 }; 1624 NimbusFeatures.newtab.getVariable.returns(true); 1625 feed._contile.refresh.resetHistory(); 1626 1627 feed.onAction(prefChangeAction); 1628 Assert.ok(feed._contile.cache.set.calledWith("contile", [])); 1629 Assert.ok(!Services.prefs.prefHasUserValue(CONTILE_CACHE_LAST_FETCH_PREF)); 1630 Assert.ok( 1631 !Services.prefs.prefHasUserValue(CONTILE_CACHE_VALID_FOR_SECONDS_PREF) 1632 ); 1633 1634 sandbox.restore(); 1635 }); 1636 1637 add_task(async function test_insert_part_1() { 1638 let sandbox = sinon.createSandbox(); 1639 1640 let prepFeed = feed => { 1641 sandbox.stub(NewTabUtils.pinnedLinks, "pin"); 1642 return feed; 1643 }; 1644 1645 { 1646 info( 1647 "TopSitesFeed.insert should pin site in first slot of empty pinned list" 1648 ); 1649 1650 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 1651 Screenshots.getScreenshotForURL.resolves(Promise.resolve(null)); 1652 await feed.getScreenshotPreview("custom", 1234); 1653 1654 Assert.ok(feed.store.dispatch.calledOnce); 1655 Assert.ok( 1656 feed.store.dispatch.calledWithExactly( 1657 actionCreators.OnlyToOneContent( 1658 { 1659 data: { preview: "", url: "custom" }, 1660 type: actionTypes.PREVIEW_RESPONSE, 1661 }, 1662 1234 1663 ) 1664 ) 1665 ); 1666 1667 Screenshots.getScreenshotForURL.resolves(FAKE_SCREENSHOT); 1668 sandbox.restore(); 1669 } 1670 1671 { 1672 info( 1673 "TopSitesFeed.insert should pin site in first slot of pinned list with " + 1674 "empty first slot" 1675 ); 1676 1677 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 1678 sandbox 1679 .stub(NewTabUtils.pinnedLinks, "links") 1680 .get(() => [null, { url: "example.com" }]); 1681 let site = { url: "foo.bar", label: "foo" }; 1682 await feed.insert({ data: { site } }); 1683 Assert.ok( 1684 NewTabUtils.pinnedLinks.pin.calledOnce, 1685 "NewTabUtils.pinnedLinks.pin called once" 1686 ); 1687 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 0)); 1688 NewTabUtils.pinnedLinks.pin.resetHistory(); 1689 sandbox.restore(); 1690 } 1691 1692 { 1693 info( 1694 "TopSitesFeed.insert should move a pinned site in first slot to the " + 1695 "next slot: part 1" 1696 ); 1697 let site1 = { url: "example.com" }; 1698 sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [site1]); 1699 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 1700 let site = { url: "foo.bar", label: "foo" }; 1701 1702 await feed.insert({ data: { site } }); 1703 Assert.ok( 1704 NewTabUtils.pinnedLinks.pin.calledTwice, 1705 "NewTabUtils.pinnedLinks.pin called twice" 1706 ); 1707 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 0)); 1708 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site1, 1)); 1709 NewTabUtils.pinnedLinks.pin.resetHistory(); 1710 sandbox.restore(); 1711 } 1712 1713 { 1714 info( 1715 "TopSitesFeed.insert should move a pinned site in first slot to the " + 1716 "next slot: part 2" 1717 ); 1718 let site1 = { url: "example.com" }; 1719 let site2 = { url: "example.org" }; 1720 sandbox 1721 .stub(NewTabUtils.pinnedLinks, "links") 1722 .get(() => [site1, null, site2]); 1723 let site = { url: "foo.bar", label: "foo" }; 1724 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 1725 await feed.insert({ data: { site } }); 1726 Assert.ok( 1727 NewTabUtils.pinnedLinks.pin.calledTwice, 1728 "NewTabUtils.pinnedLinks.pin called twice" 1729 ); 1730 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 0)); 1731 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site1, 1)); 1732 NewTabUtils.pinnedLinks.pin.resetHistory(); 1733 sandbox.restore(); 1734 } 1735 }); 1736 1737 add_task(async function test_insert_part_2() { 1738 let sandbox = sinon.createSandbox(); 1739 1740 let prepFeed = feed => { 1741 sandbox.stub(NewTabUtils.pinnedLinks, "pin"); 1742 return feed; 1743 }; 1744 1745 { 1746 info( 1747 "TopSitesFeed.insert should unpin the last site if all slots are " + 1748 "already pinned" 1749 ); 1750 let site1 = { url: "example.com" }; 1751 let site2 = { url: "example.org" }; 1752 let site3 = { url: "example.net" }; 1753 let site4 = { url: "example.biz" }; 1754 let site5 = { url: "example.info" }; 1755 let site6 = { url: "example.news" }; 1756 let site7 = { url: "example.lol" }; 1757 let site8 = { url: "example.golf" }; 1758 sandbox 1759 .stub(NewTabUtils.pinnedLinks, "links") 1760 .get(() => [site1, site2, site3, site4, site5, site6, site7, site8]); 1761 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 1762 feed.store.state.Prefs.values.topSitesRows = 1; 1763 let site = { url: "foo.bar", label: "foo" }; 1764 await feed.insert({ data: { site } }); 1765 Assert.equal( 1766 NewTabUtils.pinnedLinks.pin.callCount, 1767 8, 1768 "NewTabUtils.pinnedLinks.pin called 8 times" 1769 ); 1770 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 0)); 1771 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site1, 1)); 1772 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site2, 2)); 1773 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site3, 3)); 1774 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site4, 4)); 1775 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site5, 5)); 1776 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site6, 6)); 1777 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site7, 7)); 1778 NewTabUtils.pinnedLinks.pin.resetHistory(); 1779 sandbox.restore(); 1780 } 1781 1782 { 1783 info("TopSitesFeed.insert should trigger refresh on TOP_SITES_INSERT"); 1784 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 1785 sandbox.stub(feed, "refresh"); 1786 let addAction = { 1787 type: actionTypes.TOP_SITES_INSERT, 1788 data: { site: { url: "foo.com" } }, 1789 }; 1790 1791 await feed.insert(addAction); 1792 1793 Assert.ok(feed.refresh.calledOnce, "feed.refresh called once"); 1794 sandbox.restore(); 1795 } 1796 1797 { 1798 info("TopSitesFeed.insert should correctly handle different index values"); 1799 let index = -1; 1800 let site = { url: "foo.bar", label: "foo" }; 1801 let action = { data: { index, site } }; 1802 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 1803 1804 await feed.insert(action); 1805 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 0)); 1806 1807 index = undefined; 1808 await feed.insert(action); 1809 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 0)); 1810 1811 NewTabUtils.pinnedLinks.pin.resetHistory(); 1812 sandbox.restore(); 1813 } 1814 }); 1815 1816 add_task(async function test_insert_part_3() { 1817 let sandbox = sinon.createSandbox(); 1818 1819 let prepFeed = feed => { 1820 sandbox.stub(NewTabUtils.pinnedLinks, "pin"); 1821 return feed; 1822 }; 1823 1824 { 1825 info("TopSitesFeed.insert should pin site in specified slot that is free"); 1826 sandbox 1827 .stub(NewTabUtils.pinnedLinks, "links") 1828 .get(() => [null, { url: "example.com" }]); 1829 1830 let site = { url: "foo.bar", label: "foo" }; 1831 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 1832 1833 await feed.insert({ data: { index: 2, site, draggedFromIndex: 0 } }); 1834 Assert.ok( 1835 NewTabUtils.pinnedLinks.pin.calledOnce, 1836 "NewTabUtils.pinnedLinks.pin called once" 1837 ); 1838 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 2)); 1839 1840 NewTabUtils.pinnedLinks.pin.resetHistory(); 1841 sandbox.restore(); 1842 } 1843 1844 { 1845 info( 1846 "TopSitesFeed.insert should move a pinned site in specified slot " + 1847 "to the next slot" 1848 ); 1849 sandbox 1850 .stub(NewTabUtils.pinnedLinks, "links") 1851 .get(() => [null, null, { url: "example.com" }]); 1852 1853 let site = { url: "foo.bar", label: "foo" }; 1854 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 1855 1856 await feed.insert({ data: { index: 2, site, draggedFromIndex: 3 } }); 1857 Assert.ok( 1858 NewTabUtils.pinnedLinks.pin.calledTwice, 1859 "NewTabUtils.pinnedLinks.pin called twice" 1860 ); 1861 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 2)); 1862 Assert.ok( 1863 NewTabUtils.pinnedLinks.pin.calledWith({ url: "example.com" }, 3) 1864 ); 1865 1866 NewTabUtils.pinnedLinks.pin.resetHistory(); 1867 sandbox.restore(); 1868 } 1869 1870 { 1871 info( 1872 "TopSitesFeed.insert should move pinned sites in the direction " + 1873 "of the dragged site" 1874 ); 1875 1876 let site1 = { url: "foo.bar", label: "foo" }; 1877 let site2 = { url: "example.com", label: "example" }; 1878 sandbox 1879 .stub(NewTabUtils.pinnedLinks, "links") 1880 .get(() => [null, null, site2]); 1881 1882 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 1883 1884 await feed.insert({ data: { index: 2, site: site1, draggedFromIndex: 0 } }); 1885 Assert.ok( 1886 NewTabUtils.pinnedLinks.pin.calledTwice, 1887 "NewTabUtils.pinnedLinks.pin called twice" 1888 ); 1889 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site1, 2)); 1890 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site2, 1)); 1891 NewTabUtils.pinnedLinks.pin.resetHistory(); 1892 1893 await feed.insert({ data: { index: 2, site: site1, draggedFromIndex: 5 } }); 1894 Assert.ok( 1895 NewTabUtils.pinnedLinks.pin.calledTwice, 1896 "NewTabUtils.pinnedLinks.pin called twice" 1897 ); 1898 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site1, 2)); 1899 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site2, 3)); 1900 NewTabUtils.pinnedLinks.pin.resetHistory(); 1901 sandbox.restore(); 1902 } 1903 1904 { 1905 info("TopSitesFeed.insert should not insert past the visible top sites"); 1906 1907 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 1908 let site1 = { url: "foo.bar", label: "foo" }; 1909 await feed.insert({ 1910 data: { index: 42, site: site1, draggedFromIndex: 0 }, 1911 }); 1912 Assert.ok( 1913 NewTabUtils.pinnedLinks.pin.notCalled, 1914 "NewTabUtils.pinnedLinks.pin wasn't called" 1915 ); 1916 1917 NewTabUtils.pinnedLinks.pin.resetHistory(); 1918 sandbox.restore(); 1919 } 1920 }); 1921 1922 add_task(async function test_pin_part_1() { 1923 let sandbox = sinon.createSandbox(); 1924 1925 let prepFeed = feed => { 1926 sandbox.stub(NewTabUtils.pinnedLinks, "pin"); 1927 return feed; 1928 }; 1929 1930 { 1931 info( 1932 "TopSitesFeed.pin should pin site in specified slot empty pinned " + 1933 "list" 1934 ); 1935 let site = { 1936 url: "foo.bar", 1937 label: "foo", 1938 customScreenshotURL: "screenshot", 1939 }; 1940 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 1941 await feed.pin({ data: { index: 2, site } }); 1942 Assert.ok( 1943 NewTabUtils.pinnedLinks.pin.calledOnce, 1944 "NewTabUtils.pinnedLinks.pin called once" 1945 ); 1946 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 2)); 1947 NewTabUtils.pinnedLinks.pin.resetHistory(); 1948 sandbox.restore(); 1949 } 1950 1951 { 1952 info( 1953 "TopSitesFeed.pin should lookup the link object to update the custom " + 1954 "screenshot" 1955 ); 1956 let site = { 1957 url: "foo.bar", 1958 label: "foo", 1959 customScreenshotURL: "screenshot", 1960 }; 1961 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 1962 sandbox.spy(feed.pinnedCache, "request"); 1963 await feed.pin({ data: { index: 2, site } }); 1964 1965 Assert.ok( 1966 feed.pinnedCache.request.calledOnce, 1967 "feed.pinnedCache.request called once" 1968 ); 1969 NewTabUtils.pinnedLinks.pin.resetHistory(); 1970 sandbox.restore(); 1971 } 1972 1973 { 1974 info( 1975 "TopSitesFeed.pin should lookup the link object to update the custom " + 1976 "screenshot when the custom screenshot is initially null" 1977 ); 1978 let site = { 1979 url: "foo.bar", 1980 label: "foo", 1981 customScreenshotURL: null, 1982 }; 1983 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 1984 sandbox.spy(feed.pinnedCache, "request"); 1985 await feed.pin({ data: { index: 2, site } }); 1986 1987 Assert.ok( 1988 feed.pinnedCache.request.calledOnce, 1989 "feed.pinnedCache.request called once" 1990 ); 1991 NewTabUtils.pinnedLinks.pin.resetHistory(); 1992 sandbox.restore(); 1993 } 1994 1995 { 1996 info( 1997 "TopSitesFeed.pin should not do a link object lookup if custom " + 1998 "screenshot field is not set" 1999 ); 2000 let site = { url: "foo.bar", label: "foo" }; 2001 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2002 sandbox.spy(feed.pinnedCache, "request"); 2003 await feed.pin({ data: { index: 2, site } }); 2004 2005 Assert.ok( 2006 !feed.pinnedCache.request.called, 2007 "feed.pinnedCache.request never called" 2008 ); 2009 NewTabUtils.pinnedLinks.pin.resetHistory(); 2010 sandbox.restore(); 2011 } 2012 2013 { 2014 info( 2015 "TopSitesFeed.pin should pin site in specified slot of pinned " + 2016 "list that is free" 2017 ); 2018 sandbox 2019 .stub(NewTabUtils.pinnedLinks, "links") 2020 .get(() => [null, { url: "example.com" }]); 2021 2022 let site = { url: "foo.bar", label: "foo" }; 2023 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2024 await feed.pin({ data: { index: 2, site } }); 2025 Assert.ok( 2026 NewTabUtils.pinnedLinks.pin.calledOnce, 2027 "NewTabUtils.pinnedLinks.pin called once" 2028 ); 2029 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 2)); 2030 NewTabUtils.pinnedLinks.pin.resetHistory(); 2031 sandbox.restore(); 2032 } 2033 }); 2034 2035 add_task(async function test_pin_part_2() { 2036 let sandbox = sinon.createSandbox(); 2037 2038 let prepFeed = feed => { 2039 sandbox.stub(NewTabUtils.pinnedLinks, "pin"); 2040 return feed; 2041 }; 2042 2043 { 2044 info("TopSitesFeed.pin should save the searchTopSite attribute if set"); 2045 sandbox 2046 .stub(NewTabUtils.pinnedLinks, "links") 2047 .get(() => [null, { url: "example.com" }]); 2048 2049 let site = { url: "foo.bar", label: "foo", searchTopSite: true }; 2050 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2051 await feed.pin({ data: { index: 2, site } }); 2052 Assert.ok( 2053 NewTabUtils.pinnedLinks.pin.calledOnce, 2054 "NewTabUtils.pinnedLinks.pin called once" 2055 ); 2056 Assert.ok(NewTabUtils.pinnedLinks.pin.firstCall.args[0].searchTopSite); 2057 NewTabUtils.pinnedLinks.pin.resetHistory(); 2058 sandbox.restore(); 2059 } 2060 2061 { 2062 info( 2063 "TopSitesFeed.pin should NOT move a pinned site in specified " + 2064 "slot to the next slot" 2065 ); 2066 sandbox 2067 .stub(NewTabUtils.pinnedLinks, "links") 2068 .get(() => [null, null, { url: "example.com" }]); 2069 2070 let site = { url: "foo.bar", label: "foo" }; 2071 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2072 await feed.pin({ data: { index: 2, site } }); 2073 Assert.ok( 2074 NewTabUtils.pinnedLinks.pin.calledOnce, 2075 "NewTabUtils.pinnedLinks.pin called once" 2076 ); 2077 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 2)); 2078 NewTabUtils.pinnedLinks.pin.resetHistory(); 2079 sandbox.restore(); 2080 } 2081 2082 { 2083 info( 2084 "TopSitesFeed.pin should properly update LinksCache object " + 2085 "properties between migrations" 2086 ); 2087 sandbox 2088 .stub(NewTabUtils.pinnedLinks, "links") 2089 .get(() => [{ url: "https://foo.com/" }]); 2090 2091 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2092 let pinnedLinks = await feed.pinnedCache.request(); 2093 Assert.equal(pinnedLinks.length, 1); 2094 feed.pinnedCache.expire(); 2095 2096 pinnedLinks[0].__sharedCache.updateLink("screenshot", "foo"); 2097 2098 pinnedLinks = await feed.pinnedCache.request(); 2099 Assert.equal(pinnedLinks[0].screenshot, "foo"); 2100 2101 // Force cache expiration in order to trigger a migration of objects 2102 feed.pinnedCache.expire(); 2103 pinnedLinks[0].__sharedCache.updateLink("screenshot", "bar"); 2104 2105 pinnedLinks = await feed.pinnedCache.request(); 2106 Assert.equal(pinnedLinks[0].screenshot, "bar"); 2107 sandbox.restore(); 2108 } 2109 }); 2110 2111 add_task(async function test_pin_part_3() { 2112 let sandbox = sinon.createSandbox(); 2113 2114 let prepFeed = feed => { 2115 sandbox.stub(NewTabUtils.pinnedLinks, "pin"); 2116 return feed; 2117 }; 2118 2119 { 2120 info("TopSitesFeed.pin should call insert if index < 0"); 2121 let site = { url: "foo.bar", label: "foo" }; 2122 let action = { data: { index: -1, site } }; 2123 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2124 sandbox.spy(feed, "insert"); 2125 await feed.pin(action); 2126 2127 Assert.ok(feed.insert.calledOnce, "feed.insert called once"); 2128 Assert.ok(feed.insert.calledWithExactly(action)); 2129 NewTabUtils.pinnedLinks.pin.resetHistory(); 2130 sandbox.restore(); 2131 } 2132 2133 { 2134 info("TopSitesFeed.pin should not call insert if index == 0"); 2135 let site = { url: "foo.bar", label: "foo" }; 2136 let action = { data: { index: 0, site } }; 2137 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2138 sandbox.spy(feed, "insert"); 2139 await feed.pin(action); 2140 2141 Assert.ok(!feed.insert.called, "feed.insert not called"); 2142 NewTabUtils.pinnedLinks.pin.resetHistory(); 2143 sandbox.restore(); 2144 } 2145 2146 { 2147 info("TopSitesFeed.pin should trigger refresh on TOP_SITES_PIN"); 2148 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2149 sandbox.stub(feed, "refresh"); 2150 let pinExistingAction = { 2151 type: actionTypes.TOP_SITES_PIN, 2152 data: { site: FAKE_LINKS[4], index: 4 }, 2153 }; 2154 2155 await feed.pin(pinExistingAction); 2156 2157 Assert.ok(feed.refresh.calledOnce, "feed.refresh called once"); 2158 NewTabUtils.pinnedLinks.pin.resetHistory(); 2159 sandbox.restore(); 2160 } 2161 }); 2162 2163 add_task(async function test_integration() { 2164 let sandbox = sinon.createSandbox(); 2165 2166 info("Test adding a pinned site and removing it with actions"); 2167 let feed = getTopSitesFeedForTest(sandbox); 2168 2169 let resolvers = []; 2170 feed.store.dispatch = sandbox.stub().callsFake(() => { 2171 resolvers.shift()(); 2172 }); 2173 feed._startedUp = true; 2174 sandbox.stub(feed, "_fetchScreenshot"); 2175 2176 let forDispatch = action => 2177 new Promise(resolve => { 2178 resolvers.push(resolve); 2179 feed.onAction(action); 2180 }); 2181 2182 feed._requestRichIcon = sandbox.stub(); 2183 let url = "https://pin.me"; 2184 sandbox.stub(NewTabUtils.pinnedLinks, "pin").callsFake(link => { 2185 NewTabUtils.pinnedLinks.links.push(link); 2186 }); 2187 2188 await forDispatch({ 2189 type: actionTypes.TOP_SITES_INSERT, 2190 data: { site: { url } }, 2191 }); 2192 NewTabUtils.pinnedLinks.links.pop(); 2193 await forDispatch({ type: actionTypes.PLACES_LINK_BLOCKED }); 2194 2195 Assert.ok( 2196 feed.store.dispatch.calledTwice, 2197 "feed.store.dispatch called twice" 2198 ); 2199 Assert.equal(feed.store.dispatch.firstCall.args[0].data.links[0].url, url); 2200 Assert.equal( 2201 feed.store.dispatch.secondCall.args[0].data.links[0].url, 2202 FAKE_LINKS[0].url 2203 ); 2204 2205 sandbox.restore(); 2206 }); 2207 2208 add_task(async function test_improvesearch_noDefaultSearchTile_experiment() { 2209 let sandbox = sinon.createSandbox(); 2210 const NO_DEFAULT_SEARCH_TILE_PREF = "improvesearch.noDefaultSearchTile"; 2211 2212 let prepFeed = feed => { 2213 sandbox.stub(SearchService.prototype, "getDefault").resolves({ 2214 identifier: "google", 2215 }); 2216 return feed; 2217 }; 2218 2219 { 2220 info( 2221 "TopSitesFeed.getLinksWithDefaults should filter out alexa top 5 " + 2222 "search from the default sites" 2223 ); 2224 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2225 feed.store.state.Prefs.values[NO_DEFAULT_SEARCH_TILE_PREF] = true; 2226 let top5Test = [ 2227 "https://google.com", 2228 "https://search.yahoo.com", 2229 "https://yahoo.com", 2230 "https://bing.com", 2231 "https://ask.com", 2232 "https://duckduckgo.com", 2233 ]; 2234 2235 gGetTopSitesStub.resolves([ 2236 { url: "https://amazon.com" }, 2237 ...top5Test.map(url => ({ url })), 2238 ]); 2239 2240 const urlsReturned = (await feed.getLinksWithDefaults()).map( 2241 link => link.url 2242 ); 2243 Assert.ok( 2244 urlsReturned.includes("https://amazon.com"), 2245 "amazon included in default links" 2246 ); 2247 top5Test.forEach(url => 2248 Assert.ok(!urlsReturned.includes(url), `Should not include ${url}`) 2249 ); 2250 2251 gGetTopSitesStub.resolves(FAKE_LINKS); 2252 sandbox.restore(); 2253 } 2254 2255 { 2256 info( 2257 "TopSitesFeed.getLinksWithDefaults should not filter out alexa, default " + 2258 "search from the query results if the experiment pref is off" 2259 ); 2260 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2261 feed.store.state.Prefs.values[NO_DEFAULT_SEARCH_TILE_PREF] = false; 2262 2263 gGetTopSitesStub.resolves([ 2264 { url: "https://google.com" }, 2265 { url: "https://foo.com" }, 2266 { url: "https://duckduckgo" }, 2267 ]); 2268 let urlsReturned = (await feed.getLinksWithDefaults()).map( 2269 link => link.url 2270 ); 2271 2272 Assert.ok(urlsReturned.includes("https://google.com")); 2273 gGetTopSitesStub.resolves(FAKE_LINKS); 2274 sandbox.restore(); 2275 } 2276 2277 { 2278 info( 2279 "TopSitesFeed.getLinksWithDefaults should filter out the current " + 2280 "default search from the default sites" 2281 ); 2282 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2283 feed.store.state.Prefs.values[NO_DEFAULT_SEARCH_TILE_PREF] = true; 2284 2285 sandbox.stub(feed, "_currentSearchHostname").get(() => "amazon"); 2286 feed.onAction({ 2287 type: actionTypes.PREFS_INITIAL_VALUES, 2288 data: { "default.sites": "google.com,amazon.com" }, 2289 }); 2290 gGetTopSitesStub.resolves([{ url: "https://foo.com" }]); 2291 2292 let urlsReturned = (await feed.getLinksWithDefaults()).map( 2293 link => link.url 2294 ); 2295 Assert.ok(!urlsReturned.includes("https://amazon.com")); 2296 2297 gGetTopSitesStub.resolves(FAKE_LINKS); 2298 sandbox.restore(); 2299 } 2300 2301 { 2302 info( 2303 "TopSitesFeed.getLinksWithDefaults should not filter out current " + 2304 "default search from pinned sites even if it matches the current " + 2305 "default search" 2306 ); 2307 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2308 feed.store.state.Prefs.values[NO_DEFAULT_SEARCH_TILE_PREF] = true; 2309 2310 sandbox 2311 .stub(NewTabUtils.pinnedLinks, "links") 2312 .get(() => [{ url: "google.com" }]); 2313 gGetTopSitesStub.resolves([{ url: "https://foo.com" }]); 2314 2315 let urlsReturned = (await feed.getLinksWithDefaults()).map( 2316 link => link.url 2317 ); 2318 Assert.ok(urlsReturned.includes("google.com")); 2319 2320 gGetTopSitesStub.resolves(FAKE_LINKS); 2321 sandbox.restore(); 2322 } 2323 }); 2324 2325 add_task( 2326 async function test_improvesearch_noDefaultSearchTile_experiment_part_2() { 2327 let sandbox = sinon.createSandbox(); 2328 const NO_DEFAULT_SEARCH_TILE_PREF = "improvesearch.noDefaultSearchTile"; 2329 2330 let prepFeed = feed => { 2331 sandbox.stub(SearchService.prototype, "getDefault").resolves({ 2332 identifier: "google", 2333 }); 2334 return feed; 2335 }; 2336 2337 { 2338 info( 2339 "TopSitesFeed.getLinksWithDefaults should call refresh and set " + 2340 "._currentSearchHostname to the new engine hostname when the " + 2341 "default search engine has been set" 2342 ); 2343 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2344 feed.store.state.Prefs.values[NO_DEFAULT_SEARCH_TILE_PREF] = true; 2345 sandbox.stub(feed, "refresh"); 2346 2347 feed.observe(null, "browser-search-engine-modified", "engine-default"); 2348 Assert.equal(feed._currentSearchHostname, "duckduckgo"); 2349 Assert.ok(feed.refresh.calledOnce, "feed.refresh called once"); 2350 2351 gGetTopSitesStub.resolves(FAKE_LINKS); 2352 sandbox.restore(); 2353 } 2354 2355 { 2356 info( 2357 "TopSitesFeed.getLinksWithDefaults should call refresh when the " + 2358 "experiment pref has changed" 2359 ); 2360 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2361 feed.store.state.Prefs.values[NO_DEFAULT_SEARCH_TILE_PREF] = true; 2362 sandbox.stub(feed, "refresh"); 2363 2364 feed.onAction({ 2365 type: actionTypes.PREF_CHANGED, 2366 data: { name: NO_DEFAULT_SEARCH_TILE_PREF, value: true }, 2367 }); 2368 Assert.ok(feed.refresh.calledOnce, "feed.refresh was called once"); 2369 2370 feed.onAction({ 2371 type: actionTypes.PREF_CHANGED, 2372 data: { name: NO_DEFAULT_SEARCH_TILE_PREF, value: false }, 2373 }); 2374 Assert.ok(feed.refresh.calledTwice, "feed.refresh was called twice"); 2375 2376 gGetTopSitesStub.resolves(FAKE_LINKS); 2377 sandbox.restore(); 2378 } 2379 } 2380 ); 2381 2382 // eslint-disable-next-line max-statements 2383 add_task(async function test_improvesearch_topSitesSearchShortcuts() { 2384 let sandbox = sinon.createSandbox(); 2385 let searchEngines = [{ aliases: ["@google"] }, { aliases: ["@amazon"] }]; 2386 2387 let prepFeed = feed => { 2388 sandbox 2389 .stub(SearchService.prototype, "getAppProvidedEngines") 2390 .resolves(searchEngines); 2391 sandbox.stub(NewTabUtils.pinnedLinks, "pin").callsFake((site, index) => { 2392 NewTabUtils.pinnedLinks.links[index] = site; 2393 }); 2394 feed.store.state.Prefs.values[SEARCH_SHORTCUTS_EXPERIMENT_PREF] = true; 2395 feed.store.state.Prefs.values[SEARCH_SHORTCUTS_SEARCH_ENGINES_PREF] = 2396 "google,amazon"; 2397 feed.store.state.Prefs.values[SEARCH_SHORTCUTS_HAVE_PINNED_PREF] = ""; 2398 return feed; 2399 }; 2400 2401 { 2402 info( 2403 "TopSitesFeed should updateCustomSearchShortcuts when experiment " + 2404 "pref is turned on" 2405 ); 2406 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2407 feed.store.state.Prefs.values[SEARCH_SHORTCUTS_EXPERIMENT_PREF] = false; 2408 feed.updateCustomSearchShortcuts = sandbox.spy(); 2409 2410 // turn the experiment on 2411 feed.onAction({ 2412 type: actionTypes.PREF_CHANGED, 2413 data: { name: SEARCH_SHORTCUTS_EXPERIMENT_PREF, value: true }, 2414 }); 2415 2416 Assert.ok( 2417 feed.updateCustomSearchShortcuts.calledOnce, 2418 "feed.updateCustomSearchShortcuts called once" 2419 ); 2420 sandbox.restore(); 2421 } 2422 2423 { 2424 info( 2425 "TopSitesFeed should filter out default top sites that match a " + 2426 "hostname of a search shortcut if previously blocked" 2427 ); 2428 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2429 feed.refreshDefaults("https://amazon.ca"); 2430 sandbox 2431 .stub(NewTabUtils.blockedLinks, "links") 2432 .value([{ url: "https://amazon.com" }]); 2433 sandbox.stub(NewTabUtils.blockedLinks, "isBlocked").callsFake(site => { 2434 return NewTabUtils.blockedLinks.links[0].url === site.url; 2435 }); 2436 2437 let urlsReturned = (await feed.getLinksWithDefaults()).map( 2438 link => link.url 2439 ); 2440 Assert.ok(!urlsReturned.includes("https://amazon.ca")); 2441 sandbox.restore(); 2442 } 2443 2444 { 2445 info("TopSitesFeed should update frecent search topsite icon"); 2446 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2447 feed._tippyTopProvider.processSite = site => { 2448 site.tippyTopIcon = "icon.png"; 2449 site.backgroundColor = "#fff"; 2450 return site; 2451 }; 2452 gGetTopSitesStub.resolves([{ url: "https://google.com" }]); 2453 2454 let urlsReturned = await feed.getLinksWithDefaults(); 2455 2456 let defaultSearchTopsite = urlsReturned.find( 2457 s => s.url === "https://google.com" 2458 ); 2459 Assert.ok(defaultSearchTopsite.searchTopSite); 2460 Assert.equal(defaultSearchTopsite.tippyTopIcon, "icon.png"); 2461 Assert.equal(defaultSearchTopsite.backgroundColor, "#fff"); 2462 gGetTopSitesStub.resolves(FAKE_LINKS); 2463 sandbox.restore(); 2464 } 2465 2466 { 2467 info("TopSitesFeed should update default search topsite icon"); 2468 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2469 feed._tippyTopProvider.processSite = site => { 2470 site.tippyTopIcon = "icon.png"; 2471 site.backgroundColor = "#fff"; 2472 return site; 2473 }; 2474 gGetTopSitesStub.resolves([{ url: "https://foo.com" }]); 2475 feed.onAction({ 2476 type: actionTypes.PREFS_INITIAL_VALUES, 2477 data: { "default.sites": "google.com,amazon.com" }, 2478 }); 2479 2480 let urlsReturned = await feed.getLinksWithDefaults(); 2481 2482 let defaultSearchTopsite = urlsReturned.find( 2483 s => s.url === "https://amazon.com" 2484 ); 2485 Assert.ok(defaultSearchTopsite.searchTopSite); 2486 Assert.equal(defaultSearchTopsite.tippyTopIcon, "icon.png"); 2487 Assert.equal(defaultSearchTopsite.backgroundColor, "#fff"); 2488 gGetTopSitesStub.resolves(FAKE_LINKS); 2489 sandbox.restore(); 2490 } 2491 2492 { 2493 info( 2494 "TopSitesFeed should dispatch UPDATE_SEARCH_SHORTCUTS on " + 2495 "updateCustomSearchShortcuts" 2496 ); 2497 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2498 feed.store.state.Prefs.values["improvesearch.noDefaultSearchTile"] = true; 2499 await feed.updateCustomSearchShortcuts(); 2500 Assert.ok( 2501 feed.store.dispatch.calledOnce, 2502 "feed.store.dispatch called once" 2503 ); 2504 Assert.ok( 2505 feed.store.dispatch.calledWith({ 2506 data: { 2507 searchShortcuts: [ 2508 { 2509 keyword: "@google", 2510 shortURL: "google", 2511 url: "https://google.com", 2512 backgroundColor: undefined, 2513 smallFavicon: 2514 "chrome://activity-stream/content/data/content/tippytop/favicons/google-com.ico", 2515 tippyTopIcon: 2516 "chrome://activity-stream/content/data/content/tippytop/images/google-com@2x.png", 2517 }, 2518 { 2519 keyword: "@amazon", 2520 shortURL: "amazon", 2521 url: "https://amazon.com", 2522 backgroundColor: undefined, 2523 smallFavicon: 2524 "chrome://activity-stream/content/data/content/tippytop/favicons/amazon.ico", 2525 tippyTopIcon: 2526 "chrome://activity-stream/content/data/content/tippytop/images/amazon@2x.png", 2527 }, 2528 ], 2529 }, 2530 meta: { 2531 from: "ActivityStream:Main", 2532 to: "ActivityStream:Content", 2533 isStartup: false, 2534 }, 2535 type: "UPDATE_SEARCH_SHORTCUTS", 2536 }) 2537 ); 2538 } 2539 2540 sandbox.restore(); 2541 }); 2542 2543 // eslint-disable-next-line max-statements 2544 add_task(async function test_updatePinnedSearchShortcuts() { 2545 let sandbox = sinon.createSandbox(); 2546 2547 let prepFeed = feed => { 2548 sandbox.stub(NewTabUtils.pinnedLinks, "pin"); 2549 sandbox.stub(NewTabUtils.pinnedLinks, "unpin"); 2550 return feed; 2551 }; 2552 2553 { 2554 info( 2555 "TopSitesFeed.updatePinnedSearchShortcuts should unpin a " + 2556 "shortcut in deletedShortcuts" 2557 ); 2558 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2559 2560 let deletedShortcuts = [ 2561 { 2562 url: "https://google.com", 2563 searchVendor: "google", 2564 label: "google", 2565 searchTopSite: true, 2566 }, 2567 ]; 2568 let addedShortcuts = []; 2569 sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [ 2570 null, 2571 null, 2572 { 2573 url: "https://amazon.com", 2574 searchVendor: "amazon", 2575 label: "amazon", 2576 searchTopSite: true, 2577 }, 2578 ]); 2579 2580 feed.updatePinnedSearchShortcuts({ addedShortcuts, deletedShortcuts }); 2581 Assert.ok( 2582 NewTabUtils.pinnedLinks.pin.notCalled, 2583 "NewTabUtils.pinnedLinks.pin not called" 2584 ); 2585 Assert.ok( 2586 NewTabUtils.pinnedLinks.unpin.calledOnce, 2587 "NewTabUtils.pinnedLinks.unpin called once" 2588 ); 2589 Assert.ok( 2590 NewTabUtils.pinnedLinks.unpin.calledWith({ 2591 url: "https://google.com", 2592 }) 2593 ); 2594 2595 NewTabUtils.pinnedLinks.pin.resetHistory(); 2596 NewTabUtils.pinnedLinks.unpin.resetHistory(); 2597 sandbox.restore(); 2598 } 2599 2600 { 2601 info( 2602 "TopSitesFeed.updatePinnedSearchShortcuts should pin a shortcut " + 2603 "in addedShortcuts" 2604 ); 2605 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2606 2607 let addedShortcuts = [ 2608 { 2609 url: "https://google.com", 2610 searchVendor: "google", 2611 label: "google", 2612 searchTopSite: true, 2613 }, 2614 ]; 2615 let deletedShortcuts = []; 2616 sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [ 2617 null, 2618 null, 2619 { 2620 url: "https://amazon.com", 2621 searchVendor: "amazon", 2622 label: "amazon", 2623 searchTopSite: true, 2624 }, 2625 ]); 2626 feed.updatePinnedSearchShortcuts({ addedShortcuts, deletedShortcuts }); 2627 2628 Assert.ok( 2629 NewTabUtils.pinnedLinks.unpin.notCalled, 2630 "NewTabUtils.pinnedLinks.unpin not called" 2631 ); 2632 Assert.ok( 2633 NewTabUtils.pinnedLinks.pin.calledOnce, 2634 "NewTabUtils.pinnedLinks.pin called once" 2635 ); 2636 Assert.ok( 2637 NewTabUtils.pinnedLinks.pin.calledWith( 2638 { 2639 label: "google", 2640 searchTopSite: true, 2641 searchVendor: "google", 2642 url: "https://google.com", 2643 }, 2644 0 2645 ) 2646 ); 2647 2648 NewTabUtils.pinnedLinks.pin.resetHistory(); 2649 NewTabUtils.pinnedLinks.unpin.resetHistory(); 2650 sandbox.restore(); 2651 } 2652 2653 { 2654 info( 2655 "TopSitesFeed.updatePinnedSearchShortcuts should pin and unpin " + 2656 "in the same action" 2657 ); 2658 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2659 2660 let addedShortcuts = [ 2661 { 2662 url: "https://google.com", 2663 searchVendor: "google", 2664 label: "google", 2665 searchTopSite: true, 2666 }, 2667 { 2668 url: "https://ebay.com", 2669 searchVendor: "ebay", 2670 label: "ebay", 2671 searchTopSite: true, 2672 }, 2673 ]; 2674 let deletedShortcuts = [ 2675 { 2676 url: "https://amazon.com", 2677 searchVendor: "amazon", 2678 label: "amazon", 2679 searchTopSite: true, 2680 }, 2681 ]; 2682 2683 sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [ 2684 { url: "https://foo.com" }, 2685 { 2686 url: "https://amazon.com", 2687 searchVendor: "amazon", 2688 label: "amazon", 2689 searchTopSite: true, 2690 }, 2691 ]); 2692 2693 feed.updatePinnedSearchShortcuts({ addedShortcuts, deletedShortcuts }); 2694 2695 Assert.ok( 2696 NewTabUtils.pinnedLinks.unpin.calledOnce, 2697 "NewTabUtils.pinnedLinks.unpin called once" 2698 ); 2699 Assert.ok( 2700 NewTabUtils.pinnedLinks.pin.calledTwice, 2701 "NewTabUtils.pinnedLinks.pin called twice" 2702 ); 2703 2704 NewTabUtils.pinnedLinks.pin.resetHistory(); 2705 NewTabUtils.pinnedLinks.unpin.resetHistory(); 2706 sandbox.restore(); 2707 } 2708 2709 { 2710 info( 2711 "TopSitesFeed.updatePinnedSearchShortcuts should pin a shortcut in " + 2712 "addedShortcuts even if pinnedLinks is full" 2713 ); 2714 let feed = prepFeed(getTopSitesFeedForTest(sandbox)); 2715 2716 let addedShortcuts = [ 2717 { 2718 url: "https://google.com", 2719 searchVendor: "google", 2720 label: "google", 2721 searchTopSite: true, 2722 }, 2723 ]; 2724 let deletedShortcuts = []; 2725 sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => FAKE_LINKS); 2726 feed.updatePinnedSearchShortcuts({ addedShortcuts, deletedShortcuts }); 2727 2728 Assert.ok( 2729 NewTabUtils.pinnedLinks.unpin.notCalled, 2730 "NewTabUtils.pinnedLinks.unpin not called" 2731 ); 2732 Assert.ok( 2733 NewTabUtils.pinnedLinks.pin.calledWith( 2734 { label: "google", searchTopSite: true, url: "https://google.com" }, 2735 0 2736 ), 2737 "NewTabUtils.pinnedLinks.unpin not called" 2738 ); 2739 2740 NewTabUtils.pinnedLinks.pin.resetHistory(); 2741 NewTabUtils.pinnedLinks.unpin.resetHistory(); 2742 sandbox.restore(); 2743 } 2744 }); 2745 2746 // eslint-disable-next-line max-statements 2747 add_task(async function test_ContileIntegration() { 2748 let sandbox = sinon.createSandbox(); 2749 Services.prefs.setStringPref( 2750 TOP_SITES_BLOCKED_SPONSORS_PREF, 2751 `["foo","bar"]` 2752 ); 2753 2754 let prepFeed = feed => { 2755 sandbox.stub(NimbusFeatures.newtab, "getVariable").returns(true); 2756 feed.store.state.Prefs.values[SHOW_SPONSORED_PREF] = true; 2757 let fetchStub = sandbox.stub(feed, "fetch"); 2758 return { feed, fetchStub }; 2759 }; 2760 2761 { 2762 info("TopSitesFeed._fetchSites should fetch sites from Contile"); 2763 let { feed, fetchStub } = prepFeed(getTopSitesFeedForTest(sandbox)); 2764 fetchStub.resolves({ 2765 ok: true, 2766 status: 200, 2767 headers: new Map([ 2768 ["cache-control", "private, max-age=859, stale-if-error=10463"], 2769 ]), 2770 json: () => 2771 Promise.resolve({ 2772 tiles: [ 2773 { 2774 url: "https://www.test.com", 2775 image_url: "images/test-com.png", 2776 click_url: "https://www.test-click.com", 2777 impression_url: "https://www.test-impression.com", 2778 name: "test", 2779 }, 2780 { 2781 url: "https://www.test1.com", 2782 image_url: "images/test1-com.png", 2783 click_url: "https://www.test1-click.com", 2784 impression_url: "https://www.test1-impression.com", 2785 name: "test1", 2786 }, 2787 ], 2788 }), 2789 }); 2790 2791 let fetched = await feed._contile._fetchSites(); 2792 2793 Assert.ok(fetched); 2794 Assert.equal(feed._contile.sites.length, 2); 2795 2796 info("TopSitesFeed._fetchSites should not send cookies"); 2797 Assert.ok(fetchStub.calledOnce, "fetch should be called once"); 2798 Assert.equal( 2799 fetchStub.firstCall.args[1].credentials, 2800 "omit", 2801 "should not send cookies" 2802 ); 2803 sandbox.restore(); 2804 } 2805 2806 { 2807 info("TopSitesFeed._fetchSites should call allocatePositions"); 2808 let { feed } = prepFeed(getTopSitesFeedForTest(sandbox)); 2809 sandbox.stub(feed, "allocatePositions").resolves(); 2810 await feed._contile.refresh(); 2811 2812 Assert.ok( 2813 feed.allocatePositions.calledOnce, 2814 "feed.allocatePositions called once" 2815 ); 2816 sandbox.restore(); 2817 } 2818 2819 { 2820 info( 2821 "TopSitesFeed._fetchSites should fetch SOV (Share-of-Voice) " + 2822 "settings from Contile" 2823 ); 2824 let { feed, fetchStub } = prepFeed(getTopSitesFeedForTest(sandbox)); 2825 2826 let sov = { 2827 name: "SOV-20230518215316", 2828 allocations: [ 2829 { 2830 position: 1, 2831 allocation: [ 2832 { 2833 partner: "foo", 2834 percentage: 100, 2835 }, 2836 { 2837 partner: "bar", 2838 percentage: 0, 2839 }, 2840 ], 2841 }, 2842 { 2843 position: 2, 2844 allocation: [ 2845 { 2846 partner: "foo", 2847 percentage: 80, 2848 }, 2849 { 2850 partner: "bar", 2851 percentage: 20, 2852 }, 2853 ], 2854 }, 2855 ], 2856 }; 2857 fetchStub.resolves({ 2858 ok: true, 2859 status: 200, 2860 headers: new Map([ 2861 ["cache-control", "private, max-age=859, stale-if-error=10463"], 2862 ]), 2863 json: () => 2864 Promise.resolve({ 2865 sov: btoa(JSON.stringify(sov)), 2866 tiles: [ 2867 { 2868 url: "https://www.test.com", 2869 image_url: "images/test-com.png", 2870 click_url: "https://www.test-click.com", 2871 impression_url: "https://www.test-impression.com", 2872 name: "test", 2873 }, 2874 { 2875 url: "https://www.test1.com", 2876 image_url: "images/test1-com.png", 2877 click_url: "https://www.test1-click.com", 2878 impression_url: "https://www.test1-impression.com", 2879 name: "test1", 2880 }, 2881 ], 2882 }), 2883 }); 2884 2885 let fetched = await feed._contile._fetchSites(); 2886 2887 Assert.ok(fetched); 2888 Assert.deepEqual(feed._contile.sov, sov); 2889 Assert.equal(feed._contile.sites.length, 2); 2890 sandbox.restore(); 2891 } 2892 2893 { 2894 info( 2895 "TopSitesFeed._fetchSites should not fetch from Contile if " + 2896 "it's not enabled" 2897 ); 2898 let { feed, fetchStub } = prepFeed(getTopSitesFeedForTest(sandbox)); 2899 2900 NimbusFeatures.newtab.getVariable.reset(); 2901 NimbusFeatures.newtab.getVariable.returns(false); 2902 let fetched = await feed._contile._fetchSites(); 2903 2904 Assert.ok(fetchStub.notCalled, "TopSitesFeed.fetch was not called"); 2905 Assert.ok(!fetched); 2906 Assert.equal(feed._contile.sites.length, 0); 2907 sandbox.restore(); 2908 } 2909 2910 { 2911 info( 2912 "TopSitesFeed._fetchSites should still return two tiles when Contile " + 2913 "provides more than 2 tiles and filtering results in more than 2 tiles" 2914 ); 2915 let { feed, fetchStub } = prepFeed(getTopSitesFeedForTest(sandbox)); 2916 2917 NimbusFeatures.newtab.getVariable.reset(); 2918 NimbusFeatures.newtab.getVariable.onCall(0).returns(true); 2919 NimbusFeatures.newtab.getVariable.onCall(1).returns(true); 2920 2921 fetchStub.resolves({ 2922 ok: true, 2923 status: 200, 2924 headers: new Map([ 2925 ["cache-control", "private, max-age=859, stale-if-error=10463"], 2926 ]), 2927 json: () => 2928 Promise.resolve({ 2929 tiles: [ 2930 { 2931 url: "https://www.test.com", 2932 image_url: "images/test-com.png", 2933 click_url: "https://www.test-click.com", 2934 impression_url: "https://www.test-impression.com", 2935 name: "test", 2936 }, 2937 { 2938 url: "https://foo.com", 2939 image_url: "images/foo-com.png", 2940 click_url: "https://www.foo-click.com", 2941 impression_url: "https://www.foo-impression.com", 2942 name: "foo", 2943 }, 2944 { 2945 url: "https://bar.com", 2946 image_url: "images/bar-com.png", 2947 click_url: "https://www.bar-click.com", 2948 impression_url: "https://www.bar-impression.com", 2949 name: "bar", 2950 }, 2951 { 2952 url: "https://test1.com", 2953 image_url: "images/test1-com.png", 2954 click_url: "https://www.test1-click.com", 2955 impression_url: "https://www.test1-impression.com", 2956 name: "test1", 2957 }, 2958 { 2959 url: "https://test2.com", 2960 image_url: "images/test2-com.png", 2961 click_url: "https://www.test2-click.com", 2962 impression_url: "https://www.test2-impression.com", 2963 name: "test2", 2964 }, 2965 ], 2966 }), 2967 }); 2968 2969 let fetched = await feed._contile._fetchSites(); 2970 2971 Assert.ok(fetched); 2972 // Both "foo" and "bar" should be filtered 2973 Assert.equal(feed._contile.sites.length, 3); 2974 Assert.equal(feed._contile.sites[0].url, "https://www.test.com"); 2975 Assert.equal(feed._contile.sites[1].url, "https://test1.com"); 2976 Assert.equal(feed._contile.sites[2].url, "https://test2.com"); 2977 sandbox.restore(); 2978 } 2979 2980 { 2981 info( 2982 "TopSitesFeed._fetchSites should still return two tiles with " + 2983 "replacement if the Nimbus variable was unset" 2984 ); 2985 let { feed, fetchStub } = prepFeed(getTopSitesFeedForTest(sandbox)); 2986 2987 NimbusFeatures.newtab.getVariable.reset(); 2988 NimbusFeatures.newtab.getVariable.onCall(0).returns(true); 2989 NimbusFeatures.newtab.getVariable.onCall(1).returns(undefined); 2990 2991 fetchStub.resolves({ 2992 ok: true, 2993 status: 200, 2994 headers: new Map([ 2995 ["cache-control", "private, max-age=859, stale-if-error=10463"], 2996 ]), 2997 json: () => 2998 Promise.resolve({ 2999 tiles: [ 3000 { 3001 url: "https://www.test.com", 3002 image_url: "images/test-com.png", 3003 click_url: "https://www.test-click.com", 3004 impression_url: "https://www.test-impression.com", 3005 name: "test", 3006 }, 3007 { 3008 url: "https://foo.com", 3009 image_url: "images/foo-com.png", 3010 click_url: "https://www.foo-click.com", 3011 impression_url: "https://www.foo-impression.com", 3012 name: "foo", 3013 }, 3014 { 3015 url: "https://test1.com", 3016 image_url: "images/test1-com.png", 3017 click_url: "https://www.test1-click.com", 3018 impression_url: "https://www.test1-impression.com", 3019 name: "test1", 3020 }, 3021 ], 3022 }), 3023 }); 3024 3025 let fetched = await feed._contile._fetchSites(); 3026 3027 Assert.ok(fetched); 3028 Assert.equal(feed._contile.sites.length, 2); 3029 Assert.equal(feed._contile.sites[0].url, "https://www.test.com"); 3030 Assert.equal(feed._contile.sites[1].url, "https://test1.com"); 3031 sandbox.restore(); 3032 } 3033 3034 { 3035 info("TopSitesFeed._fetchSites should filter the blocked sponsors"); 3036 3037 let { feed, fetchStub } = prepFeed(getTopSitesFeedForTest(sandbox)); 3038 NimbusFeatures.newtab.getVariable.returns(true); 3039 3040 fetchStub.resolves({ 3041 ok: true, 3042 status: 200, 3043 headers: new Map([ 3044 ["cache-control", "private, max-age=859, stale-if-error=10463"], 3045 ]), 3046 json: () => 3047 Promise.resolve({ 3048 tiles: [ 3049 { 3050 url: "https://www.test.com", 3051 image_url: "images/test-com.png", 3052 click_url: "https://www.test-click.com", 3053 impression_url: "https://www.test-impression.com", 3054 name: "test", 3055 }, 3056 { 3057 url: "https://foo.com", 3058 image_url: "images/foo-com.png", 3059 click_url: "https://www.foo-click.com", 3060 impression_url: "https://www.foo-impression.com", 3061 name: "foo", 3062 }, 3063 { 3064 url: "https://bar.com", 3065 image_url: "images/bar-com.png", 3066 click_url: "https://www.bar-click.com", 3067 impression_url: "https://www.bar-impression.com", 3068 name: "bar", 3069 }, 3070 ], 3071 }), 3072 }); 3073 3074 let fetched = await feed._contile._fetchSites(); 3075 3076 Assert.ok(fetched); 3077 // Both "foo" and "bar" should be filtered 3078 Assert.equal(feed._contile.sites.length, 1); 3079 Assert.equal(feed._contile.sites[0].url, "https://www.test.com"); 3080 sandbox.restore(); 3081 } 3082 3083 { 3084 info( 3085 "TopSitesFeed._fetchSites should return false when Contile returns " + 3086 "with error status and no values are stored in cache prefs" 3087 ); 3088 let { feed, fetchStub } = prepFeed(getTopSitesFeedForTest(sandbox)); 3089 NimbusFeatures.newtab.getVariable.returns(true); 3090 feed._contile.cache.get.returns({ contile: [] }); 3091 Services.prefs.setIntPref(CONTILE_CACHE_LAST_FETCH_PREF, 0); 3092 3093 fetchStub.resolves({ 3094 ok: false, 3095 status: 500, 3096 }); 3097 3098 let fetched = await feed._contile._fetchSites(); 3099 3100 Assert.ok(!fetched); 3101 Assert.ok(!feed._contile.sites.length); 3102 sandbox.restore(); 3103 } 3104 3105 { 3106 info( 3107 "TopSitesFeed._fetchSites should return false when Contile " + 3108 "returns with error status and cached tiles are expried" 3109 ); 3110 let { feed, fetchStub } = prepFeed(getTopSitesFeedForTest(sandbox)); 3111 NimbusFeatures.newtab.getVariable.returns(true); 3112 feed._contile.cache.get.returns({ contile: [] }); 3113 const THIRTY_MINUTES_AGO_IN_SECONDS = 3114 Math.round(Date.now() / 1000) - 60 * 30; 3115 Services.prefs.setIntPref( 3116 CONTILE_CACHE_LAST_FETCH_PREF, 3117 THIRTY_MINUTES_AGO_IN_SECONDS 3118 ); 3119 Services.prefs.setIntPref(CONTILE_CACHE_VALID_FOR_SECONDS_PREF, 60 * 15); 3120 3121 fetchStub.resolves({ 3122 ok: false, 3123 status: 500, 3124 }); 3125 3126 let fetched = await feed._contile._fetchSites(); 3127 3128 Assert.ok(!fetched); 3129 Assert.ok(!feed._contile.sites.length); 3130 sandbox.restore(); 3131 } 3132 3133 { 3134 info( 3135 "TopSitesFeed._fetchSites should handle invalid payload " + 3136 "properly from Contile" 3137 ); 3138 let { feed, fetchStub } = prepFeed(getTopSitesFeedForTest(sandbox)); 3139 3140 NimbusFeatures.newtab.getVariable.returns(true); 3141 fetchStub.resolves({ 3142 ok: true, 3143 status: 200, 3144 json: () => 3145 Promise.resolve({ 3146 unknown: [], 3147 }), 3148 }); 3149 3150 let fetched = await feed._contile._fetchSites(); 3151 3152 Assert.ok(!fetched); 3153 Assert.ok(!feed._contile.sites.length); 3154 sandbox.restore(); 3155 } 3156 3157 { 3158 info( 3159 "TopSitesFeed._fetchSites should handle empty payload properly " + 3160 "from Contile" 3161 ); 3162 let { feed, fetchStub } = prepFeed(getTopSitesFeedForTest(sandbox)); 3163 NimbusFeatures.newtab.getVariable.returns(true); 3164 3165 fetchStub.resolves({ 3166 ok: true, 3167 status: 200, 3168 headers: new Map([ 3169 ["cache-control", "private, max-age=859, stale-if-error=10463"], 3170 ]), 3171 json: () => 3172 Promise.resolve({ 3173 tiles: [], 3174 }), 3175 }); 3176 3177 let fetched = await feed._contile._fetchSites(); 3178 3179 Assert.ok(fetched); 3180 Assert.ok(!feed._contile.sites.length); 3181 sandbox.restore(); 3182 } 3183 3184 { 3185 info( 3186 "TopSitesFeed._fetchSites should handle no content properly " + 3187 "from Contile" 3188 ); 3189 let { feed, fetchStub } = prepFeed(getTopSitesFeedForTest(sandbox)); 3190 NimbusFeatures.newtab.getVariable.returns(true); 3191 3192 fetchStub.resolves({ ok: true, status: 204 }); 3193 3194 let fetched = await feed._contile._fetchSites(); 3195 3196 Assert.ok(!fetched); 3197 Assert.ok(!feed._contile.sites.length); 3198 sandbox.restore(); 3199 } 3200 3201 { 3202 info( 3203 "TopSitesFeed._fetchSites should set Caching Prefs after " + 3204 "a successful request" 3205 ); 3206 let { feed, fetchStub } = prepFeed(getTopSitesFeedForTest(sandbox)); 3207 NimbusFeatures.newtab.getVariable.returns(true); 3208 3209 let tiles = [ 3210 { 3211 url: "https://www.test.com", 3212 image_url: "images/test-com.png", 3213 click_url: "https://www.test-click.com", 3214 impression_url: "https://www.test-impression.com", 3215 name: "test", 3216 }, 3217 { 3218 url: "https://www.test1.com", 3219 image_url: "images/test1-com.png", 3220 click_url: "https://www.test1-click.com", 3221 impression_url: "https://www.test1-impression.com", 3222 name: "test1", 3223 }, 3224 ]; 3225 fetchStub.resolves({ 3226 ok: true, 3227 status: 200, 3228 headers: new Map([ 3229 ["cache-control", "private, max-age=859, stale-if-error=10463"], 3230 ]), 3231 json: () => 3232 Promise.resolve({ 3233 tiles, 3234 }), 3235 }); 3236 3237 let fetched = await feed._contile._fetchSites(); 3238 Assert.ok(fetched); 3239 Assert.ok(feed._contile.cache.set.calledWith("contile", tiles)); 3240 Assert.equal( 3241 Services.prefs.getIntPref(CONTILE_CACHE_VALID_FOR_SECONDS_PREF), 3242 11322 3243 ); 3244 sandbox.restore(); 3245 } 3246 3247 { 3248 info( 3249 "TopSitesFeed._fetchSites should return cached valid tiles " + 3250 "when Contile returns error status" 3251 ); 3252 let { feed, fetchStub } = prepFeed(getTopSitesFeedForTest(sandbox)); 3253 NimbusFeatures.newtab.getVariable.returns(true); 3254 3255 let tiles = [ 3256 { 3257 url: "https://www.test-cached.com", 3258 image_url: "images/test-com.png", 3259 click_url: "https://www.test-click.com", 3260 impression_url: "https://www.test-impression.com", 3261 name: "test", 3262 }, 3263 { 3264 url: "https://www.test1-cached.com", 3265 image_url: "images/test1-com.png", 3266 click_url: "https://www.test1-click.com", 3267 impression_url: "https://www.test1-impression.com", 3268 name: "test1", 3269 }, 3270 ]; 3271 3272 feed._contile.cache.get.returns({ contile: tiles }); 3273 Services.prefs.setIntPref(CONTILE_CACHE_VALID_FOR_SECONDS_PREF, 60 * 15); 3274 Services.prefs.setIntPref( 3275 CONTILE_CACHE_LAST_FETCH_PREF, 3276 Math.round(Date.now() / 1000) 3277 ); 3278 3279 fetchStub.resolves({ 3280 status: 304, 3281 }); 3282 3283 let fetched = await feed._contile._fetchSites(); 3284 Assert.ok(fetched); 3285 Assert.equal(feed._contile.sites.length, 2); 3286 Assert.equal(feed._contile.sites[0].url, "https://www.test-cached.com"); 3287 Assert.equal(feed._contile.sites[1].url, "https://www.test1-cached.com"); 3288 sandbox.restore(); 3289 } 3290 3291 { 3292 info( 3293 "TopSitesFeed._fetchSites should not be successful when contile " + 3294 "returns an error and no valid tiles are cached" 3295 ); 3296 let { feed, fetchStub } = prepFeed(getTopSitesFeedForTest(sandbox)); 3297 NimbusFeatures.newtab.getVariable.returns(true); 3298 3299 feed._contile.cache.get.returns({ contile: [] }); 3300 Services.prefs.setIntPref(CONTILE_CACHE_VALID_FOR_SECONDS_PREF, 0); 3301 Services.prefs.setIntPref(CONTILE_CACHE_LAST_FETCH_PREF, 0); 3302 3303 fetchStub.resolves({ 3304 status: 500, 3305 }); 3306 3307 let fetched = await feed._contile._fetchSites(); 3308 Assert.ok(!fetched); 3309 sandbox.restore(); 3310 } 3311 3312 { 3313 info( 3314 "TopSitesFeed._fetchSites should return cached valid tiles " + 3315 "filtering blocked tiles when Contile returns error status" 3316 ); 3317 let { feed, fetchStub } = prepFeed(getTopSitesFeedForTest(sandbox)); 3318 NimbusFeatures.newtab.getVariable.returns(true); 3319 3320 let tiles = [ 3321 { 3322 url: "https://foo.com", 3323 image_url: "images/foo-com.png", 3324 click_url: "https://www.foo-click.com", 3325 impression_url: "https://www.foo-impression.com", 3326 name: "foo", 3327 }, 3328 { 3329 url: "https://www.test1-cached.com", 3330 image_url: "images/test1-com.png", 3331 click_url: "https://www.test1-click.com", 3332 impression_url: "https://www.test1-impression.com", 3333 name: "test1", 3334 }, 3335 ]; 3336 feed._contile.cache.get.returns({ contile: tiles }); 3337 Services.prefs.setIntPref(CONTILE_CACHE_VALID_FOR_SECONDS_PREF, 60 * 15); 3338 Services.prefs.setIntPref( 3339 CONTILE_CACHE_LAST_FETCH_PREF, 3340 Math.round(Date.now() / 1000) 3341 ); 3342 3343 fetchStub.resolves({ 3344 status: 304, 3345 }); 3346 3347 let fetched = await feed._contile._fetchSites(); 3348 Assert.ok(fetched); 3349 Assert.equal(feed._contile.sites.length, 1); 3350 Assert.equal(feed._contile.sites[0].url, "https://www.test1-cached.com"); 3351 sandbox.restore(); 3352 } 3353 3354 { 3355 info( 3356 "TopSitesFeed._fetchSites should still return 3 tiles when nimbus " + 3357 "variable overrides max num of sponsored contile tiles" 3358 ); 3359 let { feed, fetchStub } = prepFeed(getTopSitesFeedForTest(sandbox)); 3360 NimbusFeatures.newtab.getVariable.returns(true); 3361 3362 sandbox.stub(NimbusFeatures.pocketNewtab, "getVariable").returns(3); 3363 fetchStub.resolves({ 3364 ok: true, 3365 status: 200, 3366 headers: new Map([ 3367 ["cache-control", "private, max-age=859, stale-if-error=10463"], 3368 ]), 3369 json: () => 3370 Promise.resolve({ 3371 tiles: [ 3372 { 3373 url: "https://www.test.com", 3374 image_url: "images/test-com.png", 3375 click_url: "https://www.test-click.com", 3376 impression_url: "https://www.test-impression.com", 3377 name: "test", 3378 }, 3379 { 3380 url: "https://test1.com", 3381 image_url: "images/test1-com.png", 3382 click_url: "https://www.test1-click.com", 3383 impression_url: "https://www.test1-impression.com", 3384 name: "test1", 3385 }, 3386 { 3387 url: "https://test2.com", 3388 image_url: "images/test2-com.png", 3389 click_url: "https://www.test2-click.com", 3390 impression_url: "https://www.test2-impression.com", 3391 name: "test2", 3392 }, 3393 ], 3394 }), 3395 }); 3396 3397 let fetched = await feed._contile._fetchSites(); 3398 3399 Assert.ok(fetched); 3400 Assert.equal(feed._contile.sites.length, 3); 3401 Assert.equal(feed._contile.sites[0].url, "https://www.test.com"); 3402 Assert.equal(feed._contile.sites[1].url, "https://test1.com"); 3403 Assert.equal(feed._contile.sites[2].url, "https://test2.com"); 3404 sandbox.restore(); 3405 } 3406 3407 { 3408 info( 3409 "TopSitesFeed._fetchSites should cast headers from a Headers object to JS object when using OHTTP" 3410 ); 3411 let { feed, fetchStub } = prepFeed(getTopSitesFeedForTest(sandbox)); 3412 3413 Services.prefs.setStringPref( 3414 "browser.newtabpage.activity-stream.discoverystream.ohttp.relayURL", 3415 "https://relay.url" 3416 ); 3417 Services.prefs.setStringPref( 3418 "browser.newtabpage.activity-stream.discoverystream.ohttp.configURL", 3419 "https://config.url" 3420 ); 3421 Services.prefs.setBoolPref( 3422 "browser.newtabpage.activity-stream.unifiedAds.ohttp.enabled", 3423 true 3424 ); 3425 feed.store.state.Prefs.values["unifiedAds.tiles.enabled"] = true; 3426 feed.store.state.Prefs.values["unifiedAds.adsFeed.enabled"] = false; 3427 feed.store.state.Prefs.values["unifiedAds.endpoint"] = 3428 "https://test.endpoint/"; 3429 feed.store.state.Prefs.values["discoverystream.placements.tiles"] = "1"; 3430 feed.store.state.Prefs.values["discoverystream.placements.tiles.counts"] = 3431 "1"; 3432 feed.store.state.Prefs.values["unifiedAds.blockedAds"] = ""; 3433 3434 const TEST_PREFLIGHT_UA_STRING = "Some test UA"; 3435 const TEST_PREFLIGHT_GEONAME_ID = "Some geo name"; 3436 const TEST_PREFLIGHT_GEO_LOCATION = "Some geo location"; 3437 3438 fetchStub.resolves({ 3439 ok: true, 3440 status: 200, 3441 json: () => 3442 Promise.resolve({ 3443 normalized_ua: TEST_PREFLIGHT_UA_STRING, 3444 geoname_id: TEST_PREFLIGHT_GEONAME_ID, 3445 geo_location: TEST_PREFLIGHT_GEO_LOCATION, 3446 }), 3447 }); 3448 3449 const fakeOhttpConfig = { config: "config" }; 3450 sandbox.stub(ObliviousHTTP, "getOHTTPConfig").resolves(fakeOhttpConfig); 3451 3452 const ohttpRequestStub = sandbox 3453 .stub(ObliviousHTTP, "ohttpRequest") 3454 .resolves({ 3455 ok: true, 3456 status: 200, 3457 headers: new Map([ 3458 ["cache-control", "private, max-age=859, stale-if-error=10463"], 3459 ]), 3460 json: () => 3461 Promise.resolve({ 3462 1: [ 3463 { 3464 block_key: 12345, 3465 name: "test", 3466 url: "https://www.test.com", 3467 image_url: "images/test-com.png", 3468 callbacks: { 3469 click: "https://www.test-click.com", 3470 impression: "https://www.test-impression.com", 3471 }, 3472 }, 3473 ], 3474 }), 3475 }); 3476 3477 let fetched = await feed._contile._fetchSites(); 3478 3479 Assert.ok(fetchStub.calledOnce, "The preflight request was made."); 3480 3481 Assert.ok(fetched); 3482 Assert.ok( 3483 ohttpRequestStub.calledOnce, 3484 "ohttpRequest should be called once" 3485 ); 3486 const callArgs = ohttpRequestStub.getCall(0).args; 3487 Assert.equal(callArgs[0], "https://relay.url", "relay URL should match"); 3488 Assert.deepEqual( 3489 callArgs[1], 3490 fakeOhttpConfig, 3491 "config should be passed through" 3492 ); 3493 3494 const sentHeaders = callArgs[3].headers; 3495 Assert.equal( 3496 typeof sentHeaders, 3497 "object", 3498 "headers should be a plain object" 3499 ); 3500 Assert.ok( 3501 // We use instanceof here since isInstance isn't available for 3502 // Headers, it seems. 3503 // eslint-disable-next-line mozilla/use-isInstance 3504 !(sentHeaders instanceof Headers), 3505 "headers should not be a Headers instance" 3506 ); 3507 3508 Assert.equal( 3509 sentHeaders["x-user-agent"], 3510 TEST_PREFLIGHT_UA_STRING, 3511 "Sent the x-user-agent header from preflight" 3512 ); 3513 Assert.equal( 3514 sentHeaders["x-geoname-id"], 3515 TEST_PREFLIGHT_GEONAME_ID, 3516 "Sent the x-geoname-id header from preflight" 3517 ); 3518 Assert.equal( 3519 sentHeaders["x-geo-location"], 3520 TEST_PREFLIGHT_GEO_LOCATION, 3521 "Sent the x-geo-location header from preflight" 3522 ); 3523 3524 info("TopSitesFeed._fetchSites should not send cookies via OHTTP"); 3525 Assert.equal(callArgs[3].credentials, "omit", "should not send cookies"); 3526 3527 Services.prefs.clearUserPref( 3528 "browser.newtabpage.activity-stream.discoverystream.ohttp.relayURL" 3529 ); 3530 Services.prefs.clearUserPref( 3531 "browser.newtabpage.activity-stream.discoverystream.ohttp.configURL" 3532 ); 3533 Services.prefs.clearUserPref( 3534 "browser.newtabpage.activity-stream.unifiedAds.ohttp.enabled" 3535 ); 3536 sandbox.restore(); 3537 } 3538 3539 Services.prefs.clearUserPref(TOP_SITES_BLOCKED_SPONSORS_PREF); 3540 });