forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			495 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			495 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 | 
						|
/* vim: set sts=2 sw=2 et tw=80: */
 | 
						|
/* 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";
 | 
						|
 | 
						|
/* exported ExtensionPageChild */
 | 
						|
 | 
						|
var EXPORTED_SYMBOLS = ["ExtensionPageChild"];
 | 
						|
 | 
						|
/**
 | 
						|
 * This file handles privileged extension page logic that runs in the
 | 
						|
 * child process.
 | 
						|
 */
 | 
						|
 | 
						|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 | 
						|
 | 
						|
ChromeUtils.defineModuleGetter(
 | 
						|
  this,
 | 
						|
  "ExtensionChildDevToolsUtils",
 | 
						|
  "resource://gre/modules/ExtensionChildDevToolsUtils.jsm"
 | 
						|
);
 | 
						|
ChromeUtils.defineModuleGetter(
 | 
						|
  this,
 | 
						|
  "ExtensionProcessScript",
 | 
						|
  "resource://gre/modules/ExtensionProcessScript.jsm"
 | 
						|
);
 | 
						|
ChromeUtils.defineModuleGetter(
 | 
						|
  this,
 | 
						|
  "Schemas",
 | 
						|
  "resource://gre/modules/Schemas.jsm"
 | 
						|
);
 | 
						|
ChromeUtils.defineModuleGetter(
 | 
						|
  this,
 | 
						|
  "WebNavigationFrames",
 | 
						|
  "resource://gre/modules/WebNavigationFrames.jsm"
 | 
						|
);
 | 
						|
 | 
						|
const CATEGORY_EXTENSION_SCRIPTS_ADDON = "webextension-scripts-addon";
 | 
						|
const CATEGORY_EXTENSION_SCRIPTS_DEVTOOLS = "webextension-scripts-devtools";
 | 
						|
 | 
						|
const { ExtensionCommon } = ChromeUtils.import(
 | 
						|
  "resource://gre/modules/ExtensionCommon.jsm"
 | 
						|
);
 | 
						|
const { ExtensionChild, ExtensionActivityLogChild } = ChromeUtils.import(
 | 
						|
  "resource://gre/modules/ExtensionChild.jsm"
 | 
						|
);
 | 
						|
const { ExtensionUtils } = ChromeUtils.import(
 | 
						|
  "resource://gre/modules/ExtensionUtils.jsm"
 | 
						|
);
 | 
						|
 | 
						|
const { getInnerWindowID, promiseEvent } = ExtensionUtils;
 | 
						|
 | 
						|
const {
 | 
						|
  BaseContext,
 | 
						|
  CanOfAPIs,
 | 
						|
  SchemaAPIManager,
 | 
						|
  defineLazyGetter,
 | 
						|
} = ExtensionCommon;
 | 
						|
 | 
						|
const { ChildAPIManager, Messenger } = ExtensionChild;
 | 
						|
 | 
						|
var ExtensionPageChild;
 | 
						|
 | 
						|
const initializeBackgroundPage = context => {
 | 
						|
  // Override the `alert()` method inside background windows;
 | 
						|
  // we alias it to console.log().
 | 
						|
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1203394
 | 
						|
  let alertDisplayedWarning = false;
 | 
						|
  const innerWindowID = getInnerWindowID(context.contentWindow);
 | 
						|
 | 
						|
  function logWarningMessage({ text, filename, lineNumber, columnNumber }) {
 | 
						|
    let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(
 | 
						|
      Ci.nsIScriptError
 | 
						|
    );
 | 
						|
    consoleMsg.initWithWindowID(
 | 
						|
      text,
 | 
						|
      filename,
 | 
						|
      null,
 | 
						|
      lineNumber,
 | 
						|
      columnNumber,
 | 
						|
      Ci.nsIScriptError.warningFlag,
 | 
						|
      "webextension",
 | 
						|
      innerWindowID
 | 
						|
    );
 | 
						|
    Services.console.logMessage(consoleMsg);
 | 
						|
  }
 | 
						|
 | 
						|
  let alertOverwrite = text => {
 | 
						|
    const { filename, columnNumber, lineNumber } = Components.stack.caller;
 | 
						|
 | 
						|
    if (!alertDisplayedWarning) {
 | 
						|
      context.childManager.callParentAsyncFunction(
 | 
						|
        "runtime.openBrowserConsole",
 | 
						|
        []
 | 
						|
      );
 | 
						|
 | 
						|
      logWarningMessage({
 | 
						|
        text:
 | 
						|
          "alert() is not supported in background windows; please use console.log instead.",
 | 
						|
        filename,
 | 
						|
        lineNumber,
 | 
						|
        columnNumber,
 | 
						|
      });
 | 
						|
 | 
						|
      alertDisplayedWarning = true;
 | 
						|
    }
 | 
						|
 | 
						|
    logWarningMessage({ text, filename, lineNumber, columnNumber });
 | 
						|
  };
 | 
						|
  Cu.exportFunction(alertOverwrite, context.contentWindow, {
 | 
						|
    defineAs: "alert",
 | 
						|
  });
 | 
						|
};
 | 
						|
 | 
						|
function getFrameData(global) {
 | 
						|
  return ExtensionProcessScript.getFrameData(global, true);
 | 
						|
}
 | 
						|
 | 
						|
var apiManager = new (class extends SchemaAPIManager {
 | 
						|
  constructor() {
 | 
						|
    super("addon", Schemas);
 | 
						|
    this.initialized = false;
 | 
						|
  }
 | 
						|
 | 
						|
  lazyInit() {
 | 
						|
    if (!this.initialized) {
 | 
						|
      this.initialized = true;
 | 
						|
      this.initGlobal();
 | 
						|
      for (let { value } of Services.catMan.enumerateCategory(
 | 
						|
        CATEGORY_EXTENSION_SCRIPTS_ADDON
 | 
						|
      )) {
 | 
						|
        this.loadScript(value);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
})();
 | 
						|
 | 
						|
var devtoolsAPIManager = new (class extends SchemaAPIManager {
 | 
						|
  constructor() {
 | 
						|
    super("devtools", Schemas);
 | 
						|
    this.initialized = false;
 | 
						|
  }
 | 
						|
 | 
						|
  lazyInit() {
 | 
						|
    if (!this.initialized) {
 | 
						|
      this.initialized = true;
 | 
						|
      this.initGlobal();
 | 
						|
      for (let { value } of Services.catMan.enumerateCategory(
 | 
						|
        CATEGORY_EXTENSION_SCRIPTS_DEVTOOLS
 | 
						|
      )) {
 | 
						|
        this.loadScript(value);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
})();
 | 
						|
 | 
						|
class ExtensionBaseContextChild extends BaseContext {
 | 
						|
  /**
 | 
						|
   * This ExtensionBaseContextChild represents an addon execution environment
 | 
						|
   * that is running in an addon or devtools child process.
 | 
						|
   *
 | 
						|
   * @param {BrowserExtensionContent} extension This context's owner.
 | 
						|
   * @param {object} params
 | 
						|
   * @param {string} params.envType One of "addon_child" or "devtools_child".
 | 
						|
   * @param {nsIDOMWindow} params.contentWindow The window where the addon runs.
 | 
						|
   * @param {string} params.viewType One of "background", "popup", "tab",
 | 
						|
   *   "sidebar", "devtools_page" or "devtools_panel".
 | 
						|
   * @param {number} [params.tabId] This tab's ID, used if viewType is "tab".
 | 
						|
   */
 | 
						|
  constructor(extension, params) {
 | 
						|
    if (!params.envType) {
 | 
						|
      throw new Error("Missing envType");
 | 
						|
    }
 | 
						|
 | 
						|
    super(params.envType, extension);
 | 
						|
    let { viewType = "tab", uri, contentWindow, tabId } = params;
 | 
						|
    this.viewType = viewType;
 | 
						|
    this.uri = uri || extension.baseURI;
 | 
						|
 | 
						|
    this.setContentWindow(contentWindow);
 | 
						|
    this.browsingContextId = contentWindow.docShell.browsingContext.id;
 | 
						|
 | 
						|
    // This is the MessageSender property passed to extension.
 | 
						|
    let sender = { id: extension.id };
 | 
						|
    if (viewType == "tab") {
 | 
						|
      sender.frameId = WebNavigationFrames.getFrameId(contentWindow);
 | 
						|
      sender.tabId = tabId;
 | 
						|
      Object.defineProperty(this, "tabId", {
 | 
						|
        value: tabId,
 | 
						|
        enumerable: true,
 | 
						|
        configurable: true,
 | 
						|
      });
 | 
						|
    }
 | 
						|
    if (uri) {
 | 
						|
      sender.url = uri.spec;
 | 
						|
    }
 | 
						|
    this.sender = sender;
 | 
						|
 | 
						|
    Schemas.exportLazyGetter(contentWindow, "browser", () => {
 | 
						|
      let browserObj = Cu.createObjectIn(contentWindow);
 | 
						|
      this.childManager.inject(browserObj);
 | 
						|
      return browserObj;
 | 
						|
    });
 | 
						|
 | 
						|
    Schemas.exportLazyGetter(contentWindow, "chrome", () => {
 | 
						|
      let chromeApiWrapper = Object.create(this.childManager);
 | 
						|
      chromeApiWrapper.isChromeCompat = true;
 | 
						|
 | 
						|
      let chromeObj = Cu.createObjectIn(contentWindow);
 | 
						|
      chromeApiWrapper.inject(chromeObj);
 | 
						|
      return chromeObj;
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  logActivity(type, name, data) {
 | 
						|
    ExtensionActivityLogChild.log(this, type, name, data);
 | 
						|
  }
 | 
						|
 | 
						|
  get cloneScope() {
 | 
						|
    return this.contentWindow;
 | 
						|
  }
 | 
						|
 | 
						|
  get principal() {
 | 
						|
    return this.contentWindow.document.nodePrincipal;
 | 
						|
  }
 | 
						|
 | 
						|
  get windowId() {
 | 
						|
    if (["tab", "popup", "sidebar"].includes(this.viewType)) {
 | 
						|
      let frameData = getFrameData(this.messageManager);
 | 
						|
      return frameData ? frameData.windowId : -1;
 | 
						|
    }
 | 
						|
    return -1;
 | 
						|
  }
 | 
						|
 | 
						|
  get tabId() {
 | 
						|
    // Will be overwritten in the constructor if necessary.
 | 
						|
    return -1;
 | 
						|
  }
 | 
						|
 | 
						|
  // Called when the extension shuts down.
 | 
						|
  shutdown() {
 | 
						|
    if (this.contentWindow) {
 | 
						|
      this.contentWindow.close();
 | 
						|
    }
 | 
						|
 | 
						|
    this.unload();
 | 
						|
  }
 | 
						|
 | 
						|
  // This method is called when an extension page navigates away or
 | 
						|
  // its tab is closed.
 | 
						|
  unload() {
 | 
						|
    // Note that without this guard, we end up running unload code
 | 
						|
    // multiple times for tab pages closed by the "page-unload" handlers
 | 
						|
    // triggered below.
 | 
						|
    if (this.unloaded) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    super.unload();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
defineLazyGetter(ExtensionBaseContextChild.prototype, "messenger", function() {
 | 
						|
  return new Messenger(this, this.sender);
 | 
						|
});
 | 
						|
 | 
						|
class ExtensionPageContextChild extends ExtensionBaseContextChild {
 | 
						|
  /**
 | 
						|
   * This ExtensionPageContextChild represents a privileged addon
 | 
						|
   * execution environment that has full access to the WebExtensions
 | 
						|
   * APIs (provided that the correct permissions have been requested).
 | 
						|
   *
 | 
						|
   * This is the child side of the ExtensionPageContextParent class
 | 
						|
   * defined in ExtensionParent.jsm.
 | 
						|
   *
 | 
						|
   * @param {BrowserExtensionContent} extension This context's owner.
 | 
						|
   * @param {object} params
 | 
						|
   * @param {nsIDOMWindow} params.contentWindow The window where the addon runs.
 | 
						|
   * @param {string} params.viewType One of "background", "popup", "sidebar" or "tab".
 | 
						|
   *     "background", "sidebar" and "tab" are used by `browser.extension.getViews`.
 | 
						|
   *     "popup" is only used internally to identify page action and browser
 | 
						|
   *     action popups and options_ui pages.
 | 
						|
   * @param {number} [params.tabId] This tab's ID, used if viewType is "tab".
 | 
						|
   */
 | 
						|
  constructor(extension, params) {
 | 
						|
    super(extension, Object.assign(params, { envType: "addon_child" }));
 | 
						|
 | 
						|
    if (this.viewType == "background") {
 | 
						|
      initializeBackgroundPage(this);
 | 
						|
    }
 | 
						|
 | 
						|
    this.extension.views.add(this);
 | 
						|
  }
 | 
						|
 | 
						|
  unload() {
 | 
						|
    super.unload();
 | 
						|
    this.extension.views.delete(this);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
defineLazyGetter(
 | 
						|
  ExtensionPageContextChild.prototype,
 | 
						|
  "childManager",
 | 
						|
  function() {
 | 
						|
    this.extension.apiManager.lazyInit();
 | 
						|
 | 
						|
    let localApis = {};
 | 
						|
    let can = new CanOfAPIs(this, this.extension.apiManager, localApis);
 | 
						|
 | 
						|
    let childManager = new ChildAPIManager(this, this.messageManager, can, {
 | 
						|
      envType: "addon_parent",
 | 
						|
      viewType: this.viewType,
 | 
						|
      url: this.uri.spec,
 | 
						|
      incognito: this.incognito,
 | 
						|
    });
 | 
						|
 | 
						|
    this.callOnClose(childManager);
 | 
						|
 | 
						|
    return childManager;
 | 
						|
  }
 | 
						|
);
 | 
						|
 | 
						|
class DevToolsContextChild extends ExtensionBaseContextChild {
 | 
						|
  /**
 | 
						|
   * This DevToolsContextChild represents a devtools-related addon execution
 | 
						|
   * environment that has access to the devtools API namespace and to the same subset
 | 
						|
   * of APIs available in a content script execution environment.
 | 
						|
   *
 | 
						|
   * @param {BrowserExtensionContent} extension This context's owner.
 | 
						|
   * @param {object} params
 | 
						|
   * @param {nsIDOMWindow} params.contentWindow The window where the addon runs.
 | 
						|
   * @param {string} params.viewType One of "devtools_page" or "devtools_panel".
 | 
						|
   * @param {object} [params.devtoolsToolboxInfo] This devtools toolbox's information,
 | 
						|
   *   used if viewType is "devtools_page" or "devtools_panel".
 | 
						|
   */
 | 
						|
  constructor(extension, params) {
 | 
						|
    super(extension, Object.assign(params, { envType: "devtools_child" }));
 | 
						|
 | 
						|
    this.devtoolsToolboxInfo = params.devtoolsToolboxInfo;
 | 
						|
    ExtensionChildDevToolsUtils.initThemeChangeObserver(
 | 
						|
      params.devtoolsToolboxInfo.themeName,
 | 
						|
      this
 | 
						|
    );
 | 
						|
 | 
						|
    this.extension.devtoolsViews.add(this);
 | 
						|
  }
 | 
						|
 | 
						|
  unload() {
 | 
						|
    super.unload();
 | 
						|
    this.extension.devtoolsViews.delete(this);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
defineLazyGetter(DevToolsContextChild.prototype, "childManager", function() {
 | 
						|
  devtoolsAPIManager.lazyInit();
 | 
						|
 | 
						|
  let localApis = {};
 | 
						|
  let can = new CanOfAPIs(this, devtoolsAPIManager, localApis);
 | 
						|
 | 
						|
  let childManager = new ChildAPIManager(this, this.messageManager, can, {
 | 
						|
    envType: "devtools_parent",
 | 
						|
    viewType: this.viewType,
 | 
						|
    url: this.uri.spec,
 | 
						|
    incognito: this.incognito,
 | 
						|
  });
 | 
						|
 | 
						|
  this.callOnClose(childManager);
 | 
						|
 | 
						|
  return childManager;
 | 
						|
});
 | 
						|
 | 
						|
ExtensionPageChild = {
 | 
						|
  // Map<innerWindowId, ExtensionPageContextChild>
 | 
						|
  extensionContexts: new Map(),
 | 
						|
 | 
						|
  initialized: false,
 | 
						|
 | 
						|
  apiManager,
 | 
						|
 | 
						|
  _init() {
 | 
						|
    if (this.initialized) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    this.initialized = true;
 | 
						|
 | 
						|
    Services.obs.addObserver(this, "inner-window-destroyed"); // eslint-ignore-line mozilla/balanced-listeners
 | 
						|
  },
 | 
						|
 | 
						|
  observe(subject, topic, data) {
 | 
						|
    if (topic === "inner-window-destroyed") {
 | 
						|
      let windowId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
 | 
						|
 | 
						|
      this.destroyExtensionContext(windowId);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  expectViewLoad(global, viewType) {
 | 
						|
    if (["popup", "sidebar"].includes(viewType)) {
 | 
						|
      global.docShell.isAppTab = true;
 | 
						|
    }
 | 
						|
 | 
						|
    promiseEvent(
 | 
						|
      global,
 | 
						|
      "DOMContentLoaded",
 | 
						|
      true,
 | 
						|
      event => event.target.location != "about:blank"
 | 
						|
    ).then(() => {
 | 
						|
      let windowId = getInnerWindowID(global.content);
 | 
						|
      let context = this.extensionContexts.get(windowId);
 | 
						|
 | 
						|
      global.sendAsyncMessage("Extension:ExtensionViewLoaded", {
 | 
						|
        childId: context && context.childManager.id,
 | 
						|
      });
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Create a privileged context at initial-document-element-inserted.
 | 
						|
   *
 | 
						|
   * @param {BrowserExtensionContent} extension
 | 
						|
   *     The extension for which the context should be created.
 | 
						|
   * @param {nsIDOMWindow} contentWindow The global of the page.
 | 
						|
   */
 | 
						|
  initExtensionContext(extension, contentWindow) {
 | 
						|
    this._init();
 | 
						|
 | 
						|
    if (!WebExtensionPolicy.isExtensionProcess) {
 | 
						|
      throw new Error(
 | 
						|
        "Cannot create an extension page context in current process"
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    let windowId = getInnerWindowID(contentWindow);
 | 
						|
    let context = this.extensionContexts.get(windowId);
 | 
						|
    if (context) {
 | 
						|
      if (context.extension !== extension) {
 | 
						|
        throw new Error(
 | 
						|
          "A different extension context already exists for this frame"
 | 
						|
        );
 | 
						|
      }
 | 
						|
      throw new Error(
 | 
						|
        "An extension context was already initialized for this frame"
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    let mm = contentWindow.docShell.messageManager;
 | 
						|
 | 
						|
    let { viewType, tabId, devtoolsToolboxInfo } = getFrameData(mm) || {};
 | 
						|
 | 
						|
    let uri = contentWindow.document.documentURIObject;
 | 
						|
 | 
						|
    if (devtoolsToolboxInfo) {
 | 
						|
      context = new DevToolsContextChild(extension, {
 | 
						|
        viewType,
 | 
						|
        contentWindow,
 | 
						|
        uri,
 | 
						|
        tabId,
 | 
						|
        devtoolsToolboxInfo,
 | 
						|
      });
 | 
						|
    } else {
 | 
						|
      context = new ExtensionPageContextChild(extension, {
 | 
						|
        viewType,
 | 
						|
        contentWindow,
 | 
						|
        uri,
 | 
						|
        tabId,
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    this.extensionContexts.set(windowId, context);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Close the ExtensionPageContextChild belonging to the given window, if any.
 | 
						|
   *
 | 
						|
   * @param {number} windowId The inner window ID of the destroyed context.
 | 
						|
   */
 | 
						|
  destroyExtensionContext(windowId) {
 | 
						|
    let context = this.extensionContexts.get(windowId);
 | 
						|
    if (context) {
 | 
						|
      context.unload();
 | 
						|
      this.extensionContexts.delete(windowId);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  shutdownExtension(extensionId) {
 | 
						|
    for (let [windowId, context] of this.extensionContexts) {
 | 
						|
      if (context.extension.id == extensionId) {
 | 
						|
        context.shutdown();
 | 
						|
        this.extensionContexts.delete(windowId);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
};
 |