const ids = {
  INPUT_ID: "input1",
  FORM1_ID: "form1",
  FORM2_ID: "form2",
  CHANGE_INPUT_ID: "input2",
};

function task(contentIds) {
  let resolve;
  let promise = new Promise(r => {
    resolve = r;
  });

  function unexpectedContentEvent(evt) {
    Assert.ok(false, "Received a " + evt.type + " event on content");
  }

  var gDoc = null;

  addEventListener("load", tabLoad, true);

  function tabLoad() {
    if (content.location.href == "about:blank") {
      return;
    }
    removeEventListener("load", tabLoad, true);

    gDoc = content.document;
    gDoc.addEventListener("DOMFormHasPassword", unexpectedContentEvent);
    addEventListener("DOMFormHasPassword", unexpectedContentEvent);
    gDoc.defaultView.setTimeout(test_inputAdd, 0);
  }

  function test_inputAdd() {
    addEventListener("DOMFormHasPassword", test_inputAddHandler, {
      once: true,
      capture: true,
    });
    let input = gDoc.createElementNS("http://www.w3.org/1999/xhtml", "input");
    input.setAttribute("type", "password");
    input.setAttribute("id", contentIds.INPUT_ID);
    input.setAttribute("data-test", "unique-attribute");
    gDoc.getElementById(contentIds.FORM1_ID).appendChild(input);
  }

  function test_inputAddHandler(evt) {
    evt.stopPropagation();
    Assert.equal(
      evt.target.id,
      contentIds.FORM1_ID,
      evt.type + " event targets correct form element (added password element)"
    );
    gDoc.defaultView.setTimeout(test_inputChangeForm, 0);
  }

  function test_inputChangeForm() {
    addEventListener("DOMFormHasPassword", test_inputChangeFormHandler, {
      once: true,
      capture: true,
    });
    let input = gDoc.getElementById(contentIds.INPUT_ID);
    input.setAttribute("form", contentIds.FORM2_ID);
  }

  function test_inputChangeFormHandler(evt) {
    evt.stopPropagation();
    Assert.equal(
      evt.target.id,
      contentIds.FORM2_ID,
      evt.type + " event targets correct form element (changed form)"
    );
    gDoc.defaultView.setTimeout(test_inputChangesType, 0);
  }

  function test_inputChangesType() {
    addEventListener("DOMFormHasPassword", test_inputChangesTypeHandler, {
      once: true,
      capture: true,
    });
    let input = gDoc.getElementById(contentIds.CHANGE_INPUT_ID);
    input.setAttribute("type", "password");
  }

  function test_inputChangesTypeHandler(evt) {
    evt.stopPropagation();
    Assert.equal(
      evt.target.id,
      contentIds.FORM1_ID,
      evt.type + " event targets correct form element (changed type)"
    );
    gDoc.defaultView.setTimeout(finish, 0);
  }

  function finish() {
    removeEventListener("DOMFormHasPassword", unexpectedContentEvent);
    gDoc.removeEventListener("DOMFormHasPassword", unexpectedContentEvent);
    resolve();
  }

  return promise;
}

add_task(async function test_disconnectedInputs() {
  const tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser));
  // TODO: Switch to SpecialPowers.spawn
  // eslint-disable-next-line mozilla/reject-contenttask-spawn
  await ContentTask.spawn(tab.linkedBrowser, null, async () => {
    const unexpectedEvent = evt => {
      Assert.ok(
        false,
        `${evt.type} should not be fired for disconnected forms.`
      );
    };

    addEventListener("DOMFormHasPassword", unexpectedEvent);

    const form = content.document.createElement("form");
    const passwordInput = content.document.createElement("input");
    passwordInput.setAttribute("type", "password");
    form.appendChild(passwordInput);

    // Delay the execution for a bit to allow time for any asynchronously
    // dispatched 'DOMFormHasPassword' events to be processed.
    // This is necessary because such events might not be triggered immediately,
    // and we want to ensure that if they are dispatched, they are captured
    // before we remove the event listener.
    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    await new Promise(resolve => setTimeout(resolve, 50));
    removeEventListener("DOMFormHasPassword", unexpectedEvent);
  });

  Assert.ok(true, "Test completed");
  gBrowser.removeCurrentTab();
});

add_task(async function () {
  let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser));

  // TODO: Switch to SpecialPowers.spawn
  // eslint-disable-next-line mozilla/reject-contenttask-spawn
  let promise = ContentTask.spawn(tab.linkedBrowser, ids, task);
  BrowserTestUtils.startLoadingURIString(
    tab.linkedBrowser,
    `data:text/html;charset=utf-8,
      <html><body>
      <form id="${ids.FORM1_ID}">
        <input id="${ids.CHANGE_INPUT_ID}">
      </form>
      <form id="${ids.FORM2_ID}"></form>
      </body></html>`
  );
  await promise;

  Assert.ok(true, "Test completed");
  gBrowser.removeCurrentTab();
});
