Bug 1037374 - Uplift Add-on SDK to Firefox r=me

This commit is contained in:
Erik Vold 2014-07-15 06:19:40 -07:00
parent 90931c71a2
commit e31408ed12
19 changed files with 298 additions and 53 deletions

View File

@ -451,7 +451,7 @@ TestRunner.prototype = {
function tiredOfWaiting() {
self._logTestFailed("timed out");
if ("testMessage" in self.console) {
self.console.testMessage(false, false, self.test.name, "Timed out");
self.console.testMessage(false, false, self.test.name, "Test timed out");
}
else {
self.console.error("fail:", "Timed out")

View File

@ -73,7 +73,8 @@ let panelContract = contract(merge({
}),
contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), {
msg: 'The `contentStyleFile` option must be a local URL or an array of URLs'
})
}),
contextMenu: boolean
}, displayContract.rules, loaderContract.rules));
@ -134,6 +135,7 @@ const Panel = Class({
defaultHeight: 240,
focus: true,
position: Object.freeze({}),
contextMenu: false
}, panelContract(options));
models.set(this, model);
@ -151,6 +153,9 @@ const Panel = Class({
// Load panel content.
domPanel.setURL(view, model.contentURL);
// Allow context menu
domPanel.allowContextMenu(view, model.contextMenu);
setupAutoHide(this);
@ -188,7 +193,15 @@ const Panel = Class({
/* Public API: Panel.position */
get position() modelFor(this).position,
/* Public API: Panel.contextMenu */
get contextMenu() modelFor(this).contextMenu,
set contextMenu(allow) {
let model = modelFor(this);
model.contextMenu = panelContract({ contextMenu: allow }).contextMenu;
domPanel.allowContextMenu(viewFor(this), model.contextMenu);
},
get contentURL() modelFor(this).contentURL,
set contentURL(value) {
let model = modelFor(this);
@ -226,7 +239,8 @@ const Panel = Class({
height: model.height,
defaultWidth: model.defaultWidth,
defaultHeight: model.defaultHeight,
focus: model.focus
focus: model.focus,
contextMenu: model.contextMenu
}, displayContract(options));
if (!isDisposed(this))

View File

@ -211,7 +211,7 @@ function show(panel, options, anchor) {
// Prevent the panel from getting focus when showing up
// if focus is set to false
panel.setAttribute("noautofocus", !options.focus);
let window = anchor && getOwnerBrowserWindow(anchor);
let { document } = window ? window : getMostRecentBrowserWindow();
attach(panel, document);
@ -404,3 +404,13 @@ exports.getContentDocument = getContentDocument;
function setURL(panel, url) getContentFrame(panel).setAttribute("src", url)
exports.setURL = setURL;
function allowContextMenu(panel, allow) {
if(allow) {
panel.setAttribute("context", "contentAreaContextMenu");
}
else {
panel.removeAttribute("context");
}
}
exports.allowContextMenu = allowContextMenu;

View File

@ -18,6 +18,7 @@ const system = require("../system");
const memory = require('../deprecated/memory');
const { gc: gcPromise } = require('./memory');
const { defer } = require('../core/promise');
const { extend } = require('../core/heritage');
// Trick manifest builder to make it think we need these modules ?
const unit = require("../deprecated/unit-test");
@ -453,7 +454,7 @@ var consoleListener = {
};
function TestRunnerConsole(base, options) {
this.__proto__ = {
let proto = extend(base, {
errorsLogged: 0,
warn: function warn() {
this.errorsLogged++;
@ -470,8 +471,8 @@ function TestRunnerConsole(base, options) {
if (first == "pass:")
print(".");
},
__proto__: base
};
});
return Object.create(proto);
}
function stringify(arg) {

View File

@ -19,7 +19,7 @@ function CustomLoader(module, globals, packaging, overrides={}) {
options = override(options, {
id: overrides.id || options.id,
globals: override(defaultGlobals, globals || {}),
modules: override(options.modules || {}, {
modules: override(override(options.modules || {}, overrides.modules || {}), {
'sdk/addon/window': addonWindow
})
});
@ -31,6 +31,8 @@ function CustomLoader(module, globals, packaging, overrides={}) {
require: Require(loader, module),
sandbox: function(id) {
let requirement = loader.resolve(id, module.id);
if (!requirement)
requirement = id;
let uri = resolveURI(requirement, loader.mapping);
return loader.sandboxes[uri];
},
@ -73,13 +75,13 @@ exports.LoaderWithHookedConsole = function (module, callback) {
return {
loader: CustomLoader(module, {
console: new HookedPlainTextConsole(hook, null, null)
}, override(require("@loader/options"), {
}, null, {
modules: {
'sdk/console/plain-text': {
PlainTextConsole: HookedPlainTextConsole.bind(null, hook)
}
}
})),
}),
messages: messages
};
}
@ -112,11 +114,11 @@ exports.LoaderWithFilteredConsole = function (module, callback) {
return CustomLoader(module, {
console: new HookedPlainTextConsole(hook, null, null)
}, override(require("@loader/options"), {
}, null, {
modules: {
'sdk/console/plain-text': {
PlainTextConsole: HookedPlainTextConsole.bind(null, hook)
}
}
}));
});
}

View File

@ -714,7 +714,8 @@ const Loader = iced(function Loader(options) {
console: console
},
resolve: options.isNative ?
exports.nodeResolve :
// Make the returned resolve function have the same signature
(id, requirer) => exports.nodeResolve(id, requirer, { rootURI: rootURI }) :
exports.resolve,
sharedGlobalBlacklist: ["sdk/indexed-db"]
}, options);

View File

@ -234,6 +234,11 @@ parser_groups = (
help="JSON file to overload package.json properties",
default=None,
cmds=['xpi'])),
(("", "--abort-on-missing-module",), dict(dest="abort_on_missing",
help="Abort if required module is missing",
action="store_true",
default=False,
cmds=['test', 'run', 'xpi', 'testpkgs'])),
]
),
@ -651,7 +656,8 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None,
# a Mozilla application (which includes running tests).
use_main = False
inherited_options = ['verbose', 'enable_e10s', 'parseable', 'check_memory']
inherited_options = ['verbose', 'enable_e10s', 'parseable', 'check_memory',
'abort_on_missing']
enforce_timeouts = False
if command == "xpi":
@ -746,9 +752,9 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None,
if ":" in options.filter:
test_filter_re = options.filter.split(":")[0]
try:
manifest = build_manifest(target_cfg, pkg_cfg, deps,
scan_tests, test_filter_re,
loader_modules)
manifest = build_manifest(target_cfg, pkg_cfg, deps, scan_tests,
test_filter_re, loader_modules,
abort_on_missing=options.abort_on_missing)
except ModuleNotFoundError, e:
print str(e)
sys.exit(1)
@ -788,6 +794,10 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None,
options.force_use_bundled_sdk = False
options.overload_modules = True
if options.pkgdir == env_root:
options.bundle_sdk = True
options.overload_modules = True
extra_environment = {}
if command == "test":
# This should be contained in the test runner package.

View File

@ -181,7 +181,7 @@ class ModuleInfo:
class ManifestBuilder:
def __init__(self, target_cfg, pkg_cfg, deps, extra_modules,
stderr=sys.stderr):
stderr=sys.stderr, abort_on_missing=False):
self.manifest = {} # maps (package,section,module) to ManifestEntry
self.target_cfg = target_cfg # the entry point
self.pkg_cfg = pkg_cfg # all known packages
@ -193,6 +193,7 @@ class ManifestBuilder:
self.datamaps = {} # maps package name to DataMap instance
self.files = [] # maps manifest index to (absfn,absfn) js/docs pair
self.test_modules = [] # for runtime
self.abort_on_missing = abort_on_missing # cfx eol
def build(self, scan_tests, test_filter_re):
"""
@ -416,6 +417,12 @@ class ManifestBuilder:
# test-securable-module.js, and the modules/red.js
# that it imports, both do that intentionally
continue
if not self.abort_on_missing:
# print a warning, but tolerate missing modules
# unless cfx --abort-on-missing-module flag was set
print >>self.stderr, "Warning: missing module: %s" % reqname
me.add_requirement(reqname, reqname)
continue
lineno = locations.get(reqname) # None means define()
if lineno is None:
reqtype = "define"
@ -633,7 +640,7 @@ class ManifestBuilder:
return None
def build_manifest(target_cfg, pkg_cfg, deps, scan_tests,
test_filter_re=None, extra_modules=[]):
test_filter_re=None, extra_modules=[], abort_on_missing=False):
"""
Perform recursive dependency analysis starting from entry_point,
building up a manifest of modules that need to be included in the XPI.
@ -659,7 +666,8 @@ def build_manifest(target_cfg, pkg_cfg, deps, scan_tests,
code which does, so it knows what to copy into the XPI.
"""
mxt = ManifestBuilder(target_cfg, pkg_cfg, deps, extra_modules)
mxt = ManifestBuilder(target_cfg, pkg_cfg, deps, extra_modules,
abort_on_missing=abort_on_missing)
mxt.build(scan_tests, test_filter_re)
return mxt

View File

@ -48,6 +48,7 @@ class Basic(unittest.TestCase):
def assertReqIs(modname, reqname, path):
reqs = m["one/%s" % modname]["requirements"]
self.failUnlessEqual(reqs[reqname], path)
assertReqIs("main", "sdk/panel", "sdk/panel")
assertReqIs("main", "two.js", "one/two")
assertReqIs("main", "./two", "one/two")
@ -57,11 +58,26 @@ class Basic(unittest.TestCase):
assertReqIs("subdir/three", "../main", "one/main")
target_cfg.dependencies = []
try:
# this should now work, as we ignore missing modules by default
m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False)
m = m.get_harness_options_manifest(False)
assertReqIs("main", "sdk/panel", "sdk/panel")
# note that with "addon-sdk" dependency present,
# "sdk/tabs.js" mapped to "sdk/tabs", but without,
# we just get the default (identity) mapping
assertReqIs("main", "sdk/tabs.js", "sdk/tabs.js")
except Exception, e:
self.fail("Must not throw from build_manifest() if modules are missing")
# now, because .dependencies *is* provided, we won't search 'deps',
# so we'll get a link error
# and stop_on_missing is True, we'll get a link error
self.assertRaises(manifest.ModuleNotFoundError,
manifest.build_manifest,
target_cfg, pkg_cfg, deps, scan_tests=False)
target_cfg, pkg_cfg, deps, scan_tests=False,
abort_on_missing=True)
def test_main_in_deps(self):
target_cfg = self.get_pkg("three")

View File

@ -4,10 +4,16 @@
'use strict';
const { merge } = require('sdk/util/object');
const { get } = require('sdk/preferences/service');
merge(module.exports, require('./test-tab'));
merge(module.exports, require('./test-tab-events'));
merge(module.exports, require('./test-tab-observer'));
merge(module.exports, require('./test-tab-utils'));
// e10s tests should not ride the train to aurora
if (get('app.update.channel') !== 'nightly') {
module.exports = {};
}
require('sdk/test/runner').runTestsFromModule(module);

View File

@ -1,7 +1,7 @@
/* 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";
"use strict";
var options = require("@loader/options");
@ -43,4 +43,4 @@ exports.testPackaging = function(assert) {
"private browsing metadata should be be frozen");
};
require('sdk/test').run(exports);
require("sdk/test/runner").runTestsFromModule(module);

View File

@ -0,0 +1,4 @@
{
"id": "test-packaging",
"description": "Add-on development made easy."
}

View File

@ -17,4 +17,8 @@ exports["test self.data.load"] = assert => {
"relative paths work");
};
exports["test self.id"] = assert => {
assert.equal(self.id, "test-self@jetpack", "self.id should be correct.");
};
require("sdk/test/runner").runTestsFromModule(module);

View File

@ -6,6 +6,55 @@
const { on, once, off, emit, count } = require("sdk/event/core");
const { setImmediate, setTimeout } = require("sdk/timers");
const { defer } = require("sdk/core/promise");
/**
* Utility function that returns a promise once the specified event's `type`
* is emitted on the given `target`, or the delay specified is passed.
*
* @param {Object|Number} [target]
* The delay to wait, or the object that receives the event.
* If not given, the function returns a promise that will be resolved
* as soon as possible.
* @param {String} [type]
* A string representing the event type to waiting for.
* @param {Boolean} [capture]
* If `true`, `capture` indicates that the user wishes to initiate capture.
*
* @returns {Promise}
* A promise resolved once the delay given is passed, or the object
* receives the event specified
*/
const wait = (target, type, capture) => {
let { promise, resolve, reject } = defer();
if (!arguments.length) {
setImmediate(resolve);
}
else if (typeof(target) === "number") {
setTimeout(resolve, target);
}
else if (typeof(target.once) === "function") {
target.once(type, resolve);
}
else if (typeof(target.addEventListener) === "function") {
target.addEventListener(type, function listener(...args) {
this.removeEventListener(type, listener, capture);
resolve(...args);
}, capture);
}
else if (typeof(target) === "object" && target !== null) {
once(target, type, resolve);
}
else {
reject('Invalid target given.');
}
return promise;
};
exports.wait = wait;
function scenario(setup) {
return function(unit) {
return function(assert) {
@ -56,4 +105,4 @@ exports.FIFO = scenario(function(target, expected, actual) {
return expected.reduce(function(result, value) {
return result.concat(value + "#1", value + "#2", value + "#3");
}, []);
});
});

View File

@ -136,6 +136,7 @@ exports["test Create Proxy Test With Events"] = createProxyTest("", function (he
});
/* Disabled due to bug 1038432
// Bug 714778: There was some issue around `toString` functions
// that ended up being shared between content scripts
exports["test Shared To String Proxies"] = createProxyTest("", function(helper) {
@ -165,7 +166,7 @@ exports["test Shared To String Proxies"] = createProxyTest("", function(helper)
);
});
});
*/
// Ensure that postMessage is working correctly across documents with an iframe
let html = '<iframe id="iframe" name="test" src="data:text/html;charset=utf-8," />';

View File

@ -12,7 +12,7 @@ module.metadata = {
const { Cc, Ci } = require("chrome");
const { Loader } = require('sdk/test/loader');
const { LoaderWithHookedConsole } = require("sdk/test/loader");
const timer = require("sdk/timers");
const { setTimeout } = require("sdk/timers");
const self = require('sdk/self');
const { open, close, focus, ready } = require('sdk/window/helpers');
const { isPrivate } = require('sdk/private-browsing');
@ -22,6 +22,8 @@ const { getMostRecentBrowserWindow } = require('sdk/window/utils');
const { getWindow } = require('sdk/panel/window');
const { pb } = require('./private-browsing/helper');
const { URL } = require('sdk/url');
const { wait } = require('./event/helpers');
const fixtures = require('./fixtures')
const SVG_URL = fixtures.url('mofo_logo.SVG');
@ -544,7 +546,7 @@ function makeEventOrderTest(options) {
panel.on(event, function() {
assert.equal(event, expectedEvents.shift());
if (cb)
timer.setTimeout(cb, 1);
setTimeout(cb, 1);
});
return {then: expect};
}
@ -1056,6 +1058,136 @@ exports['test panel CSS list'] = function(assert, done) {
panel.show();
};
exports['test panel contextmenu validation'] = function(assert) {
const loader = Loader(module);
const { Panel } = loader.require('sdk/panel');
let panel = Panel({});
assert.equal(panel.contextMenu, false,
'contextMenu option is `false` by default');
panel.destroy();
panel = Panel({
contextMenu: false
});
assert.equal(panel.contextMenu, false,
'contextMenu option is `false`');
panel.contextMenu = true;
assert.equal(panel.contextMenu, true,
'contextMenu option accepts boolean values');
panel.destroy();
panel = Panel({
contextMenu: true
});
assert.equal(panel.contextMenu, true,
'contextMenu option is `true`');
panel.contextMenu = false;
assert.equal(panel.contextMenu, false,
'contextMenu option accepts boolean values');
assert.throws(() =>
Panel({contextMenu: 1}),
/The option "contextMenu" must be one of the following types: boolean, undefined, null/,
'contextMenu only accepts boolean or nil values');
panel = Panel();
assert.throws(() =>
panel.contextMenu = 1,
/The option "contextMenu" must be one of the following types: boolean, undefined, null/,
'contextMenu only accepts boolean or nil values');
loader.unload();
}
exports['test panel contextmenu enabled'] = function*(assert) {
const loader = Loader(module);
const { Panel } = loader.require('sdk/panel');
const { getActiveView } = loader.require('sdk/view/core');
const { getContentDocument } = loader.require('sdk/panel/utils');
let contextmenu = getMostRecentBrowserWindow().
document.getElementById("contentAreaContextMenu");
let panel = Panel({contextMenu: true});
panel.show();
yield wait(panel, 'show');
let view = getActiveView(panel);
let window = getContentDocument(view).defaultView;
let { sendMouseEvent } = window.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDOMWindowUtils);
yield ready(window);
assert.equal(contextmenu.state, 'closed',
'contextmenu must be closed');
sendMouseEvent('contextmenu', 20, 20, 2, 1, 0);
yield wait(contextmenu, 'popupshown');
assert.equal(contextmenu.state, 'open',
'contextmenu is opened');
contextmenu.hidePopup();
loader.unload();
}
exports['test panel contextmenu disabled'] = function*(assert) {
const loader = Loader(module);
const { Panel } = loader.require('sdk/panel');
const { getActiveView } = loader.require('sdk/view/core');
const { getContentDocument } = loader.require('sdk/panel/utils');
let contextmenu = getMostRecentBrowserWindow().
document.getElementById("contentAreaContextMenu");
let listener = () => assert.fail('popupshown should never be called');
let panel = Panel();
panel.show();
yield wait(panel, 'show');
let view = getActiveView(panel);
let window = getContentDocument(view).defaultView;
let { sendMouseEvent } = window.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDOMWindowUtils);
yield ready(window);
assert.equal(contextmenu.state, 'closed',
'contextmenu must be closed');
sendMouseEvent('contextmenu', 20, 20, 2, 1, 0);
contextmenu.addEventListener('popupshown', listener);
yield wait(1000);
contextmenu.removeEventListener('popupshown', listener);
assert.equal(contextmenu.state, 'closed',
'contextmenu was never open');
loader.unload();
}
if (isWindowPBSupported) {
exports.testGetWindow = function(assert, done) {

View File

@ -234,6 +234,8 @@ exports.testConsoleInnerID = function(assert) {
let Console = require("sdk/console/plain-text").PlainTextConsole;
let { log, info, warn, error, debug, exception, trace } = new Console(function() {}, "test ID");
prefs.set(SDK_LOG_LEVEL_PREF, "all");
let messages = [];
function onMessage({ subject }) {
let message = subject.wrappedJSObject;
@ -253,6 +255,8 @@ exports.testConsoleInnerID = function(assert) {
assert.deepEqual(messages[2], { msg: "Test error", type: "error", innerID: "test ID" }, "Should see the right event");
system.off("console-api-log-event", onMessage);
restorePrefs();
};
function restorePrefs() {

View File

@ -51,9 +51,13 @@ function checkError (assert, name, e) {
// we'd also like to assert that the right filename
// and linenumber is in the stacktrace
let tb = traceback.fromException(e);
// Get the second to last frame, as the last frame is inside
// toolkit/loader
let lastFrame = tb[tb.length-2];
// The last frame may be inside a loader
let lastFrame = tb[tb.length - 1];
if (lastFrame.fileName.indexOf("toolkit/loader.js") !== -1 ||
lastFrame.fileName.indexOf("sdk/loader/cuddlefish.js") !== -1)
lastFrame = tb[tb.length - 2];
assert.ok(lastFrame.fileName.indexOf("test-require.js") !== -1,
'Filename found in stacktrace');
assert.equal(lastFrame.lineNumber, REQUIRE_LINE_NO,

View File

@ -40,27 +40,6 @@ exports.testSelf = function(assert) {
'usePrivateBrowsing property is false by default');
};
exports.testSelfID = function(assert, done) {
var self = require("sdk/self");
// We can't assert anything about the ID inside the unit test right now,
// because the ID we get depends upon how the test was invoked. The idea
// is that it is supposed to come from the main top-level package's
// package.json file, from the "id" key.
assert.equal(typeof(self.id), "string", "self.id is a string");
assert.ok(self.id.length > 0);
AddonManager.getAddonByID(self.id, function(addon) {
if (!addon) {
assert.fail("did not find addon with self.id");
}
else {
assert.pass("found addon with self.id");
}
done();
});
}
exports.testSelfHandlesLackingLoaderOptions = function (assert) {
let root = module.uri.substr(0, module.uri.lastIndexOf('/'));
let uri = root + '/fixtures/loader/self/';