// META: script=/common/get-host-info.sub.js

promise_test(async t => {
  let iframeLoadCounter = 0;
  const div = document.createElement('div');
  const iframe = document.createElement('iframe');
  t.add_cleanup(() => iframe.remove());

  const loadPromise = new Promise((resolve) => {
    iframe.onload = () => {
      iframeLoadCounter++;
      resolve()
    };
  });

  div.append(iframe);
  document.body.append(div);
  await loadPromise;
  assert_equals(iframeLoadCounter, 1, "iframe loads");

  const innerDocument = iframe.contentDocument;
  assert_true(innerDocument !== null, "about:blank Document is reachable");

  iframe.onload = () => iframeLoadCounter++;
  await new Promise(resolve => t.step_timeout(resolve, 50));
  document.body.moveBefore(iframe, null);
  assert_equals(iframe.contentDocument, innerDocument, "Document is preserved");
  assert_equals(iframeLoadCounter, 1, "iframe does not reload");
}, "moveBefore(): about:blank iframe's document is preserved");

const kRemoveNewParent = 'remove new parent';
const kRemoveSelf = 'remove self';
const kRemoveSelfViaReplaceChildren = 'remove self via replaceChildren()';
const kRemoveSelfViaInnerHTML = 'remove self via innerHTML';

promise_test(async t => {
  const div = document.createElement('div');
  const iframe = document.createElement('iframe');
  t.add_cleanup(() => iframe.remove());

  const loadPromise = new Promise(resolve => iframe.onload = resolve);
  iframe.src = '/resources/blank.html';

  div.append(iframe);
  document.body.append(div);
  await loadPromise;
  const innerDocument = iframe.contentDocument;

  document.body.moveBefore(iframe, null);
  assert_equals(iframe.contentDocument, innerDocument, "Document is preserved");
}, "moveBefore(): simple same-origin document is preserved");

// This function runs the same test with a few variations. The meat of the test
// loads a cross-origin iframe which asserts that it does not get reloaded.
// Second, we remove the iframe from the parent document in a few different ways
// to trigger initially crashy paths in Chromium during the implementation of
// this feature.
function runTest(removalType) {
  promise_test(async t => {
    let iframeLoadCounter = 0;
    const oldParent = document.createElement('div');
    const newParent = document.createElement('div');
    const iframe = document.createElement('iframe');
    iframe.onload = e => iframeLoadCounter++;
    switch (removalType) {
      case kRemoveNewParent:
        t.add_cleanup(() => newParent.remove());
        break;
      case kRemoveSelf:
        t.add_cleanup(() => iframe.remove());
        break;
      case kRemoveSelfViaReplaceChildren:
        t.add_cleanup(() => newParent.replaceChildren());
        break;
      case kRemoveSelfViaInnerHTML:
        t.add_cleanup(() => {newParent.innerHTML = '';});
        break;
    }

    const loadMessagePromise = new Promise(resolve => window.onmessage = resolve);
    const crossOriginIframeURL = new URL('resources/moveBefore-iframe.html',
        location.href.replace(self.origin, get_host_info().HTTP_REMOTE_ORIGIN));
    iframe.src = crossOriginIframeURL;

    oldParent.append(iframe);
    document.body.append(oldParent, newParent);
    const loadMessage = await loadMessagePromise;
    assert_equals(loadMessage.data, 'loaded');

    const messagePromise = new Promise(resolve => window.onmessage = resolve);
    newParent.moveBefore(iframe, null);
    iframe.contentWindow.postMessage("after moveBefore", "*");
    const message = await messagePromise;
    // If `moveBefore()` behaved just like `insertBefore()`, and reloaded the
    // document, then `message` would contain `loaded` instead of
    // `ack after moveBefore`.
    assert_equals(message.data, 'ack after moveBefore', 'Iframe did not load reload after moveBefore()');
    assert_equals(iframeLoadCounter, 1, "iframe does not fire a second load event");
  }, `moveBefore(): cross-origin iframe is preserved: ${removalType}`);
}

runTest(kRemoveNewParent);
runTest(kRemoveSelf);
runTest(kRemoveSelfViaReplaceChildren);
runTest(kRemoveSelfViaInnerHTML);

promise_test(async t => {
  const iframe1 = document.createElement('iframe');
  iframe1.name = 'iframe1';
  const iframe2 = document.createElement('iframe');
  iframe2.name = 'iframe2';
  const iframe3 = document.createElement('iframe');
  iframe3.name = 'iframe3';

  document.body.append(iframe1, iframe2, iframe3);

  // Assert that the order of iframes in the DOM matches the order of iframes in
  // `window.frames`.
  let iframes = document.querySelectorAll('iframe');
  assert_equals(iframes[0].name, "iframe1", "iframe1 comes first in DOM");
  assert_equals(iframes[1].name, "iframe2", "iframe2 comes second in DOM");
  assert_equals(iframes[2].name, "iframe3", "iframe3 comes last in DOM");
  assert_equals(window.frames[0].name, "iframe1", "iframe1 comes first in frames");
  assert_equals(window.frames[1].name, "iframe2", "iframe2 comes second in frames");
  assert_equals(window.frames[2].name, "iframe3", "iframe3 comes last in frames");

  // Reverse the order of iframes in the DOM.
  document.body.moveBefore(iframe2, iframe1);
  document.body.moveBefore(iframe3, iframe2);

  // Assert that the order of iframes in the DOM is inverse the order of iframes
  // in `window.frames`.
  iframes = document.querySelectorAll('iframe');
  assert_equals(iframes[0].name, "iframe3", "iframe3 comes first in DOM after moveBefore");
  assert_equals(iframes[1].name, "iframe2", "iframe2 comes second in DOM after moveBefore");
  assert_equals(iframes[2].name, "iframe1", "iframe1 comes last in DOM after moveBefore");
  assert_equals(window.frames[0].name, "iframe1", "iframe1 comes first in frames after moveBefore");
  assert_equals(window.frames[1].name, "iframe2", "iframe2 comes second in frames after moveBefore");
  assert_equals(window.frames[2].name, "iframe3", "iframe3 comes last in frames afterMoveBefore");
}, "window.frames ordering does not change due to moveBefore()");
