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

"use strict";

// Here we test that the `dispatchKeyEvent` API resolves after all the synchronous event
// handlers from the content page have been flushed.
//
// Say the content page has an event handler such as:
//
//   el.addEventListener("keyup", () => {
//     doSomeVeryLongProcessing(); // <- takes a long time but is synchronous!
//     window.myVariable = "newValue";
//   });
//
// And imagine this is tested via:
//
//   await Input.dispatchKeyEvent(...);
//   const myVariable = await Runtime.evaluate({ expression: "window.myVariable" });
//   equals(myVariable, "newValue");
//
// In order for this to work, we need to be sure that `await Input.dispatchKeyEvent`
// resolves only after the content page flushed the event handlers (and
// `window.myVariable = "newValue"` was executed).
//
// This can be racy because Input.dispatchKeyEvent and window.myVariable = "newValue" run
// in different processes.

const PAGE_URL =
  "https://example.com/browser/remote/cdp/test/browser/input/doc_dispatchKeyEvent_race.html";

add_task(async function ({ client }) {
  await loadURL(PAGE_URL);

  const { Input, Runtime } = client;

  // Need an enabled Runtime domain to run evaluate.
  info("Enable the Runtime domain");
  await Runtime.enable();
  const { context } = await Runtime.executionContextCreated();

  info("Focus the input on the page");
  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
    const input = content.document.querySelector("input");
    input.focus();
    is(input, content.document.activeElement, "Input should be focused");
  });

  // See doc_input_dispatchKeyEvent_race.html
  // The page listens to `input` events to update a property on window.
  // We will check that the value is updated as soon dispatchKeyEvent has resolved.
  await checkWindowTestValue("initial-value", context.id, Runtime);

  info("Write 'hhhhhh' ('h' times 6)");
  for (let i = 0; i < 6; i++) {
    await dispatchKeyEvent(Input, "h", 72, "keyDown");
    await dispatchKeyEvent(Input, "h", 72, "keyUp");
  }
  await checkWindowTestValue("hhhhhh", context.id, Runtime);

  info("Write 'aaaaaa' with 6 consecutive keydown and one keyup");
  await Promise.all([
    dispatchKeyEvent(Input, "a", 65, "keyDown"),
    dispatchKeyEvent(Input, "a", 65, "keyDown"),
    dispatchKeyEvent(Input, "a", 65, "keyDown"),
    dispatchKeyEvent(Input, "a", 65, "keyDown"),
    dispatchKeyEvent(Input, "a", 65, "keyDown"),
    dispatchKeyEvent(Input, "a", 65, "keyDown"),
  ]);
  await dispatchKeyEvent(Input, "a", 65, "keyUp");
  await checkWindowTestValue("hhhhhhaaaaaa", context.id, Runtime);
});

function dispatchKeyEvent(Input, key, keyCode, type, modifiers = 0) {
  info(`Send ${type} for key ${key}`);
  return Input.dispatchKeyEvent({
    type,
    modifiers,
    windowsVirtualKeyCode: keyCode,
    key,
  });
}

async function checkWindowTestValue(expected, contextId, Runtime) {
  info("Retrieve the value of `window.testValue` in the test page");
  const { result } = await Runtime.evaluate({
    contextId,
    expression: "window.testValue",
  });

  is(result.value, expected, "Content window test value is correct");
}
