<!DOCTYPE HTML>

<html>
<head>
<meta charset="utf-8">
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <script src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
  <script type="text/javascript" src="head_webrequest.js"></script>
  <script type="text/javascript" src="head.js"></script>
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script>
"use strict";

let extensionData = {
  manifest: {
    permissions: ["webRequest", "webRequestBlocking", "<all_urls>", "tabs"],
  },
  background() {
    browser.webRequest.onBeforeRequest.addListener(details => {
      if (details.url.endsWith("/favicon.ico")) {
        // We don't care about favicon.ico in this test. It is hard to control
        // whether the request happens.
        browser.test.log(`Ignoring favicon request: ${details.url}`);
        return;
      }
      browser.test.sendMessage("onBeforeRequest", details);
    }, {urls: ["<all_urls>"]}, ["blocking"]);

    let tab;
    browser.tabs.onCreated.addListener(newTab => {
      browser.test.sendMessage("tab-created");
      tab = newTab;
    });

    browser.test.onMessage.addListener(msg => {
      if (msg === "close-tab") {
        browser.tabs.remove(tab.id);
        browser.test.sendMessage("tab-closed");
      }
    });
  },
};

let expected = {
  "file_simple_xhr.html": {
    type: "main_frame",
    toplevel: true,
  },
  "file_image_good.png": {
    type: "image",
    toplevel: true,
    origin: "file_simple_xhr.html",
  },
  "example.txt": {
    type: "xmlhttprequest",
    toplevel: true,
    origin: "file_simple_xhr.html",
  },
  // sub frames will have the origin and first ancestor is the
  // parent document
  "file_simple_xhr_frame.html": {
    type: "sub_frame",
    toplevelParent: true,
    origin: "file_simple_xhr.html",
    parent: "file_simple_xhr.html",
  },
  // a resource in a sub frame will have origin of the subframe,
  // but the ancestor chain starts with the parent document
  "xhr_resource": {
    type: "xmlhttprequest",
    origin: "file_simple_xhr_frame.html",
    parent: "file_simple_xhr.html",
  },
  "file_image_bad.png": {
    type: "image",
    depth: 2,
    origin: "file_simple_xhr_frame.html",
    parent: "file_simple_xhr.html",
  },
  "file_simple_xhr_frame2.html": {
    type: "sub_frame",
    depth: 2,
    origin: "file_simple_xhr_frame.html",
    parent: "file_simple_xhr_frame.html",
  },
  "file_image_redirect.png": {
    type: "image",
    depth: 2,
    origin: "file_simple_xhr_frame2.html",
    parent: "file_simple_xhr_frame.html",
  },
  "xhr_resource_2": {
    type: "xmlhttprequest",
    depth: 2,
    origin: "file_simple_xhr_frame2.html",
    parent: "file_simple_xhr_frame.html",
  },
  // This is loaded in a sandbox iframe.  originUrl is not available for that,
  // and requests within a sandboxed iframe will additionally have an empty
  // url on their immediate parent/ancestor.
  "file_simple_sandboxed_frame.html": {
    type: "sub_frame",
    depth: 3,
    parent: "file_simple_xhr_frame2.html",
  },
  "xhr_sandboxed": {
    type: "xmlhttprequest",
    sandboxed: true,
    depth: 3,
    parent: "",
  },
  "file_image_great.png": {
    type: "image",
    sandboxed: true,
    depth: 3,
    parent: "",
  },
  "file_simple_sandboxed_subframe.html": {
    type: "sub_frame",
    depth: 4,
    parent: "",
  },
};

function checkDetails(details) {
  let url = new URL(details.url);
  let filename = url.pathname.split("/").pop();
  ok(filename in expected, `Should be expecting a request for ${filename}`);
  let expect = expected[filename];
  is(expect.type, details.type, `${details.type} type matches`);
  if (details.parentFrameId == -1) {
    is(details.frameAncestors.length, 0, "no ancestors for main_frame requests");
  } else if (details.parentFrameId == 0) {
    is(details.frameAncestors.length, 1, "one ancestors for sub_frame requests");
  } else {
    ok(details.frameAncestors.length > 1, "have multiple ancestors for deep subframe requests");
    is(details.frameAncestors.length, expect.depth, "have multiple ancestors for deep subframe requests");
  }
  if (details.parentFrameId > -1) {
    ok(!expect.origin || details.originUrl.includes(expect.origin), "origin url is correct");
    is(details.frameAncestors[0].frameId, details.parentFrameId, "first ancestor matches request.parentFrameId");
    ok(details.frameAncestors[0].url.includes(expect.parent), "ancestor parent page correct");
    is(details.frameAncestors[details.frameAncestors.length - 1].frameId, 0, "last ancestor is always zero");
    // All our tests should be somewhere within the frame that we set topframe in the query string.  That
    // frame will always be the last ancestor.
    ok(details.frameAncestors[details.frameAncestors.length - 1].url.includes("topframe=true"), "last ancestor is always topframe");
  }
  if (expect.toplevel) {
    is(details.frameId, 0, "expect load at top level");
    is(details.parentFrameId, -1, "expect top level frame to have no parent");
  } else if (details.type == "sub_frame") {
    ok(details.frameId > 0, "expect sub_frame to load into a new frame");
    if (expect.toplevelParent) {
      is(details.parentFrameId, 0, "expect sub_frame to have top level parent");
      is(details.frameAncestors.length, 1, "one ancestor for top sub_frame request");
    } else {
      ok(details.parentFrameId > 0, "expect sub_frame to have parent");
      ok(details.frameAncestors.length > 1, "sub_frame has ancestors");
    }
    expect.subframeId = details.frameId;
    expect.parentId = details.parentFrameId;
  } else if (expect.sandboxed) {
    is(details.documentUrl, undefined, "null principal documentUrl for sandboxed request");
  } else {
    // get the parent frame.
    let purl = new URL(details.documentUrl);
    let pfilename = purl.pathname.split("/").pop();
    let parent = expected[pfilename];
    is(details.frameId, parent.subframeId, "expect load in subframe");
    is(details.parentFrameId, parent.parentId, "expect subframe parent");
  }
  return filename;
}

add_task(async function test_webRequest_main_frame() {
  // Clear the image cache, since it gets in the way otherwise.
  let imgTools = SpecialPowers.Cc["@mozilla.org/image/tools;1"].getService(SpecialPowers.Ci.imgITools);
  let cache = imgTools.getImgCacheForDocument(document);
  cache.clearCache(false);
  await SpecialPowers.spawnChrome([], async () => {
    Services.cache2.clear();
  });

  let extension = ExtensionTestUtils.loadExtension(extensionData);
  await extension.startup();

  let a = addLink(`file_simple_xhr.html?topframe=true&nocache=${Math.random()}`);
  a.click();

  let remaining = new Set(Object.keys(expected));
  let totalExpectedCount = remaining.size;
  for (let i = 0; i < totalExpectedCount; i++) {
    info(`Waiting for request ${i + 1} out of ${totalExpectedCount}`);
    info(`Expecting one of: ${Array.from(remaining)}`);
    let details = await extension.awaitMessage("onBeforeRequest");
    info(`Checking details for request ${i}: ${JSON.stringify(details)}`);
    let filename = checkDetails(details);
    ok(remaining.delete(filename), `Got only one request for ${filename}`);
  }

  await extension.awaitMessage("tab-created");
  extension.sendMessage("close-tab");
  await extension.awaitMessage("tab-closed");

  await extension.unload();
});
</script>
</head>
<body>
<div id="test">Sample text</div>

</body>
</html>
