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

"use strict";

const TEST_PATH = getRootDirectory(gTestPath).replace(
  "chrome://mochitests/content",
  "https://example.com"
);

let gPathsToRemove = [];

add_setup(async function () {
  await SpecialPowers.pushPrefEnv({
    set: [["browser.download.useDownloadDir", true]],
  });
  registerCleanupFunction(async () => {
    for (let path of gPathsToRemove) {
      // IOUtils.remove ignores non-existing files out of the box.
      await IOUtils.remove(path);
    }
    let publicList = await Downloads.getList(Downloads.PUBLIC);
    await publicList.removeFinished();
  });
});

async function testLinkWithoutExtension(type, shouldHaveExtension) {
  info("Checking " + type);

  let task = function () {
    return SpecialPowers.spawn(gBrowser.selectedBrowser, [type], mimetype => {
      let link = content.document.createElement("a");
      link.textContent = "Click me";
      link.href = "data:" + mimetype + ",hello";
      link.download = "somefile";
      content.document.body.appendChild(link);
      link.click();
    });
  };

  await checkDownloadWithExtensionState(task, {
    type,
    shouldHaveExtension,
    alwaysViewPDFInline: false,
  });

  if (type == "application/pdf") {
    // For PDF, try again with the always open inline preference set
    await SpecialPowers.pushPrefEnv({
      set: [["browser.download.open_pdf_attachments_inline", true]],
    });

    await checkDownloadWithExtensionState(task, {
      type,
      shouldHaveExtension,
      alwaysViewPDFInline: true,
    });

    await SpecialPowers.popPrefEnv();
  }
}

async function checkDownloadWithExtensionState(
  task,
  { type, shouldHaveExtension, expectedName = null, alwaysViewPDFInline }
) {
  const shouldExpectDialog = Services.prefs.getBoolPref(
    "browser.download.always_ask_before_handling_new_types",
    false
  );

  let winPromise;
  if (shouldExpectDialog) {
    winPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
  }

  let publicList = await Downloads.getList(Downloads.PUBLIC);
  let shouldCheckFilename = shouldHaveExtension || !shouldExpectDialog;

  let downloadFinishedPromise = shouldCheckFilename
    ? promiseDownloadFinished(publicList)
    : null;

  // PDF should load using the internal viewer without downloading it.
  let waitForLoad;
  if (
    (!shouldExpectDialog || alwaysViewPDFInline) &&
    type == "application/pdf"
  ) {
    waitForLoad = BrowserTestUtils.waitForNewTab(gBrowser);
  }

  await task();
  await waitForLoad;

  let win;
  if (shouldExpectDialog) {
    info("Waiting for dialog.");
    win = await winPromise;
  }

  expectedName ??= shouldHaveExtension
    ? "somefile." + getMIMEInfoForType(type).primaryExtension
    : "somefile";

  let closedPromise = true;
  if (shouldExpectDialog) {
    let actualName = win.document.getElementById("location").value;
    closedPromise = BrowserTestUtils.windowClosed(win);

    if (shouldHaveExtension) {
      is(actualName, expectedName, `${type} should get an extension`);
    } else {
      is(actualName, expectedName, `${type} should not get an extension`);
    }
  }

  if (shouldExpectDialog && shouldHaveExtension) {
    // Then pick "save" in the dialog, if we have a dialog.
    let dialog = win.document.getElementById("unknownContentType");
    win.document.getElementById("save").click();
    let button = dialog.getButton("accept");
    button.disabled = false;
    dialog.acceptDialog();
  }

  if (!shouldExpectDialog && type == "application/pdf") {
    if (alwaysViewPDFInline) {
      is(
        gURLBar.inputField.value,
        "data:application/pdf,hello",
        "url is correct for " + type
      );
    } else {
      ok(
        gURLBar.inputField.value.startsWith("file://") &&
          gURLBar.inputField.value.endsWith("somefile.pdf"),
        "url is correct for " + type
      );
    }

    BrowserTestUtils.removeTab(gBrowser.selectedTab);
  }

  if (shouldExpectDialog || !alwaysViewPDFInline || type != "application/pdf") {
    // Wait for the download if it exists (may produce null).
    let download = await downloadFinishedPromise;
    if (download) {
      // Check the download's extension is correct.
      is(
        PathUtils.filename(download.target.path),
        expectedName,
        `Downloaded file should match ${expectedName}`
      );
      gPathsToRemove.push(download.target.path);
      let pathToRemove = download.target.path;
      // Avoid one file interfering with subsequent files.
      await publicList.removeFinished();
      await IOUtils.remove(pathToRemove);
    } else if (win) {
      // We just cancel out for files that would end up without a path, as we'd
      // prompt for a filename.
      win.close();
    }
  }

  return closedPromise;
}

/**
 * Check that for document types, images, videos and audio files,
 * we enforce a useful extension.
 */
add_task(async function test_enforce_useful_extension() {
  await BrowserTestUtils.withNewTab("data:text/html,", async () => {
    await testLinkWithoutExtension("image/png", true);
    await testLinkWithoutExtension("audio/ogg", true);
    await testLinkWithoutExtension("video/webm", true);
    await testLinkWithoutExtension("application/pdf", true);

    await testLinkWithoutExtension("application/x-nonsense", false);
    await testLinkWithoutExtension("application/octet-stream", false);
    await testLinkWithoutExtension("binary/octet-stream", false);
    await testLinkWithoutExtension("application/x-msdownload", false);
  });
});

/**
 * Check that we still use URL extension info when we don't have anything else,
 * despite bogus local info.
 */
add_task(async function test_broken_saved_handlerinfo_and_useless_mimetypes() {
  let bogusType = getMIMEInfoForType("binary/octet-stream");
  registerCleanupFunction(() => {
    handlerSvc.remove(bogusType);
  });
  bogusType.setFileExtensions(["jpg"]);
  let handlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
    Ci.nsIHandlerService
  );
  handlerSvc.store(bogusType);
  let tabToClean = null;
  let task = function () {
    return BrowserTestUtils.openNewForegroundTab({
      gBrowser,
      opening: TEST_PATH + "file_as.exe?foo=bar",
      waitForLoad: false,
      waitForStateStop: true,
    }).then(tab => {
      return (tabToClean = tab);
    });
  };
  await checkDownloadWithExtensionState(task, {
    type: "binary/octet-stream",
    shouldHaveExtension: true,
    expectedName: "file_as.exe",
  });
  // Downloads should really close their tabs...
  if (tabToClean?.isConnected) {
    BrowserTestUtils.removeTab(tabToClean);
  }
});
