forked from mirrors/gecko-dev
		
	Implement manifest v3 CSP that is compatible with the current chrome implementation.
Support for content_security_policy.isolated_world (a.k.a. content_security_policy.content_scripts)
has been removed for consistency with
345390adf6%5E%21/
Differential Revision: https://phabricator.services.mozilla.com/D100573
		
	
			
		
			
				
	
	
		
			422 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			422 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
"use strict";
 | 
						|
 | 
						|
/**
 | 
						|
 * This script contains the minimum, skeleton content process code that we need
 | 
						|
 * in order to lazily load other extension modules when they are first
 | 
						|
 * necessary. Anything which is not likely to be needed immediately, or shortly
 | 
						|
 * after startup, in *every* browser process live outside of this file.
 | 
						|
 */
 | 
						|
 | 
						|
var EXPORTED_SYMBOLS = ["ExtensionProcessScript"];
 | 
						|
 | 
						|
const { MessageChannel } = ChromeUtils.import(
 | 
						|
  "resource://gre/modules/MessageChannel.jsm"
 | 
						|
);
 | 
						|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 | 
						|
const { XPCOMUtils } = ChromeUtils.import(
 | 
						|
  "resource://gre/modules/XPCOMUtils.jsm"
 | 
						|
);
 | 
						|
 | 
						|
XPCOMUtils.defineLazyModuleGetters(this, {
 | 
						|
  AppConstants: "resource://gre/modules/AppConstants.jsm",
 | 
						|
  ExtensionChild: "resource://gre/modules/ExtensionChild.jsm",
 | 
						|
  ExtensionCommon: "resource://gre/modules/ExtensionCommon.jsm",
 | 
						|
  ExtensionContent: "resource://gre/modules/ExtensionContent.jsm",
 | 
						|
  ExtensionPageChild: "resource://gre/modules/ExtensionPageChild.jsm",
 | 
						|
});
 | 
						|
 | 
						|
const { ExtensionUtils } = ChromeUtils.import(
 | 
						|
  "resource://gre/modules/ExtensionUtils.jsm"
 | 
						|
);
 | 
						|
 | 
						|
XPCOMUtils.defineLazyGetter(this, "console", () =>
 | 
						|
  ExtensionCommon.getConsole()
 | 
						|
);
 | 
						|
 | 
						|
const { DefaultWeakMap, getInnerWindowID } = ExtensionUtils;
 | 
						|
 | 
						|
const { sharedData } = Services.cpmm;
 | 
						|
 | 
						|
function getData(extension, key = "") {
 | 
						|
  return sharedData.get(`extension/${extension.id}/${key}`);
 | 
						|
}
 | 
						|
 | 
						|
// We need to avoid touching Services.appinfo here in order to prevent
 | 
						|
// the wrong version from being cached during xpcshell test startup.
 | 
						|
// eslint-disable-next-line mozilla/use-services
 | 
						|
XPCOMUtils.defineLazyGetter(this, "isContentProcess", () => {
 | 
						|
  return Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
 | 
						|
});
 | 
						|
 | 
						|
XPCOMUtils.defineLazyGetter(this, "isContentScriptProcess", () => {
 | 
						|
  return (
 | 
						|
    isContentProcess ||
 | 
						|
    !WebExtensionPolicy.useRemoteWebExtensions ||
 | 
						|
    // Thunderbird still loads some content in the parent process.
 | 
						|
    AppConstants.MOZ_APP_NAME == "thunderbird"
 | 
						|
  );
 | 
						|
});
 | 
						|
 | 
						|
var extensions = new DefaultWeakMap(policy => {
 | 
						|
  return new ExtensionChild.BrowserExtensionContent(policy);
 | 
						|
});
 | 
						|
 | 
						|
var pendingExtensions = new Map();
 | 
						|
 | 
						|
var ExtensionManager;
 | 
						|
 | 
						|
class ExtensionGlobal {
 | 
						|
  constructor(global) {
 | 
						|
    this.global = global;
 | 
						|
    this.global.addMessageListener("Extension:SetFrameData", this);
 | 
						|
 | 
						|
    this.frameData = null;
 | 
						|
 | 
						|
    MessageChannel.addListener(global, "Extension:DetectLanguage", this);
 | 
						|
    MessageChannel.addListener(global, "WebNavigation:GetFrame", this);
 | 
						|
    MessageChannel.addListener(global, "WebNavigation:GetAllFrames", this);
 | 
						|
  }
 | 
						|
 | 
						|
  get messageFilterStrict() {
 | 
						|
    return {
 | 
						|
      innerWindowID: getInnerWindowID(this.global.content),
 | 
						|
    };
 | 
						|
  }
 | 
						|
 | 
						|
  getFrameData(force = false) {
 | 
						|
    if (!this.frameData && force) {
 | 
						|
      this.frameData = this.global.sendSyncMessage(
 | 
						|
        "Extension:GetTabAndWindowId"
 | 
						|
      )[0];
 | 
						|
    }
 | 
						|
    return this.frameData;
 | 
						|
  }
 | 
						|
 | 
						|
  receiveMessage({ target, messageName, recipient, data, name }) {
 | 
						|
    switch (name) {
 | 
						|
      case "Extension:SetFrameData":
 | 
						|
        if (this.frameData) {
 | 
						|
          Object.assign(this.frameData, data);
 | 
						|
        } else {
 | 
						|
          this.frameData = data;
 | 
						|
        }
 | 
						|
        if (data.viewType && WebExtensionPolicy.isExtensionProcess) {
 | 
						|
          ExtensionPageChild.expectViewLoad(this.global, data.viewType);
 | 
						|
        }
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    // SetFrameData does not have a recipient extension, or it would be
 | 
						|
    // an extension process. Anything following this point must have
 | 
						|
    // a recipient extension, so check access to the window.
 | 
						|
    let policy = WebExtensionPolicy.getByID(recipient.extensionId);
 | 
						|
    if (!policy.canAccessWindow(this.global.content)) {
 | 
						|
      throw new Error("Extension cannot access window");
 | 
						|
    }
 | 
						|
 | 
						|
    return ExtensionContent.receiveMessage(
 | 
						|
      this.global,
 | 
						|
      messageName,
 | 
						|
      target,
 | 
						|
      data,
 | 
						|
      recipient
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
ExtensionManager = {
 | 
						|
  // WeakMap<WebExtensionPolicy, Map<string, WebExtensionContentScript>>
 | 
						|
  registeredContentScripts: new DefaultWeakMap(policy => new Map()),
 | 
						|
 | 
						|
  globals: new WeakMap(),
 | 
						|
 | 
						|
  init() {
 | 
						|
    MessageChannel.setupMessageManagers([Services.cpmm]);
 | 
						|
 | 
						|
    Services.cpmm.addMessageListener("Extension:Startup", this);
 | 
						|
    Services.cpmm.addMessageListener("Extension:Shutdown", this);
 | 
						|
    Services.cpmm.addMessageListener("Extension:FlushJarCache", this);
 | 
						|
    Services.cpmm.addMessageListener("Extension:RegisterContentScript", this);
 | 
						|
    Services.cpmm.addMessageListener(
 | 
						|
      "Extension:UnregisterContentScripts",
 | 
						|
      this
 | 
						|
    );
 | 
						|
 | 
						|
    // eslint-disable-next-line mozilla/balanced-listeners
 | 
						|
    Services.obs.addObserver(
 | 
						|
      global => this.globals.set(global, new ExtensionGlobal(global)),
 | 
						|
      "tab-content-frameloader-created"
 | 
						|
    );
 | 
						|
 | 
						|
    this.updateStubExtensions();
 | 
						|
 | 
						|
    for (let id of sharedData.get("extensions/activeIDs") || []) {
 | 
						|
      this.initExtension(getData({ id }));
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  initStubPolicy(id, data) {
 | 
						|
    let resolveReadyPromise;
 | 
						|
    let readyPromise = new Promise(resolve => {
 | 
						|
      resolveReadyPromise = resolve;
 | 
						|
    });
 | 
						|
 | 
						|
    let policy = new WebExtensionPolicy({
 | 
						|
      id,
 | 
						|
      localizeCallback() {},
 | 
						|
      readyPromise,
 | 
						|
      allowedOrigins: new MatchPatternSet([]),
 | 
						|
      ...data,
 | 
						|
    });
 | 
						|
 | 
						|
    try {
 | 
						|
      policy.active = true;
 | 
						|
 | 
						|
      pendingExtensions.set(id, { policy, resolveReadyPromise });
 | 
						|
    } catch (e) {
 | 
						|
      Cu.reportError(e);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  updateStubExtensions() {
 | 
						|
    for (let [id, data] of sharedData.get("extensions/pending") || []) {
 | 
						|
      if (!pendingExtensions.has(id)) {
 | 
						|
        this.initStubPolicy(id, data);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  initExtensionPolicy(extension) {
 | 
						|
    let policy = WebExtensionPolicy.getByID(extension.id);
 | 
						|
    if (!policy || pendingExtensions.has(extension.id)) {
 | 
						|
      let localizeCallback;
 | 
						|
      if (extension.localize) {
 | 
						|
        // We have a real Extension object.
 | 
						|
        localizeCallback = extension.localize.bind(extension);
 | 
						|
      } else {
 | 
						|
        // We have serialized extension data;
 | 
						|
        localizeCallback = str => extensions.get(policy).localize(str);
 | 
						|
      }
 | 
						|
 | 
						|
      let { backgroundScripts } = extension;
 | 
						|
      if (!backgroundScripts && WebExtensionPolicy.isExtensionProcess) {
 | 
						|
        ({ backgroundScripts } = getData(extension, "extendedData") || {});
 | 
						|
      }
 | 
						|
 | 
						|
      let { backgroundWorkerScript } = extension;
 | 
						|
      if (!backgroundWorkerScript && WebExtensionPolicy.isExtensionProcess) {
 | 
						|
        ({ backgroundWorkerScript } = getData(extension, "extendedData") || {});
 | 
						|
      }
 | 
						|
 | 
						|
      policy = new WebExtensionPolicy({
 | 
						|
        id: extension.id,
 | 
						|
        mozExtensionHostname: extension.uuid,
 | 
						|
        name: extension.name,
 | 
						|
        baseURL: extension.resourceURL,
 | 
						|
 | 
						|
        isPrivileged: extension.isPrivileged,
 | 
						|
        permissions: extension.permissions,
 | 
						|
        allowedOrigins: extension.allowedOrigins,
 | 
						|
        webAccessibleResources: extension.webAccessibleResources,
 | 
						|
 | 
						|
        manifestVersion: extension.manifestVersion,
 | 
						|
        extensionPageCSP: extension.extensionPageCSP,
 | 
						|
 | 
						|
        localizeCallback,
 | 
						|
 | 
						|
        backgroundScripts,
 | 
						|
        backgroundWorkerScript,
 | 
						|
 | 
						|
        contentScripts: extension.contentScripts,
 | 
						|
      });
 | 
						|
 | 
						|
      policy.debugName = `${JSON.stringify(policy.name)} (ID: ${
 | 
						|
        policy.id
 | 
						|
      }, ${policy.getURL()})`;
 | 
						|
 | 
						|
      // Register any existent dynamically registered content script for the extension
 | 
						|
      // when a content process is started for the first time (which also cover
 | 
						|
      // a content process that crashed and it has been recreated).
 | 
						|
      const registeredContentScripts = this.registeredContentScripts.get(
 | 
						|
        policy
 | 
						|
      );
 | 
						|
 | 
						|
      for (let [scriptId, options] of getData(extension, "contentScripts") ||
 | 
						|
        []) {
 | 
						|
        const script = new WebExtensionContentScript(policy, options);
 | 
						|
 | 
						|
        // If the script is a userScript, add the additional userScriptOptions
 | 
						|
        // property to the WebExtensionContentScript instance.
 | 
						|
        if ("userScriptOptions" in options) {
 | 
						|
          script.userScriptOptions = options.userScriptOptions;
 | 
						|
        }
 | 
						|
 | 
						|
        policy.registerContentScript(script);
 | 
						|
        registeredContentScripts.set(scriptId, script);
 | 
						|
      }
 | 
						|
 | 
						|
      let stub = pendingExtensions.get(extension.id);
 | 
						|
      if (stub) {
 | 
						|
        pendingExtensions.delete(extension.id);
 | 
						|
        stub.policy.active = false;
 | 
						|
        stub.resolveReadyPromise(policy);
 | 
						|
      }
 | 
						|
 | 
						|
      policy.active = true;
 | 
						|
      policy.instanceId = extension.instanceId;
 | 
						|
      policy.optionalPermissions = extension.optionalPermissions;
 | 
						|
    }
 | 
						|
    return policy;
 | 
						|
  },
 | 
						|
 | 
						|
  initExtension(data) {
 | 
						|
    if (typeof data === "string") {
 | 
						|
      data = getData({ id: data });
 | 
						|
    }
 | 
						|
    let policy = this.initExtensionPolicy(data);
 | 
						|
 | 
						|
    policy.injectContentScripts();
 | 
						|
  },
 | 
						|
 | 
						|
  handleEvent(event) {
 | 
						|
    if (
 | 
						|
      event.type === "change" &&
 | 
						|
      event.changedKeys.includes("extensions/pending")
 | 
						|
    ) {
 | 
						|
      this.updateStubExtensions();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  receiveMessage({ name, data }) {
 | 
						|
    try {
 | 
						|
      switch (name) {
 | 
						|
        case "Extension:Startup":
 | 
						|
          this.initExtension(data);
 | 
						|
          break;
 | 
						|
 | 
						|
        case "Extension:Shutdown": {
 | 
						|
          let policy = WebExtensionPolicy.getByID(data.id);
 | 
						|
          if (policy) {
 | 
						|
            if (extensions.has(policy)) {
 | 
						|
              extensions.get(policy).shutdown();
 | 
						|
            }
 | 
						|
 | 
						|
            if (isContentProcess) {
 | 
						|
              policy.active = false;
 | 
						|
            }
 | 
						|
          }
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        case "Extension:FlushJarCache":
 | 
						|
          ExtensionUtils.flushJarCache(data.path);
 | 
						|
          break;
 | 
						|
 | 
						|
        case "Extension:RegisterContentScript": {
 | 
						|
          let policy = WebExtensionPolicy.getByID(data.id);
 | 
						|
 | 
						|
          if (policy) {
 | 
						|
            const registeredContentScripts = this.registeredContentScripts.get(
 | 
						|
              policy
 | 
						|
            );
 | 
						|
            const type =
 | 
						|
              "userScriptOptions" in data.options
 | 
						|
                ? "userScript"
 | 
						|
                : "contentScript";
 | 
						|
 | 
						|
            if (registeredContentScripts.has(data.scriptId)) {
 | 
						|
              Cu.reportError(
 | 
						|
                new Error(
 | 
						|
                  `Registering ${type} ${data.scriptId} on ${data.id} more than once`
 | 
						|
                )
 | 
						|
              );
 | 
						|
            } else {
 | 
						|
              const script = new WebExtensionContentScript(
 | 
						|
                policy,
 | 
						|
                data.options
 | 
						|
              );
 | 
						|
 | 
						|
              // If the script is a userScript, add the additional userScriptOptions
 | 
						|
              // property to the WebExtensionContentScript instance.
 | 
						|
              if (type === "userScript") {
 | 
						|
                script.userScriptOptions = data.options.userScriptOptions;
 | 
						|
              }
 | 
						|
 | 
						|
              policy.registerContentScript(script);
 | 
						|
              registeredContentScripts.set(data.scriptId, script);
 | 
						|
            }
 | 
						|
          }
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        case "Extension:UnregisterContentScripts": {
 | 
						|
          let policy = WebExtensionPolicy.getByID(data.id);
 | 
						|
 | 
						|
          if (policy) {
 | 
						|
            const registeredContentScripts = this.registeredContentScripts.get(
 | 
						|
              policy
 | 
						|
            );
 | 
						|
 | 
						|
            for (const scriptId of data.scriptIds) {
 | 
						|
              const script = registeredContentScripts.get(scriptId);
 | 
						|
              if (script) {
 | 
						|
                policy.unregisterContentScript(script);
 | 
						|
                registeredContentScripts.delete(scriptId);
 | 
						|
              }
 | 
						|
            }
 | 
						|
          }
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    } catch (e) {
 | 
						|
      Cu.reportError(e);
 | 
						|
    }
 | 
						|
    Services.cpmm.sendAsyncMessage(`${name}Complete`);
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
var ExtensionProcessScript = {
 | 
						|
  extensions,
 | 
						|
 | 
						|
  getFrameData(global, force) {
 | 
						|
    let extGlobal = ExtensionManager.globals.get(global);
 | 
						|
    return extGlobal && extGlobal.getFrameData(force);
 | 
						|
  },
 | 
						|
 | 
						|
  initExtension(extension) {
 | 
						|
    return ExtensionManager.initExtensionPolicy(extension);
 | 
						|
  },
 | 
						|
 | 
						|
  initExtensionDocument(policy, doc, privileged) {
 | 
						|
    let extension = extensions.get(policy);
 | 
						|
    if (privileged) {
 | 
						|
      ExtensionPageChild.initExtensionContext(extension, doc.defaultView);
 | 
						|
    } else {
 | 
						|
      ExtensionContent.initExtensionContext(extension, doc.defaultView);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  getExtensionChild(id) {
 | 
						|
    let policy = WebExtensionPolicy.getByID(id);
 | 
						|
    if (policy) {
 | 
						|
      return extensions.get(policy);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  preloadContentScript(contentScript) {
 | 
						|
    if (isContentScriptProcess) {
 | 
						|
      ExtensionContent.contentScripts.get(contentScript).preload();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  loadContentScript(contentScript, window) {
 | 
						|
    return ExtensionContent.contentScripts
 | 
						|
      .get(contentScript)
 | 
						|
      .injectInto(window);
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
ExtensionManager.init();
 |