/*
 * Description of the tests:
 * Tests check that default-src can be overridden by manifest-src.
 */
/*globals Cu, is, ok*/
"use strict";
const { ManifestObtainer } = ChromeUtils.import(
  "resource://gre/modules/ManifestObtainer.jsm"
);
const path = "/tests/dom/security/test/csp/";
const testFile = `${path}file_web_manifest.html`;
const mixedContentFile = `${path}file_web_manifest_mixed_content.html`;
const server = `${path}file_testserver.sjs`;
const defaultURL = new URL(`http://example.org${server}`);
const mixedURL = new URL(`http://mochi.test:8888${server}`);

// Enable web manifest processing.
Services.prefs.setBoolPref("dom.manifest.enabled", true);

const tests = [
  // Check interaction with default-src and another origin,
  // CSP allows fetching from example.org, so manifest should load.
  {
    expected: `CSP manifest-src overrides default-src of elsewhere.com`,
    get tabURL() {
      const url = new URL(defaultURL);
      url.searchParams.append("file", testFile);
      url.searchParams.append("cors", "*");
      url.searchParams.append(
        "csp",
        "default-src http://elsewhere.com; manifest-src http://example.org"
      );
      return url.href;
    },
    run(manifest) {
      is(manifest.name, "loaded", this.expected);
    },
  },
  // Check interaction with default-src none,
  // CSP allows fetching manifest from example.org, so manifest should load.
  {
    expected: `CSP manifest-src overrides default-src`,
    get tabURL() {
      const url = new URL(mixedURL);
      url.searchParams.append("file", mixedContentFile);
      url.searchParams.append("cors", "http://test:80");
      url.searchParams.append(
        "csp",
        "default-src 'self'; manifest-src http://test:80"
      );
      return url.href;
    },
    run(manifest) {
      is(manifest.name, "loaded", this.expected);
    },
  },
];

//jscs:disable
add_task(async function() {
  //jscs:enable
  const testPromises = tests.map(test => {
    const tabOptions = {
      gBrowser,
      url: test.tabURL,
      skipAnimation: true,
    };
    return BrowserTestUtils.withNewTab(tabOptions, browser =>
      testObtainingManifest(browser, test)
    );
  });
  await Promise.all(testPromises);
});

async function testObtainingManifest(aBrowser, aTest) {
  const expectsBlocked = aTest.expected.includes("block");
  const observer = expectsBlocked ? createNetObserver(aTest) : null;
  // Expect an exception (from promise rejection) if there a content policy
  // that is violated.
  try {
    const manifest = await ManifestObtainer.browserObtainManifest(aBrowser);
    aTest.run(manifest);
  } catch (e) {
    const wasBlocked = e.message.includes(
      "NetworkError when attempting to fetch resource"
    );
    ok(
      wasBlocked,
      `Expected promise rejection obtaining ${aTest.tabURL}: ${e.message}`
    );
    if (observer) {
      await observer.untilFinished;
    }
  }
}

// Helper object used to observe policy violations. It waits 1 seconds
// for a response, and then times out causing its associated test to fail.
function createNetObserver(test) {
  let finishedTest;
  let success = false;
  const finished = new Promise(resolver => {
    finishedTest = resolver;
  });
  const timeoutId = setTimeout(() => {
    if (!success) {
      test.run("This test timed out.");
      finishedTest();
    }
  }, 1000);
  var observer = {
    get untilFinished() {
      return finished;
    },
    observe(subject, topic) {
      SpecialPowers.removeObserver(observer, "csp-on-violate-policy");
      test.run(topic);
      finishedTest();
      clearTimeout(timeoutId);
      success = true;
    },
  };
  SpecialPowers.addObserver(observer, "csp-on-violate-policy");
  return observer;
}
