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

"use strict";

const fxaDevices = [
  {
    id: 1,
    name: "Foo",
    availableCommands: { "https://identity.mozilla.com/cmd/open-uri": "baz" },
    lastAccessTime: Date.now(),
  },
  {
    id: 2,
    name: "Bar",
    availableCommands: { "https://identity.mozilla.com/cmd/open-uri": "boo" },
    lastAccessTime: Date.now() + 60000, // add 30min
  },
  {
    id: 3,
    name: "Baz",
    clientRecord: "bar",
    lastAccessTime: Date.now() + 120000, // add 60min
  }, // Legacy send tab target (no availableCommands).
  { id: 4, name: "Homer" }, // Incompatible target.
];

add_setup(async function () {
  await promiseSyncReady();
  await Services.search.init();
  // gSync.init() is called in a requestIdleCallback. Force its initialization.
  gSync.init();
  sinon
    .stub(Weave.Service.clientsEngine, "getClientByFxaDeviceId")
    .callsFake(fxaDeviceId => {
      let target = fxaDevices.find(c => c.id == fxaDeviceId);
      return target ? target.clientRecord : null;
    });
  sinon.stub(Weave.Service.clientsEngine, "getClientType").returns("desktop");
  await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
});

add_task(async function test_page_contextmenu() {
  const sandbox = setupSendTabMocks({ fxaDevices });

  await openContentContextMenu("#moztext", "context-sendpagetodevice");
  is(
    document.getElementById("context-sendpagetodevice").hidden,
    false,
    "Send page to device is shown"
  );
  is(
    document.getElementById("context-sendpagetodevice").disabled,
    false,
    "Send page to device is enabled"
  );
  checkPopup([
    { label: "Bar" },
    { label: "Foo" },
    "----",
    { label: "Send to All Devices" },
    { label: "Manage Devices..." },
  ]);
  await hideContentContextMenu();

  sandbox.restore();
});

add_task(async function test_link_contextmenu() {
  const sandbox = setupSendTabMocks({ fxaDevices });
  let expectation = sandbox
    .mock(gSync)
    .expects("sendTabToDevice")
    .once()
    .withExactArgs(
      "https://www.example.org/",
      [fxaDevices[1]],
      "Click on me!!"
    );

  // Add a link to the page
  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    let a = content.document.createElement("a");
    a.href = "https://www.example.org";
    a.id = "testingLink";
    a.textContent = "Click on me!!";
    content.document.body.appendChild(a);
  });

  let contextMenu = await openContentContextMenu(
    "#testingLink",
    "context-sendlinktodevice",
    "context-sendlinktodevice-popup"
  );

  let expectedArray = ["context-openlinkintab"];

  if (
    Services.prefs.getBoolPref("privacy.userContext.enabled") &&
    ContextualIdentityService.getPublicIdentities().length
  ) {
    expectedArray.push("context-openlinkinusercontext-menu");
  }

  expectedArray.push(
    "context-openlink",
    "context-openlinkprivate",
    "context-sep-open",
    "context-bookmarklink",
    "context-savelink",
    "context-savelinktopocket",
    "context-copylink",
    "context-sendlinktodevice",
    "context-sep-sendlinktodevice",
    "context-searchselect",
    "frame-sep"
  );

  if (
    Services.prefs.getBoolPref("devtools.accessibility.enabled", true) &&
    (Services.prefs.getBoolPref("devtools.everOpened", false) ||
      Services.prefs.getIntPref("devtools.selfxss.count", 0) > 0)
  ) {
    expectedArray.push("context-inspect-a11y");
  }

  expectedArray.push("context-inspect");

  let menu = document.getElementById("contentAreaContextMenu");

  for (let i = 0, j = 0; i < menu.children.length; i++) {
    let item = menu.children[i];
    if (item.hidden) {
      continue;
    }
    Assert.equal(
      item.id,
      expectedArray[j],
      "Ids in context menu match expected values"
    );
    j++;
  }

  is(
    document.getElementById("context-sendlinktodevice").hidden,
    false,
    "Send link to device is shown"
  );
  is(
    document.getElementById("context-sendlinktodevice").disabled,
    false,
    "Send link to device is enabled"
  );
  contextMenu.activateItem(
    document
      .getElementById("context-sendlinktodevice-popup")
      .querySelector("menuitem")
  );
  await hideContentContextMenu();

  expectation.verify();
  sandbox.restore();
});

add_task(async function test_page_contextmenu_no_remote_clients() {
  const sandbox = setupSendTabMocks({ fxaDevices: [] });

  await openContentContextMenu("#moztext");
  is(
    document.getElementById("context-sendpagetodevice").hidden,
    true,
    "Send page to device is hidden"
  );
  is(
    document.getElementById("context-sendpagetodevice").disabled,
    false,
    "Send tab to device is enabled"
  );
  checkPopup();
  await hideContentContextMenu();

  sandbox.restore();
});

add_task(async function test_page_contextmenu_one_remote_client() {
  const sandbox = setupSendTabMocks({
    fxaDevices: [
      {
        id: 1,
        name: "Foo",
        availableCommands: {
          "https://identity.mozilla.com/cmd/open-uri": "baz",
        },
      },
    ],
  });

  await openContentContextMenu("#moztext", "context-sendpagetodevice");
  is(
    document.getElementById("context-sendpagetodevice").hidden,
    false,
    "Send page to device is shown"
  );
  is(
    document.getElementById("context-sendpagetodevice").disabled,
    false,
    "Send page to device is enabled"
  );
  checkPopup([{ label: "Foo" }]);
  await hideContentContextMenu();

  sandbox.restore();
});

add_task(async function test_page_contextmenu_not_sendable() {
  const sandbox = setupSendTabMocks({ fxaDevices, isSendableURI: false });

  await openContentContextMenu("#moztext");
  is(
    document.getElementById("context-sendpagetodevice").hidden,
    true,
    "Send page to device is hidden"
  );
  is(
    document.getElementById("context-sendpagetodevice").disabled,
    true,
    "Send page to device is disabled"
  );
  checkPopup();
  await hideContentContextMenu();

  sandbox.restore();
});

add_task(async function test_page_contextmenu_not_synced_yet() {
  const sandbox = setupSendTabMocks({ fxaDevices: null });

  await openContentContextMenu("#moztext");
  is(
    document.getElementById("context-sendpagetodevice").hidden,
    true,
    "Send page to device is hidden"
  );
  is(
    document.getElementById("context-sendpagetodevice").disabled,
    true,
    "Send page to device is disabled"
  );
  checkPopup();
  await hideContentContextMenu();

  sandbox.restore();
});

add_task(async function test_page_contextmenu_sync_not_ready_configured() {
  const sandbox = setupSendTabMocks({ syncReady: false });

  await openContentContextMenu("#moztext");
  is(
    document.getElementById("context-sendpagetodevice").hidden,
    true,
    "Send page to device is hidden"
  );
  is(
    document.getElementById("context-sendpagetodevice").disabled,
    true,
    "Send page to device is disabled"
  );
  checkPopup();
  await hideContentContextMenu();

  sandbox.restore();
});

add_task(async function test_page_contextmenu_sync_not_ready_other_state() {
  const sandbox = setupSendTabMocks({
    syncReady: false,
    state: UIState.STATUS_NOT_VERIFIED,
  });

  await openContentContextMenu("#moztext");
  is(
    document.getElementById("context-sendpagetodevice").hidden,
    true,
    "Send page to device is hidden"
  );
  is(
    document.getElementById("context-sendpagetodevice").disabled,
    false,
    "Send page to device is enabled"
  );
  checkPopup();
  await hideContentContextMenu();

  sandbox.restore();
});

add_task(async function test_page_contextmenu_unconfigured() {
  const sandbox = setupSendTabMocks({ state: UIState.STATUS_NOT_CONFIGURED });

  await openContentContextMenu("#moztext");
  is(
    document.getElementById("context-sendpagetodevice").hidden,
    true,
    "Send page to device is hidden"
  );
  is(
    document.getElementById("context-sendpagetodevice").disabled,
    false,
    "Send page to device is enabled"
  );
  checkPopup();

  await hideContentContextMenu();

  sandbox.restore();
});

add_task(async function test_page_contextmenu_not_verified() {
  const sandbox = setupSendTabMocks({ state: UIState.STATUS_NOT_VERIFIED });

  await openContentContextMenu("#moztext");
  is(
    document.getElementById("context-sendpagetodevice").hidden,
    true,
    "Send page to device is hidden"
  );
  is(
    document.getElementById("context-sendpagetodevice").disabled,
    false,
    "Send page to device is enabled"
  );
  checkPopup();

  await hideContentContextMenu();

  sandbox.restore();
});

add_task(async function test_page_contextmenu_login_failed() {
  const sandbox = setupSendTabMocks({ state: UIState.STATUS_LOGIN_FAILED });

  await openContentContextMenu("#moztext");
  is(
    document.getElementById("context-sendpagetodevice").hidden,
    true,
    "Send page to device is hidden"
  );
  is(
    document.getElementById("context-sendpagetodevice").disabled,
    false,
    "Send page to device is enabled"
  );
  checkPopup();

  await hideContentContextMenu();

  sandbox.restore();
});

add_task(async function test_page_contextmenu_fxa_disabled() {
  const getter = sinon.stub(gSync, "FXA_ENABLED").get(() => false);
  gSync.onFxaDisabled(); // Would have been called on gSync initialization if FXA_ENABLED had been set.
  await openContentContextMenu("#moztext");
  is(
    document.getElementById("context-sendpagetodevice").hidden,
    true,
    "Send page to device is hidden"
  );
  await hideContentContextMenu();
  getter.restore();
  [...document.querySelectorAll(".sync-ui-item")].forEach(
    e => (e.hidden = false)
  );
});

// We are not going to bother testing the visibility of context-sendlinktodevice
// since it uses the exact same code.
// However, browser_contextmenu.js contains tests that verify its presence.

add_task(async function teardown() {
  Weave.Service.clientsEngine.getClientByFxaDeviceId.restore();
  Weave.Service.clientsEngine.getClientType.restore();
  gBrowser.removeCurrentTab();
});

function checkPopup(expectedItems = null) {
  const popup = document.getElementById("context-sendpagetodevice-popup");
  if (!expectedItems) {
    is(popup.state, "closed", "Popup should be hidden.");
    return;
  }
  const menuItems = popup.children;
  for (let i = 0; i < menuItems.length; i++) {
    const menuItem = menuItems[i];
    const expectedItem = expectedItems[i];
    if (expectedItem === "----") {
      is(menuItem.nodeName, "menuseparator", "Found a separator");
      continue;
    }
    is(menuItem.nodeName, "menuitem", "Found a menu item");
    // Bug workaround, menuItem.label "…" encoding is different than ours.
    is(
      menuItem.label.normalize("NFKC"),
      expectedItem.label,
      "Correct menu item label"
    );
    is(
      menuItem.disabled,
      !!expectedItem.disabled,
      "Correct menu item disabled state"
    );
  }
  // check the length last - the above loop might have given us other clues...
  is(
    menuItems.length,
    expectedItems.length,
    "Popup has the expected children count."
  );
}

async function openContentContextMenu(selector, openSubmenuId = null) {
  const contextMenu = document.getElementById("contentAreaContextMenu");
  is(contextMenu.state, "closed", "checking if popup is closed");

  const awaitPopupShown = BrowserTestUtils.waitForEvent(
    contextMenu,
    "popupshown"
  );
  await BrowserTestUtils.synthesizeMouse(
    selector,
    0,
    0,
    {
      type: "contextmenu",
      button: 2,
      shiftkey: false,
      centered: true,
    },
    gBrowser.selectedBrowser
  );
  await awaitPopupShown;

  if (openSubmenuId) {
    const menu = document.getElementById(openSubmenuId);
    const menuPopup = menu.menupopup;
    const menuPopupPromise = BrowserTestUtils.waitForEvent(
      menuPopup,
      "popupshown"
    );
    menu.openMenu(true);
    await menuPopupPromise;
  }
  return contextMenu;
}

async function hideContentContextMenu() {
  const contextMenu = document.getElementById("contentAreaContextMenu");
  const awaitPopupHidden = BrowserTestUtils.waitForEvent(
    contextMenu,
    "popuphidden"
  );
  contextMenu.hidePopup();
  await awaitPopupHidden;
}
