mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	This patch adds the USER_SCRIPT value as a supported value in the internal WebExtensionContentScript constructor. This patch does not introduce a distinct USER_SCRIPT sandbox yet; that will be done in the next patch. Differential Revision: https://phabricator.services.mozilla.com/D228973
		
			
				
	
	
		
			512 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			512 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 | 
						|
/* vim: set sts=2 sw=2 et tw=80: */
 | 
						|
"use strict";
 | 
						|
 | 
						|
const { newURI } = Services.io;
 | 
						|
 | 
						|
const server = createHttpServer({ hosts: ["example.com"] });
 | 
						|
server.registerDirectory("/data/", do_get_file("data"));
 | 
						|
 | 
						|
async function test_url_matching({
 | 
						|
  manifestVersion = 2,
 | 
						|
  allowedOrigins = [],
 | 
						|
  checkPermissions,
 | 
						|
  expectMatches,
 | 
						|
}) {
 | 
						|
  let policy = new WebExtensionPolicy({
 | 
						|
    id: "foo@bar.baz",
 | 
						|
    mozExtensionHostname: "88fb51cd-159f-4859-83db-7065485bc9b2",
 | 
						|
    baseURL: "file:///foo",
 | 
						|
 | 
						|
    manifestVersion,
 | 
						|
    allowedOrigins: new MatchPatternSet(allowedOrigins),
 | 
						|
    localizeCallback() {},
 | 
						|
  });
 | 
						|
 | 
						|
  let contentScript = new WebExtensionContentScript(policy, {
 | 
						|
    checkPermissions,
 | 
						|
 | 
						|
    matches: new MatchPatternSet(["http://*.foo.com/bar", "*://bar.com/baz/*"]),
 | 
						|
 | 
						|
    excludeMatches: new MatchPatternSet(["*://bar.com/baz/quux"]),
 | 
						|
 | 
						|
    includeGlobs: ["*flerg*", "*.com/bar", "*/quux"].map(
 | 
						|
      glob => new MatchGlob(glob)
 | 
						|
    ),
 | 
						|
 | 
						|
    excludeGlobs: ["*glorg*"].map(glob => new MatchGlob(glob)),
 | 
						|
  });
 | 
						|
 | 
						|
  equal(
 | 
						|
    expectMatches,
 | 
						|
    contentScript.matchesURI(newURI("http://www.foo.com/bar")),
 | 
						|
    `Simple matches include should ${expectMatches ? "" : "not "} match.`
 | 
						|
  );
 | 
						|
 | 
						|
  equal(
 | 
						|
    expectMatches,
 | 
						|
    contentScript.matchesURI(newURI("https://bar.com/baz/xflergx")),
 | 
						|
    `Simple matches include should ${expectMatches ? "" : "not "} match.`
 | 
						|
  );
 | 
						|
 | 
						|
  ok(
 | 
						|
    !contentScript.matchesURI(newURI("https://bar.com/baz/xx")),
 | 
						|
    "Failed includeGlobs match pattern should not match"
 | 
						|
  );
 | 
						|
 | 
						|
  ok(
 | 
						|
    !contentScript.matchesURI(newURI("https://bar.com/baz/quux")),
 | 
						|
    "Excluded match pattern should not match"
 | 
						|
  );
 | 
						|
 | 
						|
  ok(
 | 
						|
    !contentScript.matchesURI(newURI("https://bar.com/baz/xflergxglorgx")),
 | 
						|
    "Excluded match glob should not match"
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
add_task(function test_WebExtensionContentScript_urls_mv2() {
 | 
						|
  return test_url_matching({ manifestVersion: 2, expectMatches: true });
 | 
						|
});
 | 
						|
 | 
						|
add_task(function test_WebExtensionContentScript_urls_mv2_checkPermissions() {
 | 
						|
  return test_url_matching({
 | 
						|
    manifestVersion: 2,
 | 
						|
    checkPermissions: true,
 | 
						|
    expectMatches: false,
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
add_task(function test_WebExtensionContentScript_urls_mv2_with_permissions() {
 | 
						|
  return test_url_matching({
 | 
						|
    manifestVersion: 2,
 | 
						|
    checkPermissions: true,
 | 
						|
    allowedOrigins: ["<all_urls>"],
 | 
						|
    expectMatches: true,
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
add_task(function test_WebExtensionContentScript_urls_mv3() {
 | 
						|
  // checkPermissions ignored here because it's forced for MV3.
 | 
						|
  return test_url_matching({
 | 
						|
    manifestVersion: 3,
 | 
						|
    checkPermissions: false,
 | 
						|
    expectMatches: false,
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
add_task(function test_WebExtensionContentScript_mv3_all_urls() {
 | 
						|
  return test_url_matching({
 | 
						|
    manifestVersion: 3,
 | 
						|
    allowedOrigins: ["<all_urls>"],
 | 
						|
    expectMatches: true,
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
add_task(function test_WebExtensionContentScript_mv3_wildcards() {
 | 
						|
  return test_url_matching({
 | 
						|
    manifestVersion: 3,
 | 
						|
    allowedOrigins: ["*://*.foo.com/*", "*://*.bar.com/*"],
 | 
						|
    expectMatches: true,
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
add_task(function test_WebExtensionContentScript_mv3_specific() {
 | 
						|
  return test_url_matching({
 | 
						|
    manifestVersion: 3,
 | 
						|
    allowedOrigins: ["http://www.foo.com/*", "https://bar.com/*"],
 | 
						|
    expectMatches: true,
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_WebExtensionContentScript_isUserScript() {
 | 
						|
  let policy = new WebExtensionPolicy({
 | 
						|
    id: "foo@bar.baz",
 | 
						|
    mozExtensionHostname: "ba159687-9472-4816-a1b2-8b14721d2ea6",
 | 
						|
    baseURL: "file:///foo/",
 | 
						|
    manifestVersion: 3,
 | 
						|
    allowedOrigins: new MatchPatternSet(["https://example.com/*"]),
 | 
						|
    localizeCallback() {},
 | 
						|
  });
 | 
						|
 | 
						|
  // WebExtensionContentScript defaults to world "ISOLATED", but for user
 | 
						|
  // scripts only "MAIN" and "USER_SCRIPT" worlds are permitted. "MAIN" is
 | 
						|
  // supported by user scripts and non-userScripts, so use that here.
 | 
						|
  const world = "MAIN";
 | 
						|
 | 
						|
  const matches = new MatchPatternSet(["https://example.com/match/*"]);
 | 
						|
  const includeGlobs = [new MatchGlob("*/glob/*")];
 | 
						|
  const exampleMatchesURI = newURI("https://example.com/match/");
 | 
						|
  const exampleGlobURI = newURI("https://example.com/glob/");
 | 
						|
  const exampleNotMatchedURI = newURI("https://example.com/nomatch/");
 | 
						|
  const exampleNoPermissionURI = newURI("https://example.net/glob/");
 | 
						|
 | 
						|
  let defaultScript = new WebExtensionContentScript(policy, {
 | 
						|
    world,
 | 
						|
    matches,
 | 
						|
    includeGlobs,
 | 
						|
  });
 | 
						|
  let nonUserScript = new WebExtensionContentScript(policy, {
 | 
						|
    isUserScript: false,
 | 
						|
    world,
 | 
						|
    matches,
 | 
						|
    includeGlobs,
 | 
						|
  });
 | 
						|
  let userScript = new WebExtensionContentScript(policy, {
 | 
						|
    isUserScript: true,
 | 
						|
    world,
 | 
						|
    matches,
 | 
						|
    includeGlobs,
 | 
						|
  });
 | 
						|
 | 
						|
  // Sanity checks: isUserScript flag is accurate.
 | 
						|
  equal(defaultScript.isUserScript, false, "isUserScript defaults to false");
 | 
						|
  equal(nonUserScript.isUserScript, false, "isUserScript set to false");
 | 
						|
  equal(userScript.isUserScript, true, "isUserScript set to true");
 | 
						|
 | 
						|
  // Default, equivalent to isUserScript=false: matches AND includeGlobs.
 | 
						|
  ok(
 | 
						|
    !defaultScript.matchesURI(exampleMatchesURI),
 | 
						|
    "By default: ignore matches if includeGlobs does not match"
 | 
						|
  );
 | 
						|
  ok(
 | 
						|
    !defaultScript.matchesURI(exampleGlobURI),
 | 
						|
    "By default: ignore includeGlobs if matches does not match"
 | 
						|
  );
 | 
						|
 | 
						|
  // With isUserScript=false explicitly: matches AND includeGlobs
 | 
						|
  ok(
 | 
						|
    !nonUserScript.matchesURI(exampleMatchesURI),
 | 
						|
    "Non-userScript: ignore matches if includeGlobs does not match"
 | 
						|
  );
 | 
						|
  ok(
 | 
						|
    !nonUserScript.matchesURI(exampleGlobURI),
 | 
						|
    "non-userScript: ignore includeGlobs if includeGlobs does not match"
 | 
						|
  );
 | 
						|
 | 
						|
  // With isUserScript=true explicitly: matches OR includeGlobs
 | 
						|
  ok(
 | 
						|
    userScript.matchesURI(exampleMatchesURI),
 | 
						|
    "userScript: accept matches even if includeGlobs does not match"
 | 
						|
  );
 | 
						|
  ok(
 | 
						|
    userScript.matchesURI(exampleGlobURI),
 | 
						|
    "userScript: accept includeGlobs even if matches does not match"
 | 
						|
  );
 | 
						|
  ok(
 | 
						|
    !userScript.matchesURI(exampleNoPermissionURI),
 | 
						|
    "userScript: ignore includeGlobs if permission is missing"
 | 
						|
  );
 | 
						|
 | 
						|
  // Now verify that empty matches is permitted.
 | 
						|
  let nonUserScriptEmptyMatches = new WebExtensionContentScript(policy, {
 | 
						|
    isUserScript: false,
 | 
						|
    world,
 | 
						|
    matches: [],
 | 
						|
    includeGlobs,
 | 
						|
  });
 | 
						|
  let userScriptEmptyMatches = new WebExtensionContentScript(policy, {
 | 
						|
    isUserScript: true,
 | 
						|
    world,
 | 
						|
    matches: [],
 | 
						|
    includeGlobs,
 | 
						|
  });
 | 
						|
  ok(
 | 
						|
    !nonUserScriptEmptyMatches.matchesURI(exampleGlobURI),
 | 
						|
    "non-userScript: ignore includeGlobs with empty matches"
 | 
						|
  );
 | 
						|
  ok(
 | 
						|
    userScriptEmptyMatches.matchesURI(exampleGlobURI),
 | 
						|
    "userScript: accept includeGlobs despite empty matches"
 | 
						|
  );
 | 
						|
  ok(
 | 
						|
    !userScriptEmptyMatches.matchesURI(exampleNotMatchedURI),
 | 
						|
    "userScript: ignore when not matched by includeGlobs (and empty matches)"
 | 
						|
  );
 | 
						|
  ok(
 | 
						|
    !userScriptEmptyMatches.matchesURI(exampleNoPermissionURI),
 | 
						|
    "userScript: ignore includeGlobs (and empty matches) without permission"
 | 
						|
  );
 | 
						|
 | 
						|
  // Now verify that without includeGlobs, that matches works as usual.
 | 
						|
  // The isUserScript=false case is already extensively covered elsewhere, so
 | 
						|
  // we just do a sanity check for isUserScript=true.
 | 
						|
  let userScriptNoGlobs = new WebExtensionContentScript(policy, {
 | 
						|
    isUserScript: true,
 | 
						|
    world,
 | 
						|
    matches,
 | 
						|
  });
 | 
						|
  ok(
 | 
						|
    userScriptNoGlobs.matchesURI(exampleMatchesURI),
 | 
						|
    "userScript: accept matches when includeGlobs is null"
 | 
						|
  );
 | 
						|
  ok(
 | 
						|
    !userScriptNoGlobs.matchesURI(exampleNotMatchedURI),
 | 
						|
    "userScript: ignore when not matched by matches and includeGlobs is null"
 | 
						|
  );
 | 
						|
});
 | 
						|
 | 
						|
add_task(function test_WebExtensionContentScript_restricted() {
 | 
						|
  let tests = [
 | 
						|
    {
 | 
						|
      manifestVersion: 2,
 | 
						|
      permissions: [],
 | 
						|
      expect: false,
 | 
						|
    },
 | 
						|
    {
 | 
						|
      manifestVersion: 2,
 | 
						|
      permissions: ["mozillaAddons"],
 | 
						|
      expect: true,
 | 
						|
    },
 | 
						|
    {
 | 
						|
      manifestVersion: 3,
 | 
						|
      permissions: [],
 | 
						|
      expect: false,
 | 
						|
    },
 | 
						|
    {
 | 
						|
      manifestVersion: 3,
 | 
						|
      permissions: ["mozillaAddons"],
 | 
						|
      expect: true,
 | 
						|
    },
 | 
						|
  ];
 | 
						|
 | 
						|
  for (let { manifestVersion, permissions, expect } of tests) {
 | 
						|
    let policy = new WebExtensionPolicy({
 | 
						|
      id: "foo@bar.baz",
 | 
						|
      mozExtensionHostname: "88fb51cd-159f-4859-83db-7065485bc9b2",
 | 
						|
      baseURL: "file:///foo",
 | 
						|
 | 
						|
      manifestVersion,
 | 
						|
      permissions,
 | 
						|
      allowedOrigins: new MatchPatternSet(["<all_urls>"]),
 | 
						|
      localizeCallback() {},
 | 
						|
    });
 | 
						|
    let contentScript = new WebExtensionContentScript(policy, {
 | 
						|
      checkPermissions: true,
 | 
						|
      matches: new MatchPatternSet(["<all_urls>"]),
 | 
						|
    });
 | 
						|
 | 
						|
    // AMO is on the extensions.webextensions.restrictedDomains list.
 | 
						|
    equal(
 | 
						|
      expect,
 | 
						|
      contentScript.matchesURI(newURI("https://addons.mozilla.org/foo")),
 | 
						|
      `Expect extension with [${permissions}] to ${expect ? "" : "not"} match`
 | 
						|
    );
 | 
						|
  }
 | 
						|
});
 | 
						|
 | 
						|
async function test_frame_matching(meta) {
 | 
						|
  if (AppConstants.platform == "linux") {
 | 
						|
    // The windowless browser currently does not load correctly on Linux on
 | 
						|
    // infra.
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  let baseURL = `http://example.com/data`;
 | 
						|
  let urls = {
 | 
						|
    topLevel: `${baseURL}/file_toplevel.html`,
 | 
						|
    iframe: `${baseURL}/file_iframe.html`,
 | 
						|
    dataURL: "data:,data-URL",
 | 
						|
    javascriptVoid: "javascript://void",
 | 
						|
    javascriptTrue: "javascript:true",
 | 
						|
    srcdoc: "about:srcdoc",
 | 
						|
    aboutBlank: "about:blank",
 | 
						|
  };
 | 
						|
 | 
						|
  let contentPage = await ExtensionTestUtils.loadContentPage(urls.topLevel);
 | 
						|
 | 
						|
  let tests = [
 | 
						|
    {
 | 
						|
      matches: ["http://example.com/data/*"],
 | 
						|
      contentScript: {},
 | 
						|
      topLevel: true,
 | 
						|
      iframe: false,
 | 
						|
      dataURL: false,
 | 
						|
      javascriptVoid: false,
 | 
						|
      javascriptTrue: false,
 | 
						|
      aboutBlank: false,
 | 
						|
      srcdoc: false,
 | 
						|
    },
 | 
						|
 | 
						|
    {
 | 
						|
      matches: ["http://example.com/data/*"],
 | 
						|
      contentScript: {
 | 
						|
        frameID: 0,
 | 
						|
      },
 | 
						|
      topLevel: true,
 | 
						|
      iframe: false,
 | 
						|
      dataURL: false,
 | 
						|
      javascriptVoid: false,
 | 
						|
      javascriptTrue: false,
 | 
						|
      aboutBlank: false,
 | 
						|
      srcdoc: false,
 | 
						|
    },
 | 
						|
 | 
						|
    {
 | 
						|
      matches: ["http://example.com/data/*"],
 | 
						|
      contentScript: {
 | 
						|
        allFrames: true,
 | 
						|
      },
 | 
						|
      topLevel: true,
 | 
						|
      iframe: true,
 | 
						|
      dataURL: false,
 | 
						|
      javascriptVoid: false,
 | 
						|
      javascriptTrue: false,
 | 
						|
      aboutBlank: false,
 | 
						|
      srcdoc: false,
 | 
						|
    },
 | 
						|
 | 
						|
    {
 | 
						|
      matches: ["http://example.com/data/*"],
 | 
						|
      contentScript: {
 | 
						|
        allFrames: true,
 | 
						|
        matchAboutBlank: true,
 | 
						|
      },
 | 
						|
      topLevel: true,
 | 
						|
      iframe: true,
 | 
						|
      // data-URLs used to inherit the principal until Firefox 57 (bug 1324406)
 | 
						|
      // but never did so in Chrome. In any case, match_about_blank should not
 | 
						|
      // match documents at data:-URLs.
 | 
						|
      dataURL: false,
 | 
						|
      javascriptVoid: true,
 | 
						|
      javascriptTrue: true,
 | 
						|
      aboutBlank: true,
 | 
						|
      srcdoc: true,
 | 
						|
    },
 | 
						|
 | 
						|
    {
 | 
						|
      // Note: Chrome triggers a hard error when matchOriginAsFallback is used
 | 
						|
      // without a wildcard path (/*). We fail gracefully for simplicity.
 | 
						|
      matches: ["http://example.com/data/*"],
 | 
						|
      contentScript: {
 | 
						|
        allFrames: true,
 | 
						|
        matchOriginAsFallback: true,
 | 
						|
      },
 | 
						|
      topLevel: true,
 | 
						|
      iframe: true,
 | 
						|
      // matchOriginAsFallback only matches 'matches' if the match pattern's
 | 
						|
      // path component is a wildcard. Since 'matches' is not, nothing happens.
 | 
						|
      dataURL: false,
 | 
						|
      javascriptVoid: true,
 | 
						|
      javascriptTrue: true,
 | 
						|
      aboutBlank: true,
 | 
						|
      srcdoc: true,
 | 
						|
    },
 | 
						|
 | 
						|
    {
 | 
						|
      matches: ["http://example.com/*"],
 | 
						|
      contentScript: {
 | 
						|
        allFrames: true,
 | 
						|
        matchOriginAsFallback: true,
 | 
						|
      },
 | 
						|
      topLevel: true,
 | 
						|
      iframe: true,
 | 
						|
      dataURL: true,
 | 
						|
      javascriptVoid: true,
 | 
						|
      javascriptTrue: true,
 | 
						|
      aboutBlank: true,
 | 
						|
      srcdoc: true,
 | 
						|
    },
 | 
						|
 | 
						|
    {
 | 
						|
      // pattern in "matches" does not match.
 | 
						|
      matches: ["http://foo.com/data/*"],
 | 
						|
      contentScript: {
 | 
						|
        allFrames: true,
 | 
						|
        matchAboutBlank: true,
 | 
						|
      },
 | 
						|
      topLevel: false,
 | 
						|
      iframe: false,
 | 
						|
      dataURL: false,
 | 
						|
      javascriptVoid: false,
 | 
						|
      javascriptTrue: false,
 | 
						|
      aboutBlank: false,
 | 
						|
      srcdoc: false,
 | 
						|
    },
 | 
						|
  ];
 | 
						|
 | 
						|
  // matchesWindowGlobal tests against content frames
 | 
						|
  await contentPage.spawn([{ tests, urls, meta }], args => {
 | 
						|
    let { manifestVersion = 2, allowedOrigins = [], expectMatches } = args.meta;
 | 
						|
 | 
						|
    this.windows = new Map();
 | 
						|
    this.windows.set(this.content.location.href, this.content);
 | 
						|
    for (let c of Array.from(this.content.frames)) {
 | 
						|
      let url = c.location.href;
 | 
						|
      if (url === "about:blank") {
 | 
						|
        // When javascript: URLs are loaded, the resulting URL is about:blank.
 | 
						|
        // Look up the frame's "src" attribute to distinguish them.
 | 
						|
        url = c.frameElement.src;
 | 
						|
      }
 | 
						|
      this.windows.set(url, c);
 | 
						|
    }
 | 
						|
    const { MatchPatternSet, WebExtensionContentScript, WebExtensionPolicy } =
 | 
						|
      Cu.getGlobalForObject(Services);
 | 
						|
    this.policy = new WebExtensionPolicy({
 | 
						|
      id: "foo@bar.baz",
 | 
						|
      mozExtensionHostname: "88fb51cd-159f-4859-83db-7065485bc9b2",
 | 
						|
      baseURL: "file:///foo",
 | 
						|
 | 
						|
      manifestVersion,
 | 
						|
      allowedOrigins: new MatchPatternSet(allowedOrigins),
 | 
						|
      localizeCallback() {},
 | 
						|
    });
 | 
						|
 | 
						|
    let tests = args.tests.map(t => {
 | 
						|
      t.contentScript.matches = new MatchPatternSet(t.matches);
 | 
						|
      t.script = new WebExtensionContentScript(this.policy, t.contentScript);
 | 
						|
      return t;
 | 
						|
    });
 | 
						|
    for (let [i, test] of tests.entries()) {
 | 
						|
      for (let [frame, url] of Object.entries(args.urls)) {
 | 
						|
        let should = test[frame] ? "should" : "should not";
 | 
						|
        let wgc = this.windows.get(url).windowGlobalChild;
 | 
						|
        Assert.equal(
 | 
						|
          test.script.matchesWindowGlobal(wgc),
 | 
						|
          test[frame] && expectMatches,
 | 
						|
          `Script ${i} ${should} match the ${frame} frame`
 | 
						|
        );
 | 
						|
      }
 | 
						|
    }
 | 
						|
  });
 | 
						|
 | 
						|
  await contentPage.close();
 | 
						|
}
 | 
						|
 | 
						|
add_task(function test_WebExtensionContentScript_frames_mv2() {
 | 
						|
  return test_frame_matching({
 | 
						|
    manifestVersion: 2,
 | 
						|
    expectMatches: true,
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
add_task(function test_WebExtensionContentScript_frames_mv3() {
 | 
						|
  return test_frame_matching({
 | 
						|
    manifestVersion: 3,
 | 
						|
    expectMatches: false,
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
add_task(function test_WebExtensionContentScript_frames_mv3_all_urls() {
 | 
						|
  return test_frame_matching({
 | 
						|
    manifestVersion: 3,
 | 
						|
    allowedOrigins: ["<all_urls>"],
 | 
						|
    expectMatches: true,
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
add_task(function test_WebExtensionContentScript_frames_mv3_wildcards() {
 | 
						|
  return test_frame_matching({
 | 
						|
    manifestVersion: 3,
 | 
						|
    allowedOrigins: ["*://*.example.com/*"],
 | 
						|
    expectMatches: true,
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
add_task(function test_WebExtensionContentScript_frames_mv3_specific() {
 | 
						|
  return test_frame_matching({
 | 
						|
    manifestVersion: 3,
 | 
						|
    allowedOrigins: ["http://example.com/*"],
 | 
						|
    expectMatches: true,
 | 
						|
  });
 | 
						|
});
 |