forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			545 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			545 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 | 
						|
/* vim: set sts=2 sw=2 et tw=80: */
 | 
						|
"use strict";
 | 
						|
 | 
						|
Services.prefs.setBoolPref("extensions.manifestV3.enabled", true);
 | 
						|
 | 
						|
AddonTestUtils.init(this);
 | 
						|
AddonTestUtils.createAppInfo(
 | 
						|
  "xpcshell@tests.mozilla.org",
 | 
						|
  "XPCShell",
 | 
						|
  "1",
 | 
						|
  "43"
 | 
						|
);
 | 
						|
 | 
						|
const server = createHttpServer({
 | 
						|
  hosts: ["example.net", "example.com"],
 | 
						|
});
 | 
						|
server.registerDirectory("/data/", do_get_file("data"));
 | 
						|
 | 
						|
const pageContent = `<!DOCTYPE html>
 | 
						|
  <script id="script1" src="/data/file_script_good.js"></script>
 | 
						|
  <script id="script3" src="//example.com/data/file_script_bad.js"></script>
 | 
						|
  <img id="img1" src='/data/file_image_good.png'>
 | 
						|
  <img id="img3" src='//example.com/data/file_image_good.png'>
 | 
						|
`;
 | 
						|
 | 
						|
server.registerPathHandler("/", (request, response) => {
 | 
						|
  response.setStatusLine(request.httpVersion, 200, "OK");
 | 
						|
  response.setHeader("Content-Type", "text/html");
 | 
						|
  if (request.queryString) {
 | 
						|
    response.setHeader(
 | 
						|
      "Content-Security-Policy",
 | 
						|
      decodeURIComponent(request.queryString)
 | 
						|
    );
 | 
						|
  }
 | 
						|
  response.write(pageContent);
 | 
						|
});
 | 
						|
 | 
						|
let extensionData = {
 | 
						|
  manifest: {
 | 
						|
    permissions: ["webRequest", "webRequestBlocking", "*://example.net/*"],
 | 
						|
  },
 | 
						|
  background() {
 | 
						|
    let csp_value = undefined;
 | 
						|
    browser.test.onMessage.addListener(function(msg) {
 | 
						|
      csp_value = msg;
 | 
						|
      browser.test.sendMessage("csp-set");
 | 
						|
    });
 | 
						|
    browser.webRequest.onHeadersReceived.addListener(
 | 
						|
      e => {
 | 
						|
        browser.test.log(`onHeadersReceived ${e.requestId} ${e.url}`);
 | 
						|
        if (csp_value === undefined) {
 | 
						|
          browser.test.assertTrue(false, "extension called before CSP was set");
 | 
						|
        }
 | 
						|
        if (csp_value !== null) {
 | 
						|
          e.responseHeaders = e.responseHeaders.filter(
 | 
						|
            i => i.name.toLowerCase() != "content-security-policy"
 | 
						|
          );
 | 
						|
          if (csp_value !== "") {
 | 
						|
            e.responseHeaders.push({
 | 
						|
              name: "Content-Security-Policy",
 | 
						|
              value: csp_value,
 | 
						|
            });
 | 
						|
          }
 | 
						|
        }
 | 
						|
        return { responseHeaders: e.responseHeaders };
 | 
						|
      },
 | 
						|
      { urls: ["*://example.net/*"] },
 | 
						|
      ["blocking", "responseHeaders"]
 | 
						|
    );
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * @typedef {object} ExpectedResourcesToLoad
 | 
						|
 * @property {object} img1_loaded    image from a first party origin.
 | 
						|
 * @property {object} img3_loaded    image from a third party origin.
 | 
						|
 * @property {object} script1_loaded script from a first party origin.
 | 
						|
 * @property {object} script3_loaded script from a third party origin.
 | 
						|
 * @property {object} [cspJSON] expected final document CSP (in JSON format, See dom/webidl/CSPDictionaries.webidl).
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * Test a combination of Content Security Policies against first/third party images/scripts.
 | 
						|
 *
 | 
						|
 * @param {object} opts
 | 
						|
 * @param {string} opts.site_csp The CSP to be sent by the site, or null.
 | 
						|
 * @param {string} opts.ext1_csp The CSP to be sent by the first extension,
 | 
						|
 *                          "" to remove the header, or null to not modify it.
 | 
						|
 * @param {string} opts.ext2_csp The CSP to be sent by the first extension,
 | 
						|
 *                          "" to remove the header, or null to not modify it.
 | 
						|
 * @param {ExpectedResourcesToLoad} opts.expect
 | 
						|
 *                          Object containing information which resources are expected to be loaded.
 | 
						|
 * @param {object} [opts.ext1_data] first test extension definition data (defaults to extensionData).
 | 
						|
 * @param {object} [opts.ext2_data] second test extension definition data (defaults to extensionData).
 | 
						|
 */
 | 
						|
async function test_csp({
 | 
						|
  site_csp,
 | 
						|
  ext1_csp,
 | 
						|
  ext2_csp,
 | 
						|
  expect,
 | 
						|
  ext1_data = extensionData,
 | 
						|
  ext2_data = extensionData,
 | 
						|
}) {
 | 
						|
  let extension1 = await ExtensionTestUtils.loadExtension(ext1_data);
 | 
						|
  let extension2 = await ExtensionTestUtils.loadExtension(ext2_data);
 | 
						|
  await extension1.startup();
 | 
						|
  await extension2.startup();
 | 
						|
  extension1.sendMessage(ext1_csp);
 | 
						|
  extension2.sendMessage(ext2_csp);
 | 
						|
  await extension1.awaitMessage("csp-set");
 | 
						|
  await extension2.awaitMessage("csp-set");
 | 
						|
 | 
						|
  let csp_value = encodeURIComponent(site_csp || "");
 | 
						|
  let contentPage = await ExtensionTestUtils.loadContentPage(
 | 
						|
    `http://example.net/?${csp_value}`
 | 
						|
  );
 | 
						|
  let results = await contentPage.spawn(null, async () => {
 | 
						|
    let img1 = this.content.document.getElementById("img1");
 | 
						|
    let img3 = this.content.document.getElementById("img3");
 | 
						|
    let cspJSON = JSON.parse(this.content.document.cspJSON);
 | 
						|
    return {
 | 
						|
      img1_loaded: img1.complete && img1.naturalWidth > 0,
 | 
						|
      img3_loaded: img3.complete && img3.naturalWidth > 0,
 | 
						|
      // Note: "good" and "bad" are just placeholders; they don't mean anything.
 | 
						|
      script1_loaded: !!this.content.document.getElementById("good"),
 | 
						|
      script3_loaded: !!this.content.document.getElementById("bad"),
 | 
						|
      cspJSON,
 | 
						|
    };
 | 
						|
  });
 | 
						|
 | 
						|
  await contentPage.close();
 | 
						|
  await extension1.unload();
 | 
						|
  await extension2.unload();
 | 
						|
 | 
						|
  let action = {
 | 
						|
    true: "loaded",
 | 
						|
    false: "blocked",
 | 
						|
  };
 | 
						|
 | 
						|
  info(
 | 
						|
    `test_csp: From "${site_csp}" to ${JSON.stringify(
 | 
						|
      ext1_csp
 | 
						|
    )} to ${JSON.stringify(ext2_csp)}`
 | 
						|
  );
 | 
						|
 | 
						|
  equal(
 | 
						|
    expect.img1_loaded,
 | 
						|
    results.img1_loaded,
 | 
						|
    `expected first party image to be ${action[expect.img1_loaded]}`
 | 
						|
  );
 | 
						|
  equal(
 | 
						|
    expect.img3_loaded,
 | 
						|
    results.img3_loaded,
 | 
						|
    `expected third party image to be ${action[expect.img3_loaded]}`
 | 
						|
  );
 | 
						|
  equal(
 | 
						|
    expect.script1_loaded,
 | 
						|
    results.script1_loaded,
 | 
						|
    `expected first party script to be ${action[expect.script1_loaded]}`
 | 
						|
  );
 | 
						|
  equal(
 | 
						|
    expect.script3_loaded,
 | 
						|
    results.script3_loaded,
 | 
						|
    `expected third party script to be ${action[expect.script3_loaded]}`
 | 
						|
  );
 | 
						|
 | 
						|
  if (expect.cspJSON) {
 | 
						|
    Assert.deepEqual(
 | 
						|
      expect.cspJSON,
 | 
						|
      results.cspJSON["csp-policies"],
 | 
						|
      `Got the expected final CSP set on the content document`
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
add_setup(async () => {
 | 
						|
  await AddonTestUtils.promiseStartupManager();
 | 
						|
});
 | 
						|
 | 
						|
// Test that merging csp header on both mv2 and mv3 extensions
 | 
						|
// (and combination of both).
 | 
						|
add_task(async function test_webRequest_mergecsp() {
 | 
						|
  const testCases = [
 | 
						|
    {
 | 
						|
      site_csp: "default-src *",
 | 
						|
      ext1_csp: "script-src 'none'",
 | 
						|
      ext2_csp: null,
 | 
						|
      expect: {
 | 
						|
        img1_loaded: true,
 | 
						|
        img3_loaded: true,
 | 
						|
        script1_loaded: false,
 | 
						|
        script3_loaded: false,
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      site_csp: null,
 | 
						|
      ext1_csp: "script-src 'none'",
 | 
						|
      ext2_csp: null,
 | 
						|
      expect: {
 | 
						|
        img1_loaded: true,
 | 
						|
        img3_loaded: true,
 | 
						|
        script1_loaded: false,
 | 
						|
        script3_loaded: false,
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      site_csp: "default-src *",
 | 
						|
      ext1_csp: "script-src 'none'",
 | 
						|
      ext2_csp: "img-src 'none'",
 | 
						|
      expect: {
 | 
						|
        img1_loaded: false,
 | 
						|
        img3_loaded: false,
 | 
						|
        script1_loaded: false,
 | 
						|
        script3_loaded: false,
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      site_csp: null,
 | 
						|
      ext1_csp: "script-src 'none'",
 | 
						|
      ext2_csp: "img-src 'none'",
 | 
						|
      expect: {
 | 
						|
        img1_loaded: false,
 | 
						|
        img3_loaded: false,
 | 
						|
        script1_loaded: false,
 | 
						|
        script3_loaded: false,
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      site_csp: "default-src *",
 | 
						|
      ext1_csp: "img-src example.com",
 | 
						|
      ext2_csp: "img-src example.org",
 | 
						|
      expect: {
 | 
						|
        img1_loaded: false,
 | 
						|
        img3_loaded: false,
 | 
						|
        script1_loaded: true,
 | 
						|
        script3_loaded: true,
 | 
						|
      },
 | 
						|
    },
 | 
						|
  ];
 | 
						|
 | 
						|
  const extMV2Data = { ...extensionData };
 | 
						|
  const extMV3Data = {
 | 
						|
    ...extensionData,
 | 
						|
    useAddonManager: "temporary",
 | 
						|
    manifest: {
 | 
						|
      ...extensionData.manifest,
 | 
						|
      manifest_version: 3,
 | 
						|
      permissions: ["webRequest", "webRequestBlocking"],
 | 
						|
      host_permissions: ["*://example.net/*"],
 | 
						|
      granted_host_permissions: true,
 | 
						|
    },
 | 
						|
  };
 | 
						|
 | 
						|
  info("Run all test cases on ext1 MV2 and ext2 MV2");
 | 
						|
  for (const testCase of testCases) {
 | 
						|
    await test_csp({
 | 
						|
      ...testCase,
 | 
						|
      ext1_data: extMV2Data,
 | 
						|
      ext2_data: extMV2Data,
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  info("Run all test cases on ext1 MV3 and ext2 MV3");
 | 
						|
  for (const testCase of testCases) {
 | 
						|
    await test_csp({
 | 
						|
      ...testCase,
 | 
						|
      ext1_data: extMV3Data,
 | 
						|
      ext2_data: extMV3Data,
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  info("Run all test cases on ext1 MV3 and ext2 MV2");
 | 
						|
  for (const testCase of testCases) {
 | 
						|
    await test_csp({
 | 
						|
      ...testCase,
 | 
						|
      ext1_data: extMV3Data,
 | 
						|
      ext2_data: extMV2Data,
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  info("Run all test cases on ext1 MV2 and ext2 MV3");
 | 
						|
  for (const testCase of testCases) {
 | 
						|
    await test_csp({
 | 
						|
      ...testCase,
 | 
						|
      ext1_data: extMV2Data,
 | 
						|
      ext2_data: extMV3Data,
 | 
						|
    });
 | 
						|
  }
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_remove_and_replace_csp_mv2() {
 | 
						|
  // CSP removed, CSP added.
 | 
						|
  await test_csp({
 | 
						|
    site_csp: "img-src 'self'",
 | 
						|
    ext1_csp: "",
 | 
						|
    ext2_csp: "img-src example.com",
 | 
						|
    expect: {
 | 
						|
      img1_loaded: false,
 | 
						|
      img3_loaded: true,
 | 
						|
      script1_loaded: true,
 | 
						|
      script3_loaded: true,
 | 
						|
    },
 | 
						|
  });
 | 
						|
 | 
						|
  // CSP removed, CSP added.
 | 
						|
  await test_csp({
 | 
						|
    site_csp: "default-src 'none'",
 | 
						|
    ext1_csp: "",
 | 
						|
    ext2_csp: "img-src example.com",
 | 
						|
    expect: {
 | 
						|
      img1_loaded: false,
 | 
						|
      img3_loaded: true,
 | 
						|
      script1_loaded: true,
 | 
						|
      script3_loaded: true,
 | 
						|
    },
 | 
						|
  });
 | 
						|
 | 
						|
  // CSP replaced - regression test for bug 1635781.
 | 
						|
  await test_csp({
 | 
						|
    site_csp: "default-src 'none'",
 | 
						|
    ext1_csp: "img-src example.com",
 | 
						|
    ext2_csp: null,
 | 
						|
    expect: {
 | 
						|
      img1_loaded: false,
 | 
						|
      img3_loaded: true,
 | 
						|
      script1_loaded: true,
 | 
						|
      script3_loaded: true,
 | 
						|
    },
 | 
						|
  });
 | 
						|
 | 
						|
  // CSP unchanged, CSP replaced - regression test for bug 1635781.
 | 
						|
  await test_csp({
 | 
						|
    site_csp: "default-src 'none'",
 | 
						|
    ext1_csp: null,
 | 
						|
    ext2_csp: "img-src example.com",
 | 
						|
    expect: {
 | 
						|
      img1_loaded: false,
 | 
						|
      img3_loaded: true,
 | 
						|
      script1_loaded: true,
 | 
						|
      script3_loaded: true,
 | 
						|
    },
 | 
						|
  });
 | 
						|
 | 
						|
  // CSP replaced, CSP removed.
 | 
						|
  await test_csp({
 | 
						|
    site_csp: "default-src 'none'",
 | 
						|
    ext1_csp: "img-src example.com",
 | 
						|
    ext2_csp: "",
 | 
						|
    expect: {
 | 
						|
      img1_loaded: true,
 | 
						|
      img3_loaded: true,
 | 
						|
      script1_loaded: true,
 | 
						|
      script3_loaded: true,
 | 
						|
    },
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
// Test that fully replace the website csp header from an mv3 extension
 | 
						|
// isn't allowed and it is considered a no-op.
 | 
						|
add_task(async function test_remove_and_replace_csp_mv3() {
 | 
						|
  const extMV2Data = { ...extensionData };
 | 
						|
 | 
						|
  const extMV3Data = {
 | 
						|
    ...extensionData,
 | 
						|
    useAddonManager: "temporary",
 | 
						|
    manifest: {
 | 
						|
      ...extensionData.manifest,
 | 
						|
      manifest_version: 3,
 | 
						|
      permissions: ["webRequest", "webRequestBlocking"],
 | 
						|
      host_permissions: ["*://example.net/*"],
 | 
						|
      granted_host_permissions: true,
 | 
						|
    },
 | 
						|
  };
 | 
						|
 | 
						|
  await test_csp({
 | 
						|
    // site: CSP strict on images, lax on default and script src.
 | 
						|
    site_csp: "img-src 'self'",
 | 
						|
    // ext1: MV3 extension which return an empty CSP header (which is a no-op).
 | 
						|
    ext1_csp: "",
 | 
						|
    // ext2: MV3 extension which return a CSP header (which is expected to be merged).
 | 
						|
    ext2_csp: "img-src example.com",
 | 
						|
    expect: {
 | 
						|
      img1_loaded: false,
 | 
						|
      img3_loaded: false,
 | 
						|
      script1_loaded: true,
 | 
						|
      script3_loaded: true,
 | 
						|
      cspJSON: [
 | 
						|
        { "img-src": ["'self'"], "report-only": false },
 | 
						|
        { "img-src": ["http://example.com"], "report-only": false },
 | 
						|
      ],
 | 
						|
    },
 | 
						|
    ext1_data: extMV3Data,
 | 
						|
    ext2_data: extMV3Data,
 | 
						|
  });
 | 
						|
 | 
						|
  await test_csp({
 | 
						|
    // site: CSP strict on default-src.
 | 
						|
    site_csp: "default-src 'none'",
 | 
						|
    // ext1: MV3 extension which return an empty CSP header (which is a no-op).
 | 
						|
    ext1_csp: "",
 | 
						|
    // ext2: MV3 extension which return a CSP header (which is expected to be merged).
 | 
						|
    ext2_csp: "img-src example.com",
 | 
						|
    expect: {
 | 
						|
      img1_loaded: false,
 | 
						|
      img3_loaded: false,
 | 
						|
      script1_loaded: false,
 | 
						|
      script3_loaded: false,
 | 
						|
      cspJSON: [
 | 
						|
        { "default-src": ["'none'"], "report-only": false },
 | 
						|
        { "img-src": ["http://example.com"], "report-only": false },
 | 
						|
      ],
 | 
						|
    },
 | 
						|
    ext1_data: extMV3Data,
 | 
						|
    ext2_data: extMV3Data,
 | 
						|
  });
 | 
						|
 | 
						|
  await test_csp({
 | 
						|
    // site: CSP strict on default-src.
 | 
						|
    site_csp: "default-src 'none'",
 | 
						|
    // ext1: MV3 extension which return a CSP header (which is expected to be merged and to
 | 
						|
    // not be able to make it less strict).
 | 
						|
    ext1_csp: "img-src example.com",
 | 
						|
    // ext2: MV3 extension which leaves the header unmodified.
 | 
						|
    ext2_csp: null,
 | 
						|
    expect: {
 | 
						|
      img1_loaded: false,
 | 
						|
      img3_loaded: false,
 | 
						|
      script1_loaded: false,
 | 
						|
      script3_loaded: false,
 | 
						|
      cspJSON: [
 | 
						|
        { "default-src": ["'none'"], "report-only": false },
 | 
						|
        { "img-src": ["http://example.com"], "report-only": false },
 | 
						|
      ],
 | 
						|
    },
 | 
						|
    ext1_data: extMV3Data,
 | 
						|
    ext2_data: extMV3Data,
 | 
						|
  });
 | 
						|
 | 
						|
  await test_csp({
 | 
						|
    // site: CSP strict on default-src.
 | 
						|
    site_csp: "default-src 'none'",
 | 
						|
    // ext1: MV3 extension which merges additional directive into the site csp (and can't make
 | 
						|
    // it less strict).
 | 
						|
    ext1_csp: "img-src example.com",
 | 
						|
    // ext2: MV3 extension which merges an empty CSP header (which is a no-op, unlike with MV2).
 | 
						|
    ext2_csp: "",
 | 
						|
    expect: {
 | 
						|
      img1_loaded: false,
 | 
						|
      img3_loaded: false,
 | 
						|
      script1_loaded: false,
 | 
						|
      script3_loaded: false,
 | 
						|
      cspJSON: [
 | 
						|
        { "default-src": ["'none'"], "report-only": false },
 | 
						|
        { "img-src": ["http://example.com"], "report-only": false },
 | 
						|
      ],
 | 
						|
    },
 | 
						|
    ext1_data: extMV3Data,
 | 
						|
    ext2_data: extMV3Data,
 | 
						|
  });
 | 
						|
 | 
						|
  await test_csp({
 | 
						|
    // site: lax CSP (which is expected to be made stricted by the ext1 extension).
 | 
						|
    site_csp: "default-src *",
 | 
						|
    // ext1: MV3 extension which wants to set a stricter CSP (expected to work fine with the MV3 extension)
 | 
						|
    ext1_csp: "default-src 'none'",
 | 
						|
    // ext2: MV3 extension which leaves it unchanged.
 | 
						|
    ext2_csp: null,
 | 
						|
    expect: {
 | 
						|
      img1_loaded: false,
 | 
						|
      img3_loaded: false,
 | 
						|
      script1_loaded: false,
 | 
						|
      script3_loaded: false,
 | 
						|
      cspJSON: [
 | 
						|
        { "default-src": ["*"], "report-only": false },
 | 
						|
        { "default-src": ["'none'"], "report-only": false },
 | 
						|
      ],
 | 
						|
    },
 | 
						|
    ext1_data: extMV3Data,
 | 
						|
    ext2_data: extMV3Data,
 | 
						|
  });
 | 
						|
 | 
						|
  await test_csp({
 | 
						|
    // site: CSP strict on default-src.
 | 
						|
    site_csp: "default-src 'none'",
 | 
						|
    // ext1: MV3 extension and tries to replace the strict site csp with this lax one
 | 
						|
    // (but as an MV3 extension that is going to be merged to the site csp and the
 | 
						|
    // resulting site CSP is expected to stay strict).
 | 
						|
    ext1_csp: "default-src *",
 | 
						|
    // ext2: MV3 extension which leaves it unchanged.
 | 
						|
    ext2_csp: null,
 | 
						|
    expect: {
 | 
						|
      // strict site csp merged with the lax one from ext1 stays strict.
 | 
						|
      img1_loaded: false,
 | 
						|
      img3_loaded: false,
 | 
						|
      script1_loaded: false,
 | 
						|
      script3_loaded: false,
 | 
						|
      cspJSON: [
 | 
						|
        { "default-src": ["'none'"], "report-only": false },
 | 
						|
        { "default-src": ["*"], "report-only": false },
 | 
						|
      ],
 | 
						|
    },
 | 
						|
    ext1_data: extMV3Data,
 | 
						|
    ext2_data: extMV3Data,
 | 
						|
  });
 | 
						|
 | 
						|
  await test_csp({
 | 
						|
    // site: CSP strict on default-src.
 | 
						|
    site_csp: "default-src 'none'",
 | 
						|
    // ext1: MV3 extension which return an empty CSP (expected to be a no-op for an MV3 extension).
 | 
						|
    ext1_csp: "",
 | 
						|
    // ext2: MV2 exension which wants to replace the site csp with a lax one (and still be allowed to
 | 
						|
    // because the empty one from the MV3 extension is expected to be a no-op).
 | 
						|
    ext2_csp: "default-src *",
 | 
						|
    expect: {
 | 
						|
      img1_loaded: true,
 | 
						|
      img3_loaded: true,
 | 
						|
      script1_loaded: true,
 | 
						|
      script3_loaded: true,
 | 
						|
      cspJSON: [{ "default-src": ["*"], "report-only": false }],
 | 
						|
    },
 | 
						|
    ext1_data: extMV3Data,
 | 
						|
    ext2_data: extMV2Data,
 | 
						|
  });
 | 
						|
 | 
						|
  await test_csp({
 | 
						|
    // site: CSP strict on default-src.
 | 
						|
    site_csp: "default-src 'none'",
 | 
						|
    // ext1: MV3 extension which return an empty CSP (which is expected to be a no-op).
 | 
						|
    ext1_csp: "",
 | 
						|
    // ext2: MV2 extension which also returns an empty CSP (which for an MV2 extension is expected
 | 
						|
    // to clear the CSP).
 | 
						|
    ext2_csp: "",
 | 
						|
    expect: {
 | 
						|
      img1_loaded: true,
 | 
						|
      img3_loaded: true,
 | 
						|
      script1_loaded: true,
 | 
						|
      script3_loaded: true,
 | 
						|
      // Expect the resulting final document CSP to be empty (due to the MV2 extension clearing it).
 | 
						|
      cspJSON: [],
 | 
						|
    },
 | 
						|
    ext1_data: extMV3Data,
 | 
						|
    ext2_data: extMV2Data,
 | 
						|
  });
 | 
						|
});
 |