mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 15:23:51 +00:00
Bug 1288885: Support testing WebExtensions from xpcshell tests. r=aswan
Most of the test helper code is derived from the SpecialPowers/ExtensionTestUtils code that does the same. Eventually, the two implementations should probably be unified, but I don't think it's worth the trouble for now. MozReview-Commit-ID: 7Yy9jWkGsMM --HG-- extra : rebase_source : 92cc01e1cde97fa79250d51bad5a56181fbe715c
This commit is contained in:
parent
4b714bba8f
commit
ff96d80145
@ -1,3 +1,7 @@
|
||||
{
|
||||
"extends": "../../../../../testing/xpcshell/xpcshell.eslintrc",
|
||||
|
||||
"globals": {
|
||||
"browser": false,
|
||||
},
|
||||
}
|
||||
|
@ -2,12 +2,24 @@
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
/* exported createHttpServer */
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
|
||||
"resource://gre/modules/AppConstants.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Extension",
|
||||
"resource://gre/modules/Extension.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionData",
|
||||
"resource://gre/modules/Extension.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
|
||||
"resource://gre/modules/ExtensionManagement.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionTestUtils",
|
||||
"resource://testing-common/ExtensionXPCShellUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
|
||||
"resource://testing-common/httpd.js");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
|
||||
@ -15,36 +27,29 @@ XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
|
||||
/* exported normalizeManifest */
|
||||
ExtensionTestUtils.init(this);
|
||||
|
||||
let BASE_MANIFEST = {
|
||||
"applications": {"gecko": {"id": "test@web.ext"}},
|
||||
|
||||
"manifest_version": 2,
|
||||
/**
|
||||
* Creates a new HttpServer for testing, and begins listening on the
|
||||
* specified port. Automatically shuts down the server when the test
|
||||
* unit ends.
|
||||
*
|
||||
* @param {integer} [port]
|
||||
* The port to listen on. If omitted, listen on a random
|
||||
* port. The latter is the preferred behavior.
|
||||
*
|
||||
* @returns {HttpServer}
|
||||
*/
|
||||
function createHttpServer(port = -1) {
|
||||
let server = new HttpServer();
|
||||
server.start(port);
|
||||
|
||||
"name": "name",
|
||||
"version": "0",
|
||||
};
|
||||
do_register_cleanup(() => {
|
||||
return new Promise(resolve => {
|
||||
server.stop(resolve);
|
||||
});
|
||||
});
|
||||
|
||||
function* normalizeManifest(manifest, baseManifest = BASE_MANIFEST) {
|
||||
const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
|
||||
yield Management.lazyInit();
|
||||
|
||||
let errors = [];
|
||||
let context = {
|
||||
url: null,
|
||||
|
||||
logError: error => {
|
||||
errors.push(error);
|
||||
},
|
||||
|
||||
preprocessors: {},
|
||||
};
|
||||
|
||||
manifest = Object.assign({}, baseManifest, manifest);
|
||||
|
||||
let normalized = Schemas.normalize(manifest, "manifest.WebExtensionManifest", context);
|
||||
normalized.errors = errors;
|
||||
|
||||
return normalized;
|
||||
return server;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
|
||||
add_task(function* test_manifest_commands() {
|
||||
let normalized = yield normalizeManifest({
|
||||
let normalized = yield ExtensionTestUtils.normalizeManifest({
|
||||
"commands": {
|
||||
"toggle-feature": {
|
||||
"suggested_key": {"default": "Shifty+Y"},
|
||||
|
@ -56,6 +56,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
|
||||
"resource://gre/modules/AddonManager.jsm");
|
||||
|
||||
Cu.import("resource://gre/modules/ExtensionContent.jsm");
|
||||
Cu.import("resource://gre/modules/ExtensionManagement.jsm");
|
||||
|
||||
const BASE_SCHEMA = "chrome://extensions/content/schemas/manifest.json";
|
||||
@ -326,7 +327,7 @@ class ProxyContext extends ExtensionContext {
|
||||
|
||||
function findPathInObject(obj, path) {
|
||||
for (let elt of path) {
|
||||
obj = obj[elt];
|
||||
obj = obj[elt] || undefined;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
285
toolkit/components/extensions/ExtensionXPCShellUtils.jsm
Normal file
285
toolkit/components/extensions/ExtensionXPCShellUtils.jsm
Normal file
@ -0,0 +1,285 @@
|
||||
/* 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.EXPORTED_SYMBOLS = ["ExtensionTestUtils"];
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
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",
|
||||
"resource://gre/modules/Schemas.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "uuidGenerator",
|
||||
"@mozilla.org/uuid-generator;1", "nsIUUIDGenerator");
|
||||
|
||||
/* exported ExtensionTestUtils */
|
||||
|
||||
let BASE_MANIFEST = Object.freeze({
|
||||
"applications": Object.freeze({
|
||||
"gecko": Object.freeze({
|
||||
"id": "test@web.ext",
|
||||
}),
|
||||
}),
|
||||
|
||||
"manifest_version": 2,
|
||||
|
||||
"name": "name",
|
||||
"version": "0",
|
||||
});
|
||||
|
||||
class ExtensionWrapper {
|
||||
constructor(extension, testScope) {
|
||||
this.extension = extension;
|
||||
this.testScope = testScope;
|
||||
|
||||
this.state = "uninitialized";
|
||||
|
||||
this.testResolve = null;
|
||||
this.testDone = new Promise(resolve => { this.testResolve = resolve; });
|
||||
|
||||
this.messageHandler = new Map();
|
||||
this.messageAwaiter = new Map();
|
||||
|
||||
this.messageQueue = new Set();
|
||||
|
||||
this.testScope.do_register_cleanup(() => {
|
||||
if (this.messageQueue.size) {
|
||||
let names = Array.from(this.messageQueue, ([msg]) => msg);
|
||||
this.testScope.equal(JSON.stringify(names), "[]", "message queue is empty");
|
||||
}
|
||||
if (this.messageAwaiter.size) {
|
||||
let names = Array.from(this.messageAwaiter.keys());
|
||||
this.testScope.equal(JSON.stringify(names), "[]", "no tasks awaiting on messages");
|
||||
}
|
||||
});
|
||||
|
||||
/* eslint-disable mozilla/balanced-listeners */
|
||||
extension.on("test-eq", (kind, pass, msg, expected, actual) => {
|
||||
this.testScope.ok(pass, `${msg} - Expected: ${expected}, Actual: ${actual}`);
|
||||
});
|
||||
extension.on("test-log", (kind, pass, msg) => {
|
||||
this.testScope.do_print(msg);
|
||||
});
|
||||
extension.on("test-result", (kind, pass, msg) => {
|
||||
this.testScope.ok(pass, msg);
|
||||
});
|
||||
extension.on("test-done", (kind, pass, msg, expected, actual) => {
|
||||
this.testScope.ok(pass, msg);
|
||||
this.testResolve(msg);
|
||||
});
|
||||
|
||||
extension.on("test-message", (kind, msg, ...args) => {
|
||||
let handler = this.messageHandler.get(msg);
|
||||
if (handler) {
|
||||
handler(...args);
|
||||
} else {
|
||||
this.messageQueue.add([msg, ...args]);
|
||||
this.checkMessages();
|
||||
}
|
||||
});
|
||||
/* eslint-enable mozilla/balanced-listeners */
|
||||
|
||||
this.testScope.do_register_cleanup(() => {
|
||||
if (this.state == "pending" || this.state == "running") {
|
||||
this.testScope.equal(this.state, "unloaded", "Extension left running at test shutdown");
|
||||
return this.unload();
|
||||
} else if (extension.state == "unloading") {
|
||||
this.testScope.equal(this.state, "unloaded", "Extension not fully unloaded at test shutdown");
|
||||
}
|
||||
});
|
||||
|
||||
this.testScope.do_print(`Extension loaded`);
|
||||
}
|
||||
|
||||
startup() {
|
||||
if (this.state != "uninitialized") {
|
||||
throw new Error("Extension already started");
|
||||
}
|
||||
this.state = "pending";
|
||||
|
||||
return this.extension.startup().then(
|
||||
result => {
|
||||
this.state = "running";
|
||||
|
||||
return result;
|
||||
},
|
||||
error => {
|
||||
this.state = "failed";
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
unload() {
|
||||
if (this.state != "running") {
|
||||
throw new Error("Extension not running");
|
||||
}
|
||||
this.state = "unloading";
|
||||
|
||||
this.extension.shutdown();
|
||||
this.state = "unloaded";
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
sendMessage(...args) {
|
||||
this.extension.testMessage(...args);
|
||||
}
|
||||
|
||||
awaitFinish(msg) {
|
||||
return this.testDone.then(actual => {
|
||||
if (msg) {
|
||||
this.testScope.equal(actual, msg, "test result correct");
|
||||
}
|
||||
return actual;
|
||||
});
|
||||
}
|
||||
|
||||
checkMessages() {
|
||||
for (let message of this.messageQueue) {
|
||||
let [msg, ...args] = message;
|
||||
|
||||
let listener = this.messageAwaiter.get(msg);
|
||||
if (listener) {
|
||||
this.messageQueue.delete(message);
|
||||
this.messageAwaiter.delete(msg);
|
||||
|
||||
listener.resolve(...args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkDuplicateListeners(msg) {
|
||||
if (this.messageHandler.has(msg) || this.messageAwaiter.has(msg)) {
|
||||
throw new Error("only one message handler allowed");
|
||||
}
|
||||
}
|
||||
|
||||
awaitMessage(msg) {
|
||||
return new Promise(resolve => {
|
||||
this.checkDuplicateListeners(msg);
|
||||
|
||||
this.messageAwaiter.set(msg, {resolve});
|
||||
this.checkMessages();
|
||||
});
|
||||
}
|
||||
|
||||
onMessage(msg, callback) {
|
||||
this.checkDuplicateListeners(msg);
|
||||
this.messageHandler.set(msg, callback);
|
||||
}
|
||||
}
|
||||
|
||||
var ExtensionTestUtils = {
|
||||
BASE_MANIFEST,
|
||||
|
||||
normalizeManifest: Task.async(function* (manifest, baseManifest = BASE_MANIFEST) {
|
||||
const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
|
||||
|
||||
yield Management.lazyInit();
|
||||
|
||||
let errors = [];
|
||||
let context = {
|
||||
url: null,
|
||||
|
||||
logError: error => {
|
||||
errors.push(error);
|
||||
},
|
||||
|
||||
preprocessors: {},
|
||||
};
|
||||
|
||||
manifest = Object.assign({}, baseManifest, manifest);
|
||||
|
||||
let normalized = Schemas.normalize(manifest, "manifest.WebExtensionManifest", context);
|
||||
normalized.errors = errors;
|
||||
|
||||
return normalized;
|
||||
}),
|
||||
|
||||
currentScope: null,
|
||||
|
||||
profileDir: null,
|
||||
|
||||
init(scope) {
|
||||
this.currentScope = scope;
|
||||
|
||||
this.profileDir = scope.do_get_profile();
|
||||
|
||||
// We need to load at least one frame script into every message
|
||||
// manager to ensure that the scriptable wrapper for its global gets
|
||||
// created before we try to access it externally. If we don't, we
|
||||
// fail sanity checks on debug builds the first time we try to
|
||||
// create a wrapper, because we should never have a global without a
|
||||
// cached wrapper.
|
||||
Services.mm.loadFrameScript("data:text/javascript,//", true);
|
||||
|
||||
|
||||
let tmpD = this.profileDir.clone();
|
||||
tmpD.append("tmp");
|
||||
tmpD.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
|
||||
|
||||
let dirProvider = {
|
||||
getFile(prop, persistent) {
|
||||
persistent.value = false;
|
||||
if (prop == "TmpD") {
|
||||
return tmpD.clone();
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider]),
|
||||
};
|
||||
Services.dirsvc.registerProvider(dirProvider);
|
||||
|
||||
|
||||
scope.do_register_cleanup(() => {
|
||||
tmpD.remove(true);
|
||||
Services.dirsvc.unregisterProvider(dirProvider);
|
||||
|
||||
this.currentScope = null;
|
||||
});
|
||||
},
|
||||
|
||||
addonManagerStarted: false,
|
||||
|
||||
startAddonManager() {
|
||||
if (this.addonManagerStarted) {
|
||||
return;
|
||||
}
|
||||
this.addonManagerStarted = true;
|
||||
|
||||
let appInfo = {};
|
||||
Cu.import("resource://testing-common/AppInfo.jsm", appInfo);
|
||||
|
||||
appInfo.updateAppInfo({
|
||||
ID: "xpcshell@tests.mozilla.org",
|
||||
name: "XPCShell",
|
||||
version: "48",
|
||||
platformVersion: "48",
|
||||
});
|
||||
|
||||
|
||||
let manager = Cc["@mozilla.org/addons/integration;1"].getService(Ci.nsIObserver)
|
||||
.QueryInterface(Ci.nsITimerCallback);
|
||||
manager.observe(null, "addons-startup", null);
|
||||
},
|
||||
|
||||
loadExtension(data, id = uuidGenerator.generateUUID().number) {
|
||||
let extension = Extension.generate(id, data);
|
||||
|
||||
return new ExtensionWrapper(extension, this.currentScope);
|
||||
},
|
||||
};
|
@ -19,6 +19,10 @@ EXTRA_COMPONENTS += [
|
||||
'extensions-toolkit.manifest',
|
||||
]
|
||||
|
||||
TESTING_JS_MODULES += [
|
||||
'ExtensionXPCShellUtils.jsm',
|
||||
]
|
||||
|
||||
DIRS += ['schemas']
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
@ -1,3 +1,7 @@
|
||||
{
|
||||
"extends": "../../../../../testing/xpcshell/xpcshell.eslintrc",
|
||||
|
||||
"globals": {
|
||||
"browser": false,
|
||||
},
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Extension",
|
||||
"resource://gre/modules/Extension.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionData",
|
||||
"resource://gre/modules/Extension.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionTestUtils",
|
||||
"resource://testing-common/ExtensionXPCShellUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
|
||||
@ -15,37 +17,4 @@ XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
|
||||
/* exported normalizeManifest */
|
||||
|
||||
let BASE_MANIFEST = {
|
||||
"applications": {"gecko": {"id": "test@web.ext"}},
|
||||
|
||||
"manifest_version": 2,
|
||||
|
||||
"name": "name",
|
||||
"version": "0",
|
||||
};
|
||||
|
||||
function* normalizeManifest(manifest, baseManifest = BASE_MANIFEST) {
|
||||
const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
|
||||
|
||||
yield Management.lazyInit();
|
||||
|
||||
let errors = [];
|
||||
let context = {
|
||||
url: null,
|
||||
|
||||
logError: error => {
|
||||
errors.push(error);
|
||||
},
|
||||
|
||||
preprocessors: {},
|
||||
};
|
||||
|
||||
manifest = Object.assign({}, baseManifest, manifest);
|
||||
|
||||
let normalized = Schemas.normalize(manifest, "manifest.WebExtensionManifest", context);
|
||||
normalized.errors = errors;
|
||||
|
||||
return normalized;
|
||||
}
|
||||
ExtensionTestUtils.init(this);
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
|
||||
add_task(function* test_manifest_csp() {
|
||||
let normalized = yield normalizeManifest({
|
||||
let normalized = yield ExtensionTestUtils.normalizeManifest({
|
||||
"content_security_policy": "script-src 'self'; object-src 'none'",
|
||||
});
|
||||
|
||||
@ -15,7 +15,7 @@ add_task(function* test_manifest_csp() {
|
||||
"Should have the expected poilcy string");
|
||||
|
||||
|
||||
normalized = yield normalizeManifest({
|
||||
normalized = yield ExtensionTestUtils.normalizeManifest({
|
||||
"content_security_policy": "object-src 'none'",
|
||||
});
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
|
||||
add_task(function* test_manifest_incognito() {
|
||||
let normalized = yield normalizeManifest({
|
||||
let normalized = yield ExtensionTestUtils.normalizeManifest({
|
||||
"incognito": "spanning",
|
||||
});
|
||||
|
||||
@ -14,7 +14,7 @@ add_task(function* test_manifest_incognito() {
|
||||
"spanning",
|
||||
"Should have the expected incognito string");
|
||||
|
||||
normalized = yield normalizeManifest({
|
||||
normalized = yield ExtensionTestUtils.normalizeManifest({
|
||||
"incognito": "split",
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user