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

"use strict";

/**
 * Opens up the content area context menu on a video loaded in a
 * browser.
 *
 * @param {Element} browser The <xul:browser> hosting the <video>
 *
 * @param {string} videoID The ID of the video to open the context
 * menu with.
 *
 * @param {object} modifiers The properties to pass to synthesizeMouseAtCenter.
 *
 * @returns {Promise}
 *   Resolves with the context menu DOM node once opened.
 */
async function openContextMenu(
  browser,
  videoID,
  modifiers = { type: "contextmenu", button: 2 }
) {
  let contextMenu = document.getElementById("contentAreaContextMenu");
  let popupShownPromise = BrowserTestUtils.waitForEvent(
    contextMenu,
    "popupshown"
  );
  await BrowserTestUtils.synthesizeMouseAtCenter(
    "#" + videoID,
    modifiers,
    browser
  );
  await popupShownPromise;
  return contextMenu;
}

/**
 * Closes the content area context menu.
 *
 * @param {Element} contextMenu The content area context menu opened with
 * openContextMenu.
 *
 * @returns {Promise<void>}
 */
async function closeContextMenu(contextMenu) {
  let popupHiddenPromise = BrowserTestUtils.waitForEvent(
    contextMenu,
    "popuphidden"
  );
  contextMenu.hidePopup();
  await popupHiddenPromise;
}

async function runTaskOpenClosePiPWithContextMenu(isCtrlClick = false) {
  for (const videoId of ["with-controls", "no-controls"]) {
    info(`Testing ${videoId} case.`);

    await BrowserTestUtils.withNewTab(
      {
        url: TEST_PAGE,
        gBrowser,
      },
      async browser => {
        let contextMenu;

        if (!isCtrlClick) {
          contextMenu = await openContextMenu(browser, videoId);
        } else {
          contextMenu = await openContextMenu(browser, videoId, {
            type: "contextmenu",
            shiftKey: true,
          });
        }

        info("Context menu is open.");

        const pipMenuItemId = "context-video-pictureinpicture";
        let menuItem = document.getElementById(pipMenuItemId);

        Assert.ok(
          !menuItem.hidden,
          "Should show Picture-in-Picture menu item."
        );
        Assert.ok(
          !menuItem.hasAttribute("checked"),
          "Picture-in-Picture should be unchecked."
        );

        contextMenu.activateItem(menuItem);

        await SpecialPowers.spawn(browser, [videoId], async videoID => {
          let video = content.document.getElementById(videoID);
          await ContentTaskUtils.waitForCondition(() => {
            return video.isCloningElementVisually;
          }, "Video has started being cloned.");
        });

        info("PiP player is now open.");

        contextMenu = await openContextMenu(browser, videoId);

        info("Context menu is open again.");

        contextMenu.activateItem(menuItem);

        await SpecialPowers.spawn(browser, [videoId], async videoID => {
          let video = content.document.getElementById(videoID);
          await ContentTaskUtils.waitForCondition(() => {
            return !video.isCloningElementVisually;
          }, "Video has stopped being cloned.");
        });
      }
    );
  }
}

/**
 * Tests that Picture-in-Picture can be opened and closed through the
 * context menu
 */
add_task(async () => {
  // Test with regular right click
  await runTaskOpenClosePiPWithContextMenu();

  // Test with ctrl+click (macOS only)
  let isMac = AppConstants.platform == "macosx";
  if (isMac) {
    info("Mac detected. Testing with ctrl + click");
    await runTaskOpenClosePiPWithContextMenu(isMac);
  }
});

/**
 * Tests that the Picture-in-Picture context menu is correctly updated
 * based on the Picture-in-Picture state of the video.
 */
add_task(async () => {
  for (let videoID of ["with-controls", "no-controls"]) {
    info(`Testing ${videoID} case.`);

    await BrowserTestUtils.withNewTab(
      {
        url: TEST_PAGE,
        gBrowser,
      },
      async browser => {
        let menuItem = document.getElementById(
          "context-video-pictureinpicture"
        );
        let menu = await openContextMenu(browser, videoID);
        Assert.ok(
          !menuItem.hidden,
          "Should show Picture-in-Picture menu item."
        );
        Assert.ok(
          !menuItem.hasAttribute("checked"),
          "Picture-in-Picture should be unchecked."
        );
        await closeContextMenu(menu);

        let pipWin = await triggerPictureInPicture(browser, videoID);
        ok(pipWin, "Got Picture-in-Picture window.");

        await SpecialPowers.spawn(browser, [videoID], async videoID => {
          let video = content.document.getElementById(videoID);
          await ContentTaskUtils.waitForCondition(() => {
            return video.isCloningElementVisually;
          }, "Video has started being cloned.");
        });

        menu = await openContextMenu(browser, videoID);
        Assert.ok(
          !menuItem.hidden,
          "Should show Picture-in-Picture menu item."
        );
        Assert.ok(
          menuItem.hasAttribute("checked"),
          "Picture-in-Picture should be checked."
        );
        await closeContextMenu(menu);

        let videoNotCloning = SpecialPowers.spawn(
          browser,
          [videoID],
          async videoID => {
            let video = content.document.getElementById(videoID);
            await ContentTaskUtils.waitForCondition(() => {
              return !video.isCloningElementVisually;
            }, "Video has stopped being cloned.");
          }
        );
        pipWin.close();
        await videoNotCloning;

        menu = await openContextMenu(browser, videoID);
        Assert.ok(
          !menuItem.hidden,
          "Should show Picture-in-Picture menu item."
        );
        Assert.ok(
          !menuItem.hasAttribute("checked"),
          "Picture-in-Picture should be unchecked."
        );
        await closeContextMenu(menu);

        await SpecialPowers.spawn(browser, [videoID], async videoID => {
          // Construct a new video element, and capture a stream from it
          // to redirect to the video that we're testing.
          let newVideo = content.document.createElement("video");
          content.document.body.appendChild(newVideo);

          let testedVideo = content.document.getElementById(videoID);
          newVideo.src = testedVideo.src;

          testedVideo.srcObject = newVideo.mozCaptureStream();
          await newVideo.play();
          await testedVideo.play();

          await newVideo.pause();
          await testedVideo.pause();
        });
        menu = await openContextMenu(browser, videoID);
        Assert.ok(
          !menuItem.hidden,
          "Should be showing Picture-in-Picture menu item."
        );
        Assert.ok(
          !menuItem.hasAttribute("checked"),
          "Picture-in-Picture should be unchecked."
        );
        await closeContextMenu(menu);

        pipWin = await triggerPictureInPicture(browser, videoID);
        ok(pipWin, "Got Picture-in-Picture window.");

        await SpecialPowers.spawn(browser, [videoID], async videoID => {
          let video = content.document.getElementById(videoID);
          await ContentTaskUtils.waitForCondition(() => {
            return video.isCloningElementVisually;
          }, "Video has started being cloned.");
        });

        menu = await openContextMenu(browser, videoID);
        Assert.ok(
          !menuItem.hidden,
          "Should show Picture-in-Picture menu item."
        );
        Assert.ok(
          menuItem.hasAttribute("checked"),
          "Picture-in-Picture should be checked."
        );
        await closeContextMenu(menu);
      }
    );
  }
});
