tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_FaviconProvider.js (12441B)


      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  FaviconProvider: "resource:///modules/topsites/TopSites.sys.mjs",
      8  HttpServer: "resource://testing-common/httpd.sys.mjs",
      9  NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
     10  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
     11  PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
     12  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
     13  TestUtils: "resource://testing-common/TestUtils.sys.mjs",
     14  sinon: "resource://testing-common/Sinon.sys.mjs",
     15 });
     16 
     17 let TEST_FAVICON_FILE;
     18 let TEST_DOMAIN;
     19 let TEST_PAGE_URL;
     20 let TEST_FAVICON_URL;
     21 
     22 const TEST_SVG_DATA_URL = Services.io.newURI(
     23  "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy5" +
     24    "3My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBmaWxs" +
     25    "PSIjNDI0ZTVhIj4NCiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iN" +
     26    "DQiIHN0cm9rZT0iIzQyNGU1YSIgc3Ryb2tlLXdpZHRoPSIxMSIgZmlsbD" +
     27    "0ibm9uZSIvPg0KICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjI0LjYiIHI9IjY" +
     28    "uNCIvPg0KICA8cmVjdCB4PSI0NSIgeT0iMzkuOSIgd2lkdGg9IjEwLjEi" +
     29    "IGhlaWdodD0iNDEuOCIvPg0KPC9zdmc%2BDQo%3D"
     30 );
     31 
     32 add_setup(async function setup() {
     33  info("Setup profile to use Places DB");
     34  do_get_profile(true);
     35 
     36  info("Setup favicon data");
     37  TEST_FAVICON_FILE = do_get_file("favicon.png");
     38 
     39  info("Setup http server that returns favicon content");
     40  const httpServer = new HttpServer();
     41  httpServer.registerPathHandler("/favicon.png", (request, response) => {
     42    const inputStream = Cc[
     43      "@mozilla.org/network/file-input-stream;1"
     44    ].createInstance(Ci.nsIFileInputStream);
     45    inputStream.init(TEST_FAVICON_FILE, 0x01, -1, null);
     46    const size = inputStream.available();
     47    const faviconData = NetUtil.readInputStreamToString(inputStream, size);
     48 
     49    response.setStatusLine(request.httpVersion, 200, "Ok");
     50    response.setHeader("Content-Type", "image/png", false);
     51    response.bodyOutputStream.write(faviconData, faviconData.length);
     52  });
     53  httpServer.start(-1);
     54 
     55  TEST_DOMAIN = "localhost";
     56  TEST_PAGE_URL = Services.io.newURI(
     57    `http://${TEST_DOMAIN}:${httpServer.identity.primaryPort}`
     58  );
     59  TEST_FAVICON_URL = Services.io.newURI(
     60    `http://${TEST_DOMAIN}:${httpServer.identity.primaryPort}/favicon.png`
     61  );
     62 
     63  info("Setup visit data in DB");
     64  await PlacesTestUtils.addVisits(TEST_PAGE_URL);
     65 
     66  // Save the original favicon service
     67  let originalFaviconService = PlacesUtils.favicons;
     68 
     69  registerCleanupFunction(async () => {
     70    // Restore the original favicon service
     71    PlacesUtils.favicons = originalFaviconService;
     72    await new Promise(resolve => httpServer.stop(resolve));
     73    await PlacesUtils.history.clear();
     74  });
     75 });
     76 
     77 // Test for getFaviconDataURLFromNetwork() function.
     78 add_task(async function test_getFaviconDataURLFromNetwork() {
     79  const feed = new FaviconProvider();
     80 
     81  info("Get favicon data via FaviconProvider");
     82  const result = await feed.getFaviconDataURLFromNetwork(TEST_FAVICON_URL);
     83  Assert.equal(
     84    result.spec,
     85    // eslint-disable-next-line no-use-before-define
     86    await readFileDataAsDataURL(TEST_FAVICON_FILE, "image/png"),
     87    "getFaviconDataURLFromNetwork returns correct data url"
     88  );
     89 });
     90 
     91 // Test for fetchIcon() function. If there is valid favicon data for the page in
     92 // DB, not update.
     93 add_task(async function test_fetchIcon_with_valid_favicon() {
     94  const feed = new FaviconProvider();
     95 
     96  info("Setup stub to use dummy site data from FaviconProvider.getSite()");
     97  const sandbox = sinon.createSandbox();
     98  sandbox
     99    .stub(feed, "getSite")
    100    .resolves({ domain: TEST_DOMAIN, image_url: TEST_FAVICON_URL.spec });
    101 
    102  info("Setup valid favicon data in DB");
    103  await PlacesUtils.favicons.setFaviconForPage(
    104    TEST_PAGE_URL,
    105    TEST_FAVICON_URL,
    106    TEST_SVG_DATA_URL
    107  );
    108 
    109  info("Call FaviconProvider.fetchIcon()");
    110  await feed.fetchIcon(TEST_PAGE_URL.spec);
    111 
    112  info("Check the database");
    113  const result = await PlacesUtils.favicons.getFaviconForPage(TEST_PAGE_URL);
    114  Assert.equal(result.mimeType, "image/svg+xml");
    115  Assert.equal(result.width, 65535);
    116 
    117  info("Clean up");
    118  await PlacesTestUtils.clearFavicons();
    119 });
    120 
    121 // Test for fetchIcon() function. If there is favicon data in DB but invalid or
    122 // is not in DB, get favicon data from network, and update the DB with it.
    123 add_task(async function test_fetchIcon_with_invalid_favicon() {
    124  for (const dummyFaviconInfo of [
    125    null,
    126    { iconUri: TEST_PAGE_URL, faviconSize: 0 },
    127  ]) {
    128    info(`Test for ${dummyFaviconInfo}`);
    129    const feed = new FaviconProvider();
    130 
    131    info("Setup stub to use dummy site data from FaviconProvider.getSite()");
    132    const sandbox = sinon.createSandbox();
    133    sandbox
    134      .stub(feed, "getSite")
    135      .resolves({ domain: TEST_DOMAIN, image_url: TEST_FAVICON_URL.spec });
    136 
    137    info("Setup stub to simulate invalid favicon");
    138    sandbox.stub(feed, "getFaviconInfo").resolves(dummyFaviconInfo);
    139 
    140    info("Call FaviconProvider.fetchIcon()");
    141    await feed.fetchIcon(TEST_PAGE_URL.spec);
    142 
    143    info("Check the database");
    144    const result = await PlacesUtils.favicons.getFaviconForPage(TEST_PAGE_URL);
    145    // eslint-disable-next-line no-use-before-define
    146    const expectedFaviconData = readFileData(TEST_FAVICON_FILE);
    147    Assert.equal(result.uri.spec, `${TEST_FAVICON_URL.spec}#tippytop`);
    148    Assert.deepEqual(result.rawData, expectedFaviconData);
    149    Assert.equal(result.mimeType, "image/png");
    150    Assert.equal(result.width, 16);
    151 
    152    info("Clean up");
    153    await PlacesTestUtils.clearFavicons();
    154    sandbox.restore();
    155  }
    156 });
    157 
    158 // Test for fetchIconFromRedirects() function. If there is valid favicon data
    159 // for the redirected page in DB, copy the favicon data to the destination page as well.
    160 add_task(async function test_fetchIconFromRedirects_with_valid_favicon() {
    161  const feed = new FaviconProvider();
    162 
    163  info("Setup stub to use dummy site data from FaviconProvider.getSite()");
    164  const sandbox = sinon.createSandbox();
    165  sandbox
    166    .stub(NewTabUtils.activityStreamProvider, "executePlacesQuery")
    167    .resolves([
    168      { visit_id: 1, url: TEST_DOMAIN },
    169      { visit_id: 2, url: TEST_PAGE_URL.spec },
    170    ]);
    171 
    172  info("Setup valid favicon data in DB");
    173  await PlacesUtils.favicons.setFaviconForPage(
    174    TEST_PAGE_URL,
    175    TEST_FAVICON_URL,
    176    TEST_SVG_DATA_URL
    177  );
    178 
    179  info("Setup destination");
    180  const destination = Services.io.newURI("http://destination.localhost/");
    181  await PlacesTestUtils.addVisits(destination);
    182 
    183  info("Call FaviconProvider.fetchIconFromRedirects()");
    184  await feed.fetchIconFromRedirects(destination.spec);
    185 
    186  info("Check the database");
    187  await TestUtils.waitForCondition(async () => {
    188    const result = await PlacesUtils.favicons.getFaviconForPage(destination);
    189    return !!result;
    190  });
    191  const sourceResult =
    192    await PlacesUtils.favicons.getFaviconForPage(TEST_PAGE_URL);
    193  const destinationResult =
    194    await PlacesUtils.favicons.getFaviconForPage(destination);
    195  Assert.deepEqual(destinationResult.rawData, sourceResult.rawData);
    196  Assert.equal(destinationResult.mimeType, sourceResult.mimeType);
    197  Assert.equal(destinationResult.width, sourceResult.width);
    198 
    199  info("Clean up");
    200  await PlacesTestUtils.clearFavicons();
    201  sandbox.restore();
    202 });
    203 
    204 // Copy from toolkit/components/places/tests/head_common.js
    205 function readInputStreamData(aStream) {
    206  let bistream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
    207    Ci.nsIBinaryInputStream
    208  );
    209  try {
    210    bistream.setInputStream(aStream);
    211    let expectedData = [];
    212    let avail;
    213    while ((avail = bistream.available())) {
    214      expectedData = expectedData.concat(bistream.readByteArray(avail));
    215    }
    216    return expectedData;
    217  } finally {
    218    bistream.close();
    219  }
    220 }
    221 
    222 function readFileData(aFile) {
    223  let inputStream = Cc[
    224    "@mozilla.org/network/file-input-stream;1"
    225  ].createInstance(Ci.nsIFileInputStream);
    226  // init the stream as RD_ONLY, -1 == default permissions.
    227  inputStream.init(aFile, 0x01, -1, null);
    228 
    229  // Check the returned size versus the expected size.
    230  let size = inputStream.available();
    231  let bytes = readInputStreamData(inputStream);
    232  if (size !== bytes.length) {
    233    throw new Error("Didn't read expected number of bytes");
    234  }
    235  return bytes;
    236 }
    237 
    238 async function fileDataToDataURL(data, mimeType) {
    239  const dataURL = await new Promise(resolve => {
    240    const buffer = new Uint8ClampedArray(data);
    241    const blob = new Blob([buffer], { type: mimeType });
    242    const reader = new FileReader();
    243    reader.onload = e => {
    244      resolve(e.target.result);
    245    };
    246    reader.readAsDataURL(blob);
    247  });
    248  return dataURL;
    249 }
    250 
    251 async function readFileDataAsDataURL(file, mimeType) {
    252  const data = readFileData(file);
    253  return fileDataToDataURL(data, mimeType);
    254 }
    255 
    256 const FAKE_SMALLPNG_DATA_URI =
    257  "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==";
    258 
    259 // Test: fetchIcon should fetch favicon from network if no data in DB
    260 add_task(async function test_fetchIcon_withNetworkFetch() {
    261  const sandbox = sinon.createSandbox();
    262  let feed = new FaviconProvider();
    263  let url = "https://mozilla.org/";
    264 
    265  // Set up mocks
    266  PlacesUtils.favicons = {
    267    getFaviconForPage: sandbox.stub().returns(Promise.resolve(null)),
    268    setFaviconForPage: sandbox.spy(),
    269    copyFavicons: sandbox.spy(),
    270  };
    271  feed.getSite = sandbox
    272    .stub()
    273    .returns(
    274      Promise.resolve({ domain: "mozilla.org", image_url: `${url}/icon.png` })
    275    );
    276 
    277  // Mock the getFaviconDataURLFromNetwork method
    278  sandbox
    279    .stub(feed, "getFaviconDataURLFromNetwork")
    280    .resolves({ spec: FAKE_SMALLPNG_DATA_URI });
    281 
    282  await feed.fetchIcon(url);
    283 
    284  // Assertions
    285  Assert.equal(PlacesUtils.favicons.setFaviconForPage.calledOnce, true);
    286  Assert.equal(
    287    PlacesUtils.favicons.setFaviconForPage.firstCall.args[2].spec,
    288    FAKE_SMALLPNG_DATA_URI
    289  );
    290 
    291  sandbox.restore();
    292 });
    293 
    294 // Test: fetchIcon should fetch favicon from network if invalid data in DB
    295 add_task(async function test_fetchIcon_withInvalidDataInDb() {
    296  const sandbox = sinon.createSandbox();
    297  // Set up mocks
    298  PlacesUtils.favicons = {
    299    // Invalid since no width.
    300    getFaviconForPage: sandbox
    301      .stub()
    302      .returns(Promise.resolve({ iconUri: { spec: FAKE_SMALLPNG_DATA_URI } })),
    303    setFaviconForPage: sandbox.spy(),
    304    copyFavicons: sandbox.spy(),
    305  };
    306 
    307  let feed = new FaviconProvider();
    308  let url = "https://mozilla.org/";
    309  feed.getSite = sandbox
    310    .stub()
    311    .returns(
    312      Promise.resolve({ domain: "mozilla.org", image_url: `${url}/icon.png` })
    313    );
    314 
    315  // Mock the getFaviconDataURLFromNetwork method
    316  sandbox
    317    .stub(feed, "getFaviconDataURLFromNetwork")
    318    .resolves({ spec: FAKE_SMALLPNG_DATA_URI });
    319 
    320  await feed.fetchIcon(url);
    321 
    322  // Assertions
    323  Assert.equal(PlacesUtils.favicons.setFaviconForPage.calledOnce, true);
    324  Assert.equal(
    325    PlacesUtils.favicons.setFaviconForPage.firstCall.args[2].spec,
    326    FAKE_SMALLPNG_DATA_URI
    327  );
    328 
    329  sandbox.restore();
    330 });
    331 
    332 // Test: fetchIcon should not set favicon if valid data exists in DB
    333 add_task(async function test_fetchIcon_withValidDataInDb() {
    334  const sandbox = sinon.createSandbox();
    335  // Set up mocks
    336  PlacesUtils.favicons = {
    337    getFaviconForPage: sandbox.stub().returns(
    338      Promise.resolve({
    339        iconUri: { spec: FAKE_SMALLPNG_DATA_URI },
    340        width: 100,
    341      })
    342    ),
    343    setFaviconForPage: sandbox.spy(),
    344    copyFavicons: sandbox.spy(),
    345  };
    346  let feed = new FaviconProvider();
    347  let url = "https://mozilla.org/";
    348  feed.getSite = sandbox
    349    .stub()
    350    .returns(
    351      Promise.resolve({ domain: "mozilla.org", image_url: `${url}/icon.png` })
    352    );
    353 
    354  await feed.fetchIcon(url);
    355 
    356  // Assertions
    357  Assert.equal(PlacesUtils.favicons.setFaviconForPage.called, false);
    358 
    359  sandbox.restore();
    360 });
    361 
    362 // Test: fetchIcon should not set favicon if the URL is not in TippyTop data
    363 add_task(async function test_fetchIcon_withNoTippyTopData() {
    364  const sandbox = sinon.createSandbox();
    365  let feed = new FaviconProvider();
    366  // Set up mocks
    367  PlacesUtils.favicons = {
    368    getFaviconForPage: sandbox.stub().returns(Promise.resolve(null)),
    369    setFaviconForPage: sandbox.spy(),
    370    copyFavicons: sandbox.spy(),
    371  };
    372  feed.getSite = sandbox.stub().returns(Promise.resolve(null));
    373 
    374  await feed.fetchIcon("https://example.com");
    375 
    376  // Assertions
    377  Assert.equal(PlacesUtils.favicons.setFaviconForPage.called, false);
    378 
    379  sandbox.restore();
    380 });