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

"use strict";

/**
 * Tests that Picture-in-Picture intializes without changes to video playback
 * when opened via the toggle using a touch event. Also ensures that elements
 * in the content window can still be interacted with afterwards.
 */
add_task(async () => {
  let videoID = "with-controls";

  await SpecialPowers.pushPrefEnv({
    set: [
      ["media.videocontrols.picture-in-picture.video-toggle.position", "right"],
    ],
  });

  await BrowserTestUtils.withNewTab(
    {
      url: TEST_PAGE,
      gBrowser,
    },
    async browser => {
      await ensureVideosReady(browser);
      let toggleStyles = DEFAULT_TOGGLE_STYLES;
      let stage = "hoverVideo";
      let toggleStylesForStage = toggleStyles.stages[stage];

      // Remove other page elements before reading PiP toggle's client rect.
      // Otherwise, we will provide the wrong coordinates when simulating the touch event.
      await SpecialPowers.spawn(browser, [], async () => {
        info(
          "Removing other elements first to make the PiP toggle more visible"
        );
        this.content.document.getElementById("no-controls").remove();
        let otherEls = this.content.document.querySelectorAll("h1");
        for (let el of otherEls) {
          el.remove();
        }
      });

      let toggleClientRect = await getToggleClientRect(browser, videoID);
      await prepareForToggleClick(browser, videoID);

      await SpecialPowers.spawn(
        browser,
        [{ videoID, toggleClientRect, toggleStylesForStage }],
        async args => {
          // waitForToggleOpacity is based on toggleOpacityReachesThreshold.
          // Waits for toggle to reach target opacity.
          async function waitForToggleOpacity(
            shadowRoot,
            toggleStylesForStage
          ) {
            for (let hiddenElement of toggleStylesForStage.hidden) {
              let el = shadowRoot.querySelector(hiddenElement);
              ok(
                ContentTaskUtils.isHidden(el),
                `Expected ${hiddenElement} to be hidden.`
              );
            }

            for (let opacityElement in toggleStylesForStage.opacities) {
              let opacityThreshold =
                toggleStylesForStage.opacities[opacityElement];
              let el = shadowRoot.querySelector(opacityElement);

              await ContentTaskUtils.waitForCondition(
                () => {
                  let opacity = parseFloat(
                    this.content.getComputedStyle(el).opacity
                  );
                  return opacity >= opacityThreshold;
                },
                `Toggle element ${opacityElement} should have eventually reached ` +
                  `target opacity ${opacityThreshold}`,
                100,
                100
              );

              ok(true, "Toggle reached target opacity.");
            }
          }
          let { videoID, toggleClientRect, toggleStylesForStage } = args;
          let video = this.content.document.getElementById(videoID);
          let shadowRoot = video.openOrClosedShadowRoot;

          info("Creating a new button in the content window");
          let button = this.content.document.createElement("button");
          let buttonSelected = false;
          button.ontouchstart = () => {
            info("Button selected via touch event");
            buttonSelected = true;
            return true;
          };
          button.id = "testbutton";
          button.style.backgroundColor = "red";
          // Move button to the top for better visibility.
          button.style.position = "absolute";
          button.style.top = "0";
          button.textContent = "test button";
          this.content.document.body.appendChild(button);

          await video.play();

          info("Hover over the video to show the Picture-in-Picture toggle");
          EventUtils.synthesizeMouseAtCenter(
            video,
            { type: "mousemove" },
            this.content.window
          );
          EventUtils.synthesizeMouseAtCenter(
            video,
            { type: "mouseover" },
            this.content.window
          );

          let toggleCenterX =
            toggleClientRect.left + toggleClientRect.width / 2;
          let toggleCenterY =
            toggleClientRect.top + toggleClientRect.height / 2;

          // We want to wait for the toggle to reach opacity so that we can select it.
          info("Waiting for toggle to become fully visible");
          await waitForToggleOpacity(shadowRoot, toggleStylesForStage);

          info("Simulating touch event on PiP toggle");
          let utils = EventUtils._getDOMWindowUtils(this.content.window);
          let id = utils.DEFAULT_TOUCH_POINTER_ID;
          let rx = 1;
          let ry = 1;
          let angle = 0;
          let force = 1;
          let tiltX = 0;
          let tiltY = 0;
          let twist = 0;

          let defaultPrevented = EventUtils.synthesizeTouchAtPoint(
            toggleCenterX,
            toggleCenterY,
            {
              type: "touchstart",
              id,
              rx,
              ry,
              angle,
              force,
              tiltX,
              tiltY,
              twist,
              modifiers: 0,
            },
            this.content.window
          );
          EventUtils.synthesizeTouchAtPoint(
            toggleCenterX,
            toggleCenterY,
            {
              type: "touchend",
              id,
              rx,
              ry,
              angle,
              force,
              tiltX,
              tiltY,
              twist,
              modifiers: 0,
            },
            this.content.window
          );

          ok(
            defaultPrevented,
            "Touchstart event's default actions should be prevented"
          );
          ok(!video.paused, "Video should still be playing");

          await ContentTaskUtils.waitForCondition(() => {
            return video.isCloningElementVisually;
          }, "Video is being cloned visually.");

          let testButton = this.content.document.getElementById("testbutton");
          let buttonRect = testButton.getBoundingClientRect();
          let buttonCenterX = buttonRect.left + buttonRect.width / 2;
          let buttonCenterY = buttonRect.top + buttonRect.height / 2;

          info("Simulating touch event on new button");
          defaultPrevented = EventUtils.synthesizeTouchAtPoint(
            buttonCenterX,
            buttonCenterY,
            {
              type: "touchstart",
              id,
              rx,
              ry,
              angle,
              force,
              tiltX,
              tiltY,
              twist,
              modifiers: 0,
            },
            this.content.window
          );
          EventUtils.synthesizeTouchAtPoint(
            buttonCenterX,
            buttonCenterY,
            {
              type: "touchend",
              id,
              rx,
              ry,
              angle,
              force,
              tiltX,
              tiltY,
              twist,
              modifiers: 0,
            },
            this.content.window
          );

          ok(
            buttonSelected,
            "Button in content window was selected via touchstart"
          );
          ok(
            !defaultPrevented,
            "Touchstart event's default actions should no longer be prevented"
          );
        }
      );

      try {
        info("Picture-in-Picture window should open");
        await TestUtils.waitForCondition(
          () => Services.wm.getEnumerator(WINDOW_TYPE).hasMoreElements(),
          "Found a Picture-in-Picture"
        );
        for (let win of Services.wm.getEnumerator(WINDOW_TYPE)) {
          if (!win.closed) {
            ok(true, "Found a Picture-in-Picture window as expected");
            win.close();
          }
        }
      } catch {
        ok(false, "No Picture-in-Picture window found, which is unexpected");
      }
    }
  );
});
