<!DOCTYPE html>
<html>
<head>
  <title>Test for Bug 1647519</title>
  <meta charset="utf-8">
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <script src="/tests/SimpleTest/EventUtils.js"></script>
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1647519">Mozilla Bug 1647519</a>

<script type="application/javascript">
"use strict";

function promiseMessage(source, filter = event => true) {
  return new Promise(resolve => {
    function listener(event) {
      if (event.source == source && filter(event)) {
        window.removeEventListener("message", listener);
        resolve(event);
      }
    }
    window.addEventListener("message", listener);
  });
}

async function runTests(resourcePath) {
  /* globals Assert, content */
  let doc = content.document;

  // Sends a message to the given target window and waits for a response a few
  // times to (more or less) ensure that a `javascript:` load request has had
  // time to succeed, if it were going to.
  async function doSomeRoundTrips(target) {
    for (let i = 0; i < 3; i++) {
      // Note: The ping message needs to be sent from a script running in the
      // content scope or there will be no source window for the reply to be
      // sent to.
      await content.wrappedJSObject.ping(target);
    }
  }

  function promiseEvent(target, name) {
    return new Promise(resolve => {
      target.addEventListener(name, resolve, { once: true });
    });
  }

  function createIframe(host, id) {
    let iframe = doc.createElement("iframe");
    iframe.id = id;
    iframe.name = id;
    iframe.src = `https://${host}${resourcePath}file_content_javascript_loads_frame.html`;
    doc.body.appendChild(iframe);
    return promiseEvent(iframe, "load");
  }

  const ID_SAME_ORIGIN = "frame-same-origin";
  const ID_SAME_BASE_DOMAIN = "frame-same-base-domain";
  const ID_CROSS_BASE_DOMAIN = "frame-cross-base-domain";

  await Promise.all([
    createIframe("example.com", ID_SAME_ORIGIN),
    createIframe("test1.example.com", ID_SAME_BASE_DOMAIN),
    createIframe("example.org", ID_CROSS_BASE_DOMAIN),
  ]);

  let gotJSLoadFrom = null;
  let pendingJSLoadID = null;
  content.addEventListener("message", event => {
    if ("javascriptLoadID" in event.data) {
      Assert.equal(
        event.data.javascriptLoadID,
        pendingJSLoadID,
        "Message from javascript: load should have the expected ID"
      );
      Assert.equal(
        gotJSLoadFrom,
        null,
        "Should not have seen a previous load message this cycle"
      );
      gotJSLoadFrom = event.source.name;
    }
  });

  async function watchForJSLoads(frameName, expected, task) {
    let loadId = Math.random();

    let jsURI =
      "javascript:" +
      encodeURI(`parent.postMessage({ javascriptLoadID: ${loadId} }, "*")`);

    pendingJSLoadID = loadId;
    gotJSLoadFrom = null;

    await task(jsURI);

    await doSomeRoundTrips(content.wrappedJSObject[frameName]);

    if (expected) {
      Assert.equal(
        gotJSLoadFrom,
        frameName,
        `Should have seen javascript: URI loaded into ${frameName}`
      );
    } else {
      Assert.equal(
        gotJSLoadFrom,
        null,
        "Should not have seen javascript: URI loaded"
      );
    }
  }

  let frames = [
    { name: ID_SAME_ORIGIN, expectLoad: true },
    { name: ID_SAME_BASE_DOMAIN, expectLoad: false },
    { name: ID_CROSS_BASE_DOMAIN, expectLoad: false },
  ];
  for (let { name, expectLoad } of frames) {
    info(`Checking loads for frame "${name}". Expecting loads: ${expectLoad}`);

    info("Checking location setter");
    await watchForJSLoads(name, expectLoad, jsURI => {
      // Note: We need to do this from the content scope since security checks
      // depend on the JS caller scope.
      content.wrappedJSObject.setFrameLocation(name, jsURI);
    });

    info("Checking targeted <a> load");
    await watchForJSLoads(name, expectLoad, jsURI => {
      let a = doc.createElement("a");
      a.target = name;
      a.href = jsURI;
      doc.body.appendChild(a);
      a.click();
      a.remove();
    });

    info("Checking targeted window.open load");
    await watchForJSLoads(name, expectLoad, jsURI => {
      content.wrappedJSObject.open(jsURI, name);
    });
  }
}

add_task(async function() {
  const resourcePath = location.pathname.replace(/[^\/]+$/, "");

  let win = window.open(
    `https://example.com${resourcePath}file_content_javascript_loads_root.html`
  );
  await promiseMessage(win, event => event.data == "ready");

  await SpecialPowers.spawn(win, [resourcePath], runTests);

  win.close();
});
</script>

</body>
</html>
