/**
 * Bug 1508355 - A test case for ensuring the saving channel has a correct first
 *               party domain when going through different "Save ... AS."
 */

"use strict";

const { SearchService } = ChromeUtils.importESModule(
  "moz-src:///toolkit/components/search/SearchService.sys.mjs"
);

Services.scriptloader.loadSubScript(
  "chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
  this
);

const TEST_FIRST_PARTY = "example.com";
const TEST_ORIGIN = `http://${TEST_FIRST_PARTY}`;
const TEST_BASE_PATH =
  "/browser/browser/components/originattributes/test/browser/";
const TEST_PATH = `${TEST_BASE_PATH}file_saveAs.sjs`;
const TEST_PATH_VIDEO = `${TEST_BASE_PATH}file_thirdPartyChild.video.webm`;
const TEST_PATH_IMAGE = `${TEST_BASE_PATH}file_favicon.png`;

// For the "Save Page As" test, we will check the channel of the sub-resource
// within the page. In this case, it is a image.
const TEST_PATH_PAGE = `${TEST_BASE_PATH}file_favicon.png`;

// For the "Save Frame As" test, we will check the channel of the sub-resource
// within the frame. In this case, it is a image.
const TEST_PATH_FRAME = `${TEST_BASE_PATH}file_favicon.png`;

let MockFilePicker = SpecialPowers.MockFilePicker;
MockFilePicker.init(window.browsingContext);
const tempDir = createTemporarySaveDirectory();
MockFilePicker.displayDirectory = tempDir;

add_setup(async function () {
  info("Setting the prefs.");

  await SpecialPowers.pushPrefEnv({
    set: [
      ["test.wait300msAfterTabSwitch", true],
      ["privacy.firstparty.isolate", true],
      ["dom.security.https_first", false],
    ],
  });

  info("Setting MockFilePicker.");
  mockTransferRegisterer.register();

  registerCleanupFunction(function () {
    mockTransferRegisterer.unregister();
    MockFilePicker.cleanup();
    tempDir.remove(true);
  });
});

function createTemporarySaveDirectory() {
  let saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
  saveDir.append("testsavedir");
  saveDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
  return saveDir;
}

function createPromiseForObservingChannel(aURL, aFirstParty) {
  return new Promise(resolve => {
    let observer = (aSubject, aTopic) => {
      if (aTopic === "http-on-modify-request") {
        let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
        let reqLoadInfo = httpChannel.loadInfo;

        // Make sure this is the request which we want to check.
        if (!httpChannel.URI.spec.endsWith(aURL)) {
          return;
        }

        info(`Checking loadInfo for URI: ${httpChannel.URI.spec}\n`);
        is(
          reqLoadInfo.originAttributes.firstPartyDomain,
          aFirstParty,
          "The loadInfo has correct first party domain"
        );

        Services.obs.removeObserver(observer, "http-on-modify-request");
        resolve();
      }
    };

    Services.obs.addObserver(observer, "http-on-modify-request");
  });
}

function createPromiseForTransferComplete() {
  return new Promise(resolve => {
    MockFilePicker.showCallback = fp => {
      info("MockFilePicker showCallback");

      let fileName = fp.defaultString;
      let destFile = tempDir.clone();
      destFile.append(fileName);

      MockFilePicker.setFiles([destFile]);
      MockFilePicker.filterIndex = 0; // kSaveAsType_Complete

      MockFilePicker.showCallback = null;
      mockTransferCallback = function (downloadSuccess) {
        ok(downloadSuccess, "File should have been downloaded successfully");
        mockTransferCallback = () => {};
        resolve();
      };
    };
  });
}

async function doCommandForFrameType() {
  info("Opening the frame sub-menu under the context menu.");
  let contextMenu = document.getElementById("contentAreaContextMenu");
  let frameMenu = contextMenu.querySelector("#frame");
  let frameMenuPopup = frameMenu.menupopup;
  let frameMenuPopupPromise = BrowserTestUtils.waitForEvent(
    frameMenuPopup,
    "popupshown"
  );

  frameMenu.openMenu(true);
  await frameMenuPopupPromise;

  info("Triggering the save process.");
  let saveFrameCommand = contextMenu.querySelector("#context-saveframe");
  frameMenuPopup.activateItem(saveFrameCommand);
}

add_task(async function test_setup() {
  // Make sure SearchService is ready for it to be called.
  await SearchService.init();
});

add_task(async function testContextMenuSaveAs() {
  const TEST_DATA = [
    { type: "link", path: TEST_PATH, target: "#link1" },
    { type: "video", path: TEST_PATH_VIDEO, target: "#video1" },
    { type: "image", path: TEST_PATH_IMAGE, target: "#image1" },
    { type: "page", path: TEST_PATH_PAGE, target: "body" },
    {
      type: "frame",
      path: TEST_PATH_FRAME,
      target: "body",
      doCommandFunc: doCommandForFrameType,
    },
  ];

  for (const data of TEST_DATA) {
    info(`Open a new tab for testing "Save ${data.type} as" in context menu.`);
    let tab = await BrowserTestUtils.openNewForegroundTab(
      gBrowser,
      `${TEST_ORIGIN}${TEST_PATH}?${data.type}=1`
    );

    let popupShownPromise = BrowserTestUtils.waitForEvent(
      document,
      "popupshown"
    );

    let browser = gBrowser.selectedBrowser;

    if (data.type === "frame") {
      browser = await SpecialPowers.spawn(
        tab.linkedBrowser,
        [],
        () => content.document.getElementById("frame1").browsingContext
      );
    }

    info("Open the context menu.");
    await BrowserTestUtils.synthesizeMouseAtCenter(
      data.target,
      {
        type: "contextmenu",
        button: 2,
      },
      browser
    );

    await popupShownPromise;

    let transferCompletePromise = createPromiseForTransferComplete();
    let observerPromise = createPromiseForObservingChannel(
      data.path,
      TEST_FIRST_PARTY
    );

    let contextMenu = document.getElementById("contentAreaContextMenu");
    let popupHiddenPromise = BrowserTestUtils.waitForEvent(
      contextMenu,
      "popuphidden"
    );

    // Select "Save As" option from context menu.
    if (!data.doCommandFunc) {
      let saveElement = document.getElementById(`context-save${data.type}`);
      info("Triggering the save process.");
      contextMenu.activateItem(saveElement);
    } else {
      await data.doCommandFunc();
    }

    info("Waiting for the channel.");
    await observerPromise;

    info("Wait until the save is finished.");
    await transferCompletePromise;

    info("Wait until the menu is closed.");
    await popupHiddenPromise;

    BrowserTestUtils.removeTab(tab);
  }
});

add_task(async function testFileMenuSavePageAs() {
  info(`Open a new tab for testing "Save Page AS" in the file menu.`);
  let tab = await BrowserTestUtils.openNewForegroundTab(
    gBrowser,
    `${TEST_ORIGIN}${TEST_PATH}?page=1`
  );

  let transferCompletePromise = createPromiseForTransferComplete();
  let observerPromise = createPromiseForObservingChannel(
    TEST_PATH_PAGE,
    TEST_FIRST_PARTY
  );

  let menubar = document.getElementById("main-menubar");
  let filePopup = document.getElementById("menu_FilePopup");

  // We only use the shortcut keys to open the file menu in Windows and Linux.
  // Mac doesn't have a shortcut to only open the file menu. Instead, we directly
  // trigger the save in MAC without any UI interactions.
  if (Services.appinfo.OS !== "Darwin") {
    let menubarActive = BrowserTestUtils.waitForEvent(
      menubar,
      "DOMMenuBarActive"
    );
    EventUtils.synthesizeKey("KEY_F10");
    await menubarActive;

    let popupShownPromise = BrowserTestUtils.waitForEvent(
      filePopup,
      "popupshown"
    );
    // In window, it still needs one extra down key to open the file menu.
    if (Services.appinfo.OS === "WINNT") {
      EventUtils.synthesizeKey("KEY_ArrowDown");
    }
    await popupShownPromise;
  }

  info("Triggering the save process.");
  let fileSavePageAsElement = document.getElementById("menu_savePage");
  fileSavePageAsElement.doCommand();

  info("Waiting for the channel.");
  await observerPromise;

  info("Wait until the save is finished.");
  await transferCompletePromise;

  // Close the file menu.
  if (Services.appinfo.OS !== "Darwin") {
    let popupHiddenPromise = BrowserTestUtils.waitForEvent(
      filePopup,
      "popuphidden"
    );
    filePopup.hidePopup();
    await popupHiddenPromise;
  }

  BrowserTestUtils.removeTab(tab);
});

add_task(async function testPageInfoMediaSaveAs() {
  info(
    `Open a new tab for testing "Save AS" in the media panel of the page info.`
  );
  let tab = await BrowserTestUtils.openNewForegroundTab(
    gBrowser,
    `${TEST_ORIGIN}${TEST_PATH}?pageinfo=1`
  );

  info("Open the media panel of the pageinfo.");
  let pageInfo = BrowserCommands.pageInfo(
    gBrowser.selectedBrowser.currentURI.spec,
    "mediaTab"
  );

  await BrowserTestUtils.waitForEvent(pageInfo, "page-info-init");

  let imageTree = pageInfo.document.getElementById("imagetree");
  let imageRowsNum = imageTree.view.rowCount;

  is(imageRowsNum, 2, "There should be two media items here.");

  for (let i = 0; i < imageRowsNum; i++) {
    imageTree.view.selection.select(i);
    imageTree.ensureRowIsVisible(i);
    imageTree.focus();

    let url = pageInfo.gImageView.data[i][0]; // COL_IMAGE_ADDRESS
    info(`Start to save the media item with URL: ${url}`);

    let transferCompletePromise = createPromiseForTransferComplete();
    let observerPromise = createPromiseForObservingChannel(
      url,
      TEST_FIRST_PARTY
    );

    info("Triggering the save process.");
    let saveElement = pageInfo.document.getElementById("imagesaveasbutton");
    saveElement.doCommand();

    info("Waiting for the channel.");
    await observerPromise;

    info("Wait until the save is finished.");
    await transferCompletePromise;
  }

  pageInfo.close();
  BrowserTestUtils.removeTab(tab);
});
