/* Any copyright is dedicated to the Public Domain.
 *    http://creativecommons.org/publicdomain/zero/1.0/ */

/*
 * Tests for replaceFaviconData()
 */

var iconsvc = PlacesUtils.favicons;

var originalFavicon = {
  file: do_get_file("favicon-normal16.png"),
  uri: uri(do_get_file("favicon-normal16.png")),
  data: readFileData(do_get_file("favicon-normal16.png")),
  mimetype: "image/png",
};

var uniqueFaviconId = 0;
function createFavicon(fileName) {
  let tempdir = Services.dirsvc.get("TmpD", Ci.nsIFile);

  // remove any existing file at the path we're about to copy to
  let outfile = tempdir.clone();
  outfile.append(fileName);
  try {
    outfile.remove(false);
  } catch (e) {}

  originalFavicon.file.copyToFollowingLinks(tempdir, fileName);

  let stream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
    Ci.nsIFileOutputStream
  );
  stream.init(outfile, 0x02 | 0x08 | 0x10, 0o600, 0);

  // append some data that sniffers/encoders will ignore that will distinguish
  // the different favicons we'll create
  uniqueFaviconId++;
  let uniqueStr = "uid:" + uniqueFaviconId;
  stream.write(uniqueStr, uniqueStr.length);
  stream.close();

  Assert.equal(outfile.leafName.substr(0, fileName.length), fileName);

  return {
    file: outfile,
    uri: uri(outfile),
    data: readFileData(outfile),
    mimetype: "image/png",
  };
}

function checkCallbackSucceeded(
  callbackMimetype,
  callbackData,
  sourceMimetype,
  sourceData
) {
  Assert.equal(callbackMimetype, sourceMimetype);
  Assert.ok(compareArrays(callbackData, sourceData));
}

function run_test() {
  // check that the favicon loaded correctly
  Assert.equal(originalFavicon.data.length, 286);
  run_next_test();
}

add_task(async function test_replaceFaviconData_validHistoryURI() {
  info("test replaceFaviconData for valid history uri");

  let pageURI = uri("http://test1.bar/");
  await PlacesTestUtils.addVisits(pageURI);

  let favicon = createFavicon("favicon1.png");

  iconsvc.replaceFaviconData(favicon.uri, favicon.data, favicon.mimetype);

  await new Promise(resolve => {
    iconsvc.setAndFetchFaviconForPage(
      pageURI,
      favicon.uri,
      true,
      PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
      function test_replaceFaviconData_validHistoryURI_check(
        aURI,
        aDataLen,
        aData,
        aMimeType
      ) {
        dump("GOT " + aMimeType + "\n");
        checkCallbackSucceeded(
          aMimeType,
          aData,
          favicon.mimetype,
          favicon.data
        );
        checkFaviconDataForPage(
          pageURI,
          favicon.mimetype,
          favicon.data,
          function test_replaceFaviconData_validHistoryURI_callback() {
            favicon.file.remove(false);
            resolve();
          }
        );
      },
      systemPrincipal
    );
  });

  await PlacesUtils.history.clear();
});

add_task(async function test_replaceFaviconData_overrideDefaultFavicon() {
  info("test replaceFaviconData to override a later setAndFetchFaviconForPage");

  let pageURI = uri("http://test2.bar/");
  await PlacesTestUtils.addVisits(pageURI);

  let firstFavicon = createFavicon("favicon2.png");
  let secondFavicon = createFavicon("favicon3.png");

  iconsvc.replaceFaviconData(
    firstFavicon.uri,
    secondFavicon.data,
    secondFavicon.mimetype
  );

  await new Promise(resolve => {
    iconsvc.setAndFetchFaviconForPage(
      pageURI,
      firstFavicon.uri,
      true,
      PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
      function test_replaceFaviconData_overrideDefaultFavicon_check(
        aURI,
        aDataLen,
        aData,
        aMimeType
      ) {
        checkCallbackSucceeded(
          aMimeType,
          aData,
          secondFavicon.mimetype,
          secondFavicon.data
        );
        checkFaviconDataForPage(
          pageURI,
          secondFavicon.mimetype,
          secondFavicon.data,
          function test_replaceFaviconData_overrideDefaultFavicon_callback() {
            firstFavicon.file.remove(false);
            secondFavicon.file.remove(false);
            resolve();
          }
        );
      },
      systemPrincipal
    );
  });

  await PlacesUtils.history.clear();
});

add_task(async function test_replaceFaviconData_replaceExisting() {
  info(
    "test replaceFaviconData to override a previous setAndFetchFaviconForPage"
  );

  let pageURI = uri("http://test3.bar");
  await PlacesTestUtils.addVisits(pageURI);

  let firstFavicon = createFavicon("favicon4.png");
  let secondFavicon = createFavicon("favicon5.png");

  await new Promise(resolve => {
    iconsvc.setAndFetchFaviconForPage(
      pageURI,
      firstFavicon.uri,
      true,
      PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
      function test_replaceFaviconData_replaceExisting_firstSet_check(
        aURI,
        aDataLen,
        aData,
        aMimeType
      ) {
        checkCallbackSucceeded(
          aMimeType,
          aData,
          firstFavicon.mimetype,
          firstFavicon.data
        );
        checkFaviconDataForPage(
          pageURI,
          firstFavicon.mimetype,
          firstFavicon.data,
          function test_replaceFaviconData_overrideDefaultFavicon_firstCallback() {
            iconsvc.replaceFaviconData(
              firstFavicon.uri,
              secondFavicon.data,
              secondFavicon.mimetype
            );
            PlacesTestUtils.promiseAsyncUpdates().then(() => {
              checkFaviconDataForPage(
                pageURI,
                secondFavicon.mimetype,
                secondFavicon.data,
                function test_replaceFaviconData_overrideDefaultFavicon_secondCallback() {
                  firstFavicon.file.remove(false);
                  secondFavicon.file.remove(false);
                  resolve();
                },
                systemPrincipal
              );
            });
          }
        );
      },
      systemPrincipal
    );
  });

  await PlacesUtils.history.clear();
});

add_task(async function test_replaceFaviconData_unrelatedReplace() {
  info("test replaceFaviconData to not make unrelated changes");

  let pageURI = uri("http://test4.bar/");
  await PlacesTestUtils.addVisits(pageURI);

  let favicon = createFavicon("favicon6.png");
  let unrelatedFavicon = createFavicon("favicon7.png");

  iconsvc.replaceFaviconData(
    unrelatedFavicon.uri,
    unrelatedFavicon.data,
    unrelatedFavicon.mimetype
  );

  await new Promise(resolve => {
    iconsvc.setAndFetchFaviconForPage(
      pageURI,
      favicon.uri,
      true,
      PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
      function test_replaceFaviconData_unrelatedReplace_check(
        aURI,
        aDataLen,
        aData,
        aMimeType
      ) {
        checkCallbackSucceeded(
          aMimeType,
          aData,
          favicon.mimetype,
          favicon.data
        );
        checkFaviconDataForPage(
          pageURI,
          favicon.mimetype,
          favicon.data,
          function test_replaceFaviconData_unrelatedReplace_callback() {
            favicon.file.remove(false);
            unrelatedFavicon.file.remove(false);
            resolve();
          }
        );
      },
      systemPrincipal
    );
  });

  await PlacesUtils.history.clear();
});

add_task(async function test_replaceFaviconData_badInputs() {
  info("test replaceFaviconData to throw on bad inputs");
  let icon = createFavicon("favicon8.png");

  Assert.throws(
    () => iconsvc.replaceFaviconData(icon.uri, icon.data, ""),
    /NS_ERROR_ILLEGAL_VALUE/
  );
  Assert.throws(
    () => iconsvc.replaceFaviconData(icon.uri, icon.data, "not-an-image"),
    /NS_ERROR_ILLEGAL_VALUE/
  );
  Assert.throws(
    () => iconsvc.replaceFaviconData(null, icon.data, icon.mimetype),
    /NS_ERROR_ILLEGAL_VALUE/
  );
  Assert.throws(
    () => iconsvc.replaceFaviconData(icon.uri, [], icon.mimetype),
    /NS_ERROR_ILLEGAL_VALUE/
  );
  Assert.throws(
    () => iconsvc.replaceFaviconData(icon.uri, null, icon.mimetype),
    /NS_ERROR_XPC_CANT_CONVERT_PRIMITIVE_TO_ARRAY/
  );

  icon.file.remove(false);
  await PlacesUtils.history.clear();
});

add_task(async function test_replaceFaviconData_twiceReplace() {
  info("test replaceFaviconData on multiple replacements");

  let pageURI = uri("http://test5.bar/");
  await PlacesTestUtils.addVisits(pageURI);

  let firstFavicon = createFavicon("favicon9.png");
  let secondFavicon = createFavicon("favicon10.png");

  iconsvc.replaceFaviconData(
    firstFavicon.uri,
    firstFavicon.data,
    firstFavicon.mimetype
  );
  iconsvc.replaceFaviconData(
    firstFavicon.uri,
    secondFavicon.data,
    secondFavicon.mimetype
  );

  await new Promise(resolve => {
    iconsvc.setAndFetchFaviconForPage(
      pageURI,
      firstFavicon.uri,
      true,
      PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
      function test_replaceFaviconData_twiceReplace_check(
        aURI,
        aDataLen,
        aData,
        aMimeType
      ) {
        checkCallbackSucceeded(
          aMimeType,
          aData,
          secondFavicon.mimetype,
          secondFavicon.data
        );
        checkFaviconDataForPage(
          pageURI,
          secondFavicon.mimetype,
          secondFavicon.data,
          function test_replaceFaviconData_twiceReplace_callback() {
            firstFavicon.file.remove(false);
            secondFavicon.file.remove(false);
            resolve();
          },
          systemPrincipal
        );
      },
      systemPrincipal
    );
  });

  await PlacesUtils.history.clear();
});

add_task(async function test_replaceFaviconData_rootOverwrite() {
  info("test replaceFaviconData doesn't overwrite root = 1");

  async function getRootValue(url) {
    let db = await PlacesUtils.promiseDBConnection();
    let rows = await db.execute(
      "SELECT root FROM moz_icons WHERE icon_url = :url",
      { url }
    );
    return rows[0].getResultByName("root");
  }

  const PAGE_URL = "http://rootoverwrite.bar/";
  let pageURI = Services.io.newURI(PAGE_URL);
  const ICON_URL = "http://rootoverwrite.bar/favicon.ico";
  let iconURI = Services.io.newURI(ICON_URL);

  await PlacesTestUtils.addVisits(pageURI);

  let icon = createFavicon("favicon9.png");
  PlacesUtils.favicons.replaceFaviconData(iconURI, icon.data, icon.mimetype);
  await PlacesTestUtils.addFavicons(new Map([[PAGE_URL, ICON_URL]]));

  Assert.equal(await getRootValue(ICON_URL), 1, "Check root == 1");
  let icon2 = createFavicon("favicon10.png");
  PlacesUtils.favicons.replaceFaviconData(iconURI, icon2.data, icon2.mimetype);
  // replaceFaviconData doesn't have a callback, but we must wait its updated.
  await PlacesTestUtils.promiseAsyncUpdates();
  Assert.equal(await getRootValue(ICON_URL), 1, "Check root did not change");

  await PlacesUtils.history.clear();
});
