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

"use strict";

const { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);
const { PlacesTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/PlacesTestUtils.sys.mjs"
);

Assert.equal(AppConstants.platform, "win", "Is running under Windows");

const TASKBAR_SERVICE = Cc["@mozilla.org/windows-taskbar;1"].getService(
  Ci.nsIWinTaskbar
);
Assert.ok(TASKBAR_SERVICE, "Got nsIWinTaskBar");

const JUMP_LIST_BUILDER = TASKBAR_SERVICE.createJumpListBuilder(
  false /* aPrivateBrowsing */
);
Assert.ok(JUMP_LIST_BUILDER, "Got an nsIJumpListBuilder");

// The Windows Jump List favicon cache is in the profile directory, so let's
// make sure that a profile exists.
do_get_profile();

// See FaviconHelper::kJumpListCacheDir
const JUMP_LIST_CACHE_FOLDER = "jumpListCache";
const JUMP_LIST_CACHE_PATH = PathUtils.join(
  PathUtils.profileDir,
  JUMP_LIST_CACHE_FOLDER
);
const TEST_PAGE_URI = Services.io.newURI("https://widget.test");
const TEST_FAVICON_URI = Services.io.newURI(
  `${TEST_PAGE_URI.spec}/favicon.ico`
);
const FAVICON_MIME_TYPE = "image/x-icon";

/**
 * Clears the contents of the Windows Jump List favicon cache.
 *
 * @returns {Promise<undefined>}
 */
async function emptyFaviconCache() {
  await IOUtils.remove(JUMP_LIST_CACHE_PATH, {
    ignoreAbsent: true,
    recursive: true,
  });
  await IOUtils.makeDirectory(JUMP_LIST_CACHE_PATH);
}

add_setup(async () => {
  const FAVICON_BYTES = await IOUtils.read(do_get_file("favicon.ico").path);
  Assert.ok(FAVICON_BYTES.length, "Was able to load the testing favicon.");

  await PlacesTestUtils.addVisits(TEST_PAGE_URI);
  let dataURL = await new Promise((resolve, reject) => {
    let buffer = new Uint8ClampedArray(FAVICON_BYTES);
    let blob = new Blob([buffer], { type: FAVICON_MIME_TYPE });
    let reader = new FileReader();
    reader.addEventListener("load", () => resolve(reader.result));
    reader.addEventListener("error", reject);
    reader.readAsDataURL(blob);
  });

  await PlacesTestUtils.setFaviconForPage(
    TEST_PAGE_URI,
    TEST_FAVICON_URI,
    dataURL
  );
});

/**
 * Test that we can populate the Windows Jump List favicon cache with a known
 * favicon from the favicon database.
 */
add_task(async function test_write_favicon_cache() {
  // Let's make sure that the Windows Jump List favicon cache folder is empty
  // to start.
  await emptyFaviconCache();

  let firstCallCachePath =
    await JUMP_LIST_BUILDER.obtainAndCacheFaviconAsync(TEST_PAGE_URI);
  Assert.ok(firstCallCachePath, "Got a non-empty cache path");

  Assert.ok(
    await IOUtils.exists(firstCallCachePath),
    "A file exists at the cache path"
  );
  let cacheBytes = await IOUtils.read(firstCallCachePath);
  let sniffer = Cc["@mozilla.org/image/loader;1"].createInstance(
    Ci.nsIContentSniffer
  );

  // Now test that if we re-request the same favicon, that we don't write the
  // same favicon to disk - but continue to use the existing cached file.
  Assert.equal(
    sniffer.getMIMETypeFromContent(null, cacheBytes, cacheBytes.length),
    FAVICON_MIME_TYPE,
    "An image was written to the path."
  );
  let { lastModified } = await IOUtils.stat(firstCallCachePath);

  let secondCallCachePath =
    await JUMP_LIST_BUILDER.obtainAndCacheFaviconAsync(TEST_PAGE_URI);
  Assert.equal(
    secondCallCachePath,
    firstCallCachePath,
    "Got back the same path the second time."
  );
  Assert.equal(
    (await IOUtils.stat(secondCallCachePath)).lastModified,
    lastModified,
    "The file did not get written to again."
  );
});

/**
 * Test that if we request to cache a favicon that doesn't exist within the
 * favicon database that we reject the Promise with a result code.
 */
add_task(async function test_invalid_favicon() {
  // Let's make sure that the Windows Jump List favicon cache folder is empty
  // to start.
  await emptyFaviconCache();
  await Assert.rejects(
    JUMP_LIST_BUILDER.obtainAndCacheFaviconAsync(
      Services.io.newURI("https://page.that.does.not.exist.com")
    ),
    /NS_ERROR_FAILURE/
  );

  // And ensure that no favicons got written despite the error.
  let { length: numberOfFavicons } =
    await IOUtils.getChildren(JUMP_LIST_CACHE_PATH);
  Assert.equal(numberOfFavicons, 0, "No favicons were written");
});

/**
 * Test that we will overwrite the favicon cache with a fresh copy if the cache
 * has expired.
 */
add_task(async function test_expired_favicon_cache() {
  // Let's make sure that the Windows Jump List favicon cache folder is empty
  // to start.
  await emptyFaviconCache();

  let firstCallCachePath =
    await JUMP_LIST_BUILDER.obtainAndCacheFaviconAsync(TEST_PAGE_URI);
  Assert.ok(firstCallCachePath, "Got a non-empty cache path");
  Assert.ok(
    await IOUtils.exists(firstCallCachePath),
    "A file exists at the cache path"
  );

  // Now make it seem as if the favicon was last modified last week. That's
  // definitely expired, since the actual expiry is when the icon is a day old.
  let aWeekAgo = Date.now() - 86400 * 7; // 86400 seconds in a day, times 7 days.
  await IOUtils.setModificationTime(firstCallCachePath, aWeekAgo);
  let { lastModified } = await IOUtils.stat(firstCallCachePath);

  let secondCallCachePath =
    await JUMP_LIST_BUILDER.obtainAndCacheFaviconAsync(TEST_PAGE_URI);
  Assert.equal(
    secondCallCachePath,
    firstCallCachePath,
    "Got back the same path the second time."
  );
  Assert.equal(
    (await IOUtils.stat(secondCallCachePath)).lastModified,
    lastModified,
    "The file got overwritten."
  );
});
