Bug 1344616: Support running content script tests in xpcshell. r?mixedpuppy
MozReview-Commit-ID: 57yhjZxVl90
--- a/testing/mochitest/BrowserTestUtils/ContentTask.jsm
+++ b/testing/mochitest/BrowserTestUtils/ContentTask.jsm
@@ -9,17 +9,17 @@
this.EXPORTED_SYMBOLS = [
"ContentTask"
];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Services.jsm");
-const FRAME_SCRIPT = "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js";
+const FRAME_SCRIPT = "resource://testing-common/content-task.js";
/**
* Keeps track of whether the frame script was already loaded.
*/
var gFrameScriptLoaded = false;
/**
* Mapping from message id to associated promise.
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -44,17 +44,17 @@ function getConsole() {
maxLogLevelPref: "extensions.webextensions.log.level",
prefix: "WebExtensions",
});
}
XPCOMUtils.defineLazyGetter(this, "console", getConsole);
let nextId = 0;
-const {uniqueProcessID} = Services.appinfo;
+XPCOMUtils.defineLazyGetter(this, "uniqueProcessID", () => Services.appinfo.uniqueProcessID);
function getUniqueId() {
return `${nextId++}-${uniqueProcessID}`;
}
/**
* An Error subclass for which complete error messages are always passed
* to extensions, rather than being interpreted as an unknown error.
@@ -864,35 +864,37 @@ function getMessageManager(target) {
}
return target.QueryInterface(Ci.nsIMessageSender);
}
function flushJarCache(jarPath) {
Services.obs.notifyObservers(null, "flush-cache-entry", jarPath);
}
-const PlatformInfo = Object.freeze({
- os: (function() {
- let os = AppConstants.platform;
- if (os == "macosx") {
- os = "mac";
- }
- return os;
- })(),
- arch: (function() {
- let abi = Services.appinfo.XPCOMABI;
- let [arch] = abi.split("-");
- if (arch == "x86") {
- arch = "x86-32";
- } else if (arch == "x86_64") {
- arch = "x86-64";
- }
- return arch;
- })(),
-});
+function PlatformInfo() {
+ return Object.freeze({
+ os: (function() {
+ let os = AppConstants.platform;
+ if (os == "macosx") {
+ os = "mac";
+ }
+ return os;
+ })(),
+ arch: (function() {
+ let abi = Services.appinfo.XPCOMABI;
+ let [arch] = abi.split("-");
+ if (arch == "x86") {
+ arch = "x86-32";
+ } else if (arch == "x86_64") {
+ arch = "x86-64";
+ }
+ return arch;
+ })(),
+ });
+}
function detectLanguage(text) {
return LanguageDetector.detectLanguage(text).then(result => ({
isReliable: result.confident,
languages: result.languages.map(lang => {
return {
language: lang.languageCode,
percentage: lang.percent,
@@ -1188,12 +1190,13 @@ this.ExtensionUtils = {
DefaultMap,
DefaultWeakMap,
EventEmitter,
ExtensionError,
IconDetails,
LimitedSet,
LocaleData,
MessageManagerProxy,
- PlatformInfo,
SingletonEventManager,
SpreadArgs,
};
+
+XPCOMUtils.defineLazyGetter(this.ExtensionUtils, "PlatformInfo", PlatformInfo);
--- a/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
+++ b/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
@@ -3,16 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["ExtensionTestUtils"];
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+Components.utils.import("resource://gre/modules/ExtensionUtils.jsm");
Components.utils.import("resource://gre/modules/Task.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Extension",
"resource://gre/modules/Extension.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
@@ -22,29 +23,139 @@ XPCOMUtils.defineLazyModuleGetter(this,
XPCOMUtils.defineLazyGetter(this, "Management", () => {
const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
return Management;
});
/* exported ExtensionTestUtils */
+const {
+ promiseDocumentLoaded,
+ promiseEvent,
+ promiseObserved,
+} = ExtensionUtils;
+
+var REMOTE_CONTENT_SCRIPTS = false;
+
let BASE_MANIFEST = Object.freeze({
"applications": Object.freeze({
"gecko": Object.freeze({
"id": "test@web.ext",
}),
}),
"manifest_version": 2,
"name": "name",
"version": "0",
});
+
+function frameScript() {
+ Components.utils.import("resource://gre/modules/ExtensionContent.jsm");
+
+ ExtensionContent.init(this);
+ addEventListener("unload", () => {
+ ExtensionContent.uninit(this);
+ });
+}
+
+const FRAME_SCRIPT = `data:text/javascript,(${frameScript}).call(this)`;
+
+
+const XUL_URL = "data:application/vnd.mozilla.xul+xml;charset=utf-8," + encodeURI(
+ `<?xml version="1.0"?>
+ <window id="documentElement"/>`);
+
+let kungFuDeathGrip = new Set();
+function promiseBrowserLoaded(browser, url) {
+ return new Promise(resolve => {
+ const listener = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIWebProgressListener]),
+
+ onStateChange(webProgress, request, stateFlags, statusCode) {
+ let requestUrl = request.URI ? request.URI.spec : webProgress.DOMWindow.location.href;
+
+ if (webProgress.isTopLevel && requestUrl === url &&
+ (stateFlags & Ci.nsIWebProgressListener.STATE_STOP)) {
+ resolve();
+ kungFuDeathGrip.delete(listener);
+ browser.removeProgressListener(listener);
+ }
+ },
+ };
+
+ // addProgressListener only supports weak references, so we need to
+ // use one. But we also need to make sure it stays alive until we're
+ // done with it, so thunk away a strong reference to keep it alive.
+ kungFuDeathGrip.add(listener);
+ browser.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
+ });
+}
+
+class ContentPage {
+ constructor(remote = REMOTE_CONTENT_SCRIPTS) {
+ this.remote = remote;
+
+ this.browserReady = this._initBrowser();
+ }
+
+ async _initBrowser() {
+ this.windowlessBrowser = Services.appShell.createWindowlessBrowser(true);
+
+ let system = Services.scriptSecurityManager.getSystemPrincipal();
+
+ let chromeShell = this.windowlessBrowser.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIWebNavigation);
+
+ chromeShell.createAboutBlankContentViewer(system);
+ chromeShell.loadURI(XUL_URL, 0, null, null, null);
+
+ await promiseObserved("chrome-document-global-created",
+ win => win.document == chromeShell.document);
+
+ let chromeDoc = await promiseDocumentLoaded(chromeShell.document);
+
+ let browser = chromeDoc.createElement("browser");
+ browser.setAttribute("type", "content");
+
+ let awaitFrameLoader = Promise.resolve();
+ if (this.remote) {
+ awaitFrameLoader = promiseEvent(browser, "XULFrameLoaderCreated");
+ browser.setAttribute("remote", "true");
+ }
+
+ chromeDoc.documentElement.appendChild(browser);
+
+ await awaitFrameLoader;
+ browser.messageManager.loadFrameScript(FRAME_SCRIPT, true);
+
+ this.browser = browser;
+ return browser;
+ }
+
+ async loadURL(url) {
+ await this.browserReady;
+
+ this.browser.loadURI(url);
+ return promiseBrowserLoaded(this.browser, url);
+ }
+
+ async close() {
+ await this.browserReady;
+
+ this.browser = null;
+
+ this.windowlessBrowser.close();
+ this.windowlessBrowser = null;
+ }
+}
+
class ExtensionWrapper {
constructor(extension, testScope) {
this.extension = extension;
this.testScope = testScope;
this.state = "uninitialized";
this.testResolve = null;
@@ -298,9 +409,25 @@ var ExtensionTestUtils = {
manager.observe(null, "addons-startup", null);
},
loadExtension(data) {
let extension = Extension.generate(data);
return new ExtensionWrapper(extension, this.currentScope);
},
+
+ get remoteContentScripts() {
+ return REMOTE_CONTENT_SCRIPTS;
+ },
+
+ set remoteContentScripts(val) {
+ REMOTE_CONTENT_SCRIPTS = !!val;
+ },
+
+ loadContentPage(url, remote = undefined) {
+ let contentPage = new ContentPage(remote);
+
+ return contentPage.loadURL(url).then(() => {
+ return contentPage;
+ });
+ },
};