From 60bea9af38d2e79a2822e55c6acbd75bef7b2ffe Mon Sep 17 00:00:00 2001 From: Wes Kocher Date: Sat, 3 Aug 2013 09:50:12 -0700 Subject: [PATCH] Bug 901239 - Uplift Add-on SDK to Firefox r=me --- .../source/doc/dev-guide-source/credits.md | 1 + addon-sdk/source/lib/sdk/content/worker.js | 2 +- .../source/lib/sdk/deprecated/api-utils.js | 76 +++++- addon-sdk/source/lib/sdk/tabs/tab-fennec.js | 11 + addon-sdk/source/lib/sdk/url.js | 14 +- addon-sdk/source/lib/sdk/util/array.js | 18 +- .../source/lib/sdk/windows/tabs-fennec.js | 9 +- .../source/python-lib/cuddlefish/__init__.py | 6 +- .../cuddlefish/tests/test_linker.py | 7 +- .../source/python-lib/mozrunner/__init__.py | 6 +- .../python-lib/mozrunner/killableprocess.py | 3 +- .../source/test/tabs/test-firefox-tabs.js | 34 --- addon-sdk/source/test/test-api-utils.js | 231 +++++++++++++----- addon-sdk/source/test/test-array.js | 134 +++++----- addon-sdk/source/test/test-browser-events.js | 6 + addon-sdk/source/test/test-content-events.js | 6 +- addon-sdk/source/test/test-tabs-common.js | 49 ++++ addon-sdk/source/test/test-url.js | 51 +++- 18 files changed, 467 insertions(+), 197 deletions(-) diff --git a/addon-sdk/source/doc/dev-guide-source/credits.md b/addon-sdk/source/doc/dev-guide-source/credits.md index 30386881911b..73de5d5fb304 100644 --- a/addon-sdk/source/doc/dev-guide-source/credits.md +++ b/addon-sdk/source/doc/dev-guide-source/credits.md @@ -140,6 +140,7 @@ We'd like to thank our many Jetpack project contributors! They include: * Tim Taubert * Shane Tomlinson * Dave Townsend +* [Fraser Tweedale](https://github.com/frasertweedale) * [Matthias Tylkowski](https://github.com/tylkomat) ### V ### diff --git a/addon-sdk/source/lib/sdk/content/worker.js b/addon-sdk/source/lib/sdk/content/worker.js index f8742db0affe..2eddaee035de 100644 --- a/addon-sdk/source/lib/sdk/content/worker.js +++ b/addon-sdk/source/lib/sdk/content/worker.js @@ -369,7 +369,7 @@ const WorkerSandbox = EventEmitter.compose({ /** * Message-passing facility for communication between code running * in the content and add-on process. - * @see https://jetpack.mozillalabs.com/sdk/latest/docs/#module/api-utils/content/worker + * @see https://addons.mozilla.org/en-US/developers/docs/sdk/latest/modules/sdk/content/worker.html */ const Worker = EventEmitter.compose({ on: Trait.required, diff --git a/addon-sdk/source/lib/sdk/deprecated/api-utils.js b/addon-sdk/source/lib/sdk/deprecated/api-utils.js index 568042b734e2..d4dc835eed95 100644 --- a/addon-sdk/source/lib/sdk/deprecated/api-utils.js +++ b/addon-sdk/source/lib/sdk/deprecated/api-utils.js @@ -5,12 +5,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; - module.metadata = { "stability": "deprecated" }; const memory = require("./memory"); + +const { merge } = require("../util/object"); +const { union } = require("../util/array"); +const { isNil } = require("../lang/type"); + // The possible return values of getTypeOf. const VALID_TYPES = [ "array", @@ -23,6 +27,8 @@ const VALID_TYPES = [ "undefined", ]; +const { isArray } = Array; + /** * Returns a function C that creates instances of privateCtor. C may be called * with or without the new keyword. The prototype of each instance returned @@ -86,6 +92,7 @@ exports.validateOptions = function validateOptions(options, requirements) { let validatedOptions = {}; for (let key in requirements) { + let isOptional = false; let mapThrew = false; let req = requirements[key]; let [optsVal, keyInOpts] = (key in options) ? @@ -103,17 +110,27 @@ exports.validateOptions = function validateOptions(options, requirements) { } } if (req.is) { - // Sanity check the caller's type names. - req.is.forEach(function (typ) { - if (VALID_TYPES.indexOf(typ) < 0) { - let msg = 'Internal error: invalid requirement type "' + typ + '".'; - throw new Error(msg); - } - }); - if (req.is.indexOf(getTypeOf(optsVal)) < 0) - throw new RequirementError(key, req); + let types = req.is; + + if (!isArray(types) && isArray(types.is)) + types = types.is; + + if (isArray(types)) { + isOptional = ['undefined', 'null'].every(v => ~types.indexOf(v)); + + // Sanity check the caller's type names. + types.forEach(function (typ) { + if (VALID_TYPES.indexOf(typ) < 0) { + let msg = 'Internal error: invalid requirement type "' + typ + '".'; + throw new Error(msg); + } + }); + if (types.indexOf(getTypeOf(optsVal)) < 0) + throw new RequirementError(key, req); + } } - if (req.ok && !req.ok(optsVal)) + + if (req.ok && ((!isOptional || !isNil(optsVal)) && !req.ok(optsVal))) throw new RequirementError(key, req); if (keyInOpts || (req.map && !mapThrew && optsVal !== undefined)) @@ -142,7 +159,7 @@ let getTypeOf = exports.getTypeOf = function getTypeOf(val) { if (typ === "object") { if (!val) return "null"; - if (Array.isArray(val)) + if (isArray(val)) return "array"; } return typ; @@ -164,3 +181,38 @@ function RequirementError(key, requirement) { this.message = msg; } RequirementError.prototype = Object.create(Error.prototype); + +let string = { is: ['string', 'undefined', 'null'] }; +exports.string = string; + +let number = { is: ['number', 'undefined', 'null'] }; +exports.number = number; + +let boolean = { is: ['boolean', 'undefined', 'null'] }; +exports.boolean = boolean; + +let object = { is: ['object', 'undefined', 'null'] }; +exports.object = object; + +let isTruthyType = type => !(type === 'undefined' || type === 'null'); +let findTypes = v => { while (!isArray(v) && v.is) v = v.is; return v }; + +function required(req) { + let types = (findTypes(req) || VALID_TYPES).filter(isTruthyType); + + return merge({}, req, {is: types}); +} +exports.required = required; + +function optional(req) { + req = merge({is: []}, req); + req.is = findTypes(req).filter(isTruthyType).concat('undefined', 'null'); + + return req; +} +exports.optional = optional; + +function either(...types) { + return union.apply(null, types.map(findTypes)); +} +exports.either = either; diff --git a/addon-sdk/source/lib/sdk/tabs/tab-fennec.js b/addon-sdk/source/lib/sdk/tabs/tab-fennec.js index 65929c184c19..70a7659beb68 100644 --- a/addon-sdk/source/lib/sdk/tabs/tab-fennec.js +++ b/addon-sdk/source/lib/sdk/tabs/tab-fennec.js @@ -33,6 +33,9 @@ const Tab = Class({ // TabReady let onReady = tabInternals.onReady = onTabReady.bind(this); tab.browser.addEventListener(EVENTS.ready.dom, onReady, false); + + let onPageShow = tabInternals.onPageShow = onTabPageShow.bind(this); + tab.browser.addEventListener(EVENTS.pageshow.dom, onPageShow, false); // TabClose let onClose = tabInternals.onClose = onTabClose.bind(this); @@ -180,8 +183,10 @@ function cleanupTab(tab) { if (tabInternals.tab.browser) { tabInternals.tab.browser.removeEventListener(EVENTS.ready.dom, tabInternals.onReady, false); + tabInternals.tab.browser.removeEventListener(EVENTS.pageshow.dom, tabInternals.onPageShow, false); } tabInternals.onReady = null; + tabInternals.onPageShow = null; tabInternals.window.BrowserApp.deck.removeEventListener(EVENTS.close.dom, tabInternals.onClose, false); tabInternals.onClose = null; rawTabNS(tabInternals.tab).tab = null; @@ -198,6 +203,12 @@ function onTabReady(event) { } } +function onTabPageShow(event) { + let win = event.target.defaultView; + if (win === win.top) + emit(this, 'pageshow', this, event.persisted); +} + // TabClose function onTabClose(event) { let rawTab = getTabForBrowser(event.target); diff --git a/addon-sdk/source/lib/sdk/url.js b/addon-sdk/source/lib/sdk/url.js index 87bca3686974..06a383ca18f6 100644 --- a/addon-sdk/source/lib/sdk/url.js +++ b/addon-sdk/source/lib/sdk/url.js @@ -1,7 +1,6 @@ /* 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"; module.metadata = { @@ -277,3 +276,16 @@ let isValidURI = exports.isValidURI = function (uri) { } return true; } + +function isLocalURL(url) { + if (String.indexOf(url, './') === 0) + return true; + + try { + return ['resource', 'data', 'chrome'].indexOf(URL(url).scheme) > -1; + } + catch(e) {} + + return false; +} +exports.isLocalURL = isLocalURL; diff --git a/addon-sdk/source/lib/sdk/util/array.js b/addon-sdk/source/lib/sdk/util/array.js index 75ccf9e8630f..198024624c7f 100644 --- a/addon-sdk/source/lib/sdk/util/array.js +++ b/addon-sdk/source/lib/sdk/util/array.js @@ -72,12 +72,22 @@ exports.remove = function remove(array, element) { * Source array. * @returns {Array} */ -exports.unique = function unique(array) { - return array.reduce(function(values, element) { - add(values, element); - return values; +function unique(array) { + return array.reduce(function(result, item) { + add(result, item); + return result; }, []); }; +exports.unique = unique; + +/** + * Produce an array that contains the union: each distinct element from all + * of the passed-in arrays. + */ +function union() { + return unique(Array.concat.apply(null, arguments)); +}; +exports.union = union; exports.flatten = function flatten(array){ var flat = []; diff --git a/addon-sdk/source/lib/sdk/windows/tabs-fennec.js b/addon-sdk/source/lib/sdk/windows/tabs-fennec.js index ba6793d274f8..8ce80d3cb4cb 100644 --- a/addon-sdk/source/lib/sdk/windows/tabs-fennec.js +++ b/addon-sdk/source/lib/sdk/windows/tabs-fennec.js @@ -131,9 +131,12 @@ function onTabOpen(event) { tab.on('ready', function() emit(gTabs, 'ready', tab)); tab.once('close', onTabClose); + tab.on('pageshow', function(_tab, persisted) + emit(gTabs, 'pageshow', tab, persisted)); + emit(tab, 'open', tab); emit(gTabs, 'open', tab); -}; +} // TabSelect function onTabSelect(event) { @@ -153,10 +156,10 @@ function onTabSelect(event) { emit(t, 'deactivate', t); emit(gTabs, 'deactivate', t); } -}; +} // TabClose function onTabClose(tab) { removeTab(tab); emit(gTabs, EVENTS.close.name, tab); -}; +} diff --git a/addon-sdk/source/python-lib/cuddlefish/__init__.py b/addon-sdk/source/python-lib/cuddlefish/__init__.py index 2672da9acb69..d3826e2fd461 100644 --- a/addon-sdk/source/python-lib/cuddlefish/__init__.py +++ b/addon-sdk/source/python-lib/cuddlefish/__init__.py @@ -172,7 +172,7 @@ parser_groups = ( (("", "--strip-sdk",), dict(dest="bundle_sdk", help=("Do not ship SDK modules in the xpi"), action="store_false", - default=True, + default=False, cmds=['run', 'test', 'testex', 'testpkgs', 'testall', 'xpi'])), (("", "--force-use-bundled-sdk",), dict(dest="force_use_bundled_sdk", @@ -564,7 +564,7 @@ def initializer(env_root, args, out=sys.stdout, err=sys.stderr): print >>out, 'Do "cfx test" to test it and "cfx run" to try it. Have fun!' else: print >>out, '\nYour sample add-on is now ready in the \'' + args[1] + '\' directory.' - print >>out, 'Change to that directory, then do "cfx test" to test it, \nand "cfx run" to try it. Have fun!' + print >>out, 'Change to that directory, then do "cfx test" to test it, \nand "cfx run" to try it. Have fun!' return {"result":0, "jid":jid} def buildJID(target_cfg): @@ -593,7 +593,7 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None, (options, args) = parse_args(**parser_kwargs) config_args = get_config_args(options.config, env_root); - + # reparse configs with arguments from local.json if config_args: parser_kwargs['arguments'] += config_args diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py index d13f306eda65..66c53cf148de 100755 --- a/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py +++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py @@ -188,16 +188,13 @@ class Contents(unittest.TestCase): self.failUnlessEqual(e.args[0], 0) zf = zipfile.ZipFile("seven.xpi", "r") names = zf.namelist() - # the first problem found in bug 664840 was that cuddlefish.js - # (the loader) was stripped out on windows, due to a /-vs-\ bug - self.assertIn("resources/addon-sdk/lib/sdk/loader/cuddlefish.js", names) - # the second problem found in bug 664840 was that an addon + # problem found in bug 664840 was that an addon # without an explicit tests/ directory would copy all files from # the package into a bogus JID-PKGNAME-tests/ directory, so check # for that testfiles = [fn for fn in names if "seven/tests" in fn] self.failUnlessEqual([], testfiles) - # the third problem was that data files were being stripped from + # another problem was that data files were being stripped from # the XPI. Note that data/ is only supposed to be included if a # module that actually gets used does a require("self") . self.assertIn("resources/seven/data/text.data", diff --git a/addon-sdk/source/python-lib/mozrunner/__init__.py b/addon-sdk/source/python-lib/mozrunner/__init__.py index 2e5c57abd908..87c2c320fc58 100644 --- a/addon-sdk/source/python-lib/mozrunner/__init__.py +++ b/addon-sdk/source/python-lib/mozrunner/__init__.py @@ -406,7 +406,8 @@ class Runner(object): def find_binary(self): """Finds the binary for self.names if one was not provided.""" binary = None - if sys.platform in ('linux2', 'sunos5', 'solaris'): + if sys.platform in ('linux2', 'sunos5', 'solaris') \ + or sys.platform.startswith('freebsd'): for name in reversed(self.names): binary = findInPath(name) elif os.name == 'nt' or sys.platform == 'cygwin': @@ -578,7 +579,8 @@ class FirefoxRunner(Runner): def names(self): if sys.platform == 'darwin': return ['firefox', 'nightly', 'shiretoko'] - if (sys.platform == 'linux2') or (sys.platform in ('sunos5', 'solaris')): + if sys.platform in ('linux2', 'sunos5', 'solaris') \ + or sys.platform.startswith('freebsd'): return ['firefox', 'mozilla-firefox', 'iceweasel'] if os.name == 'nt' or sys.platform == 'cygwin': return ['firefox'] diff --git a/addon-sdk/source/python-lib/mozrunner/killableprocess.py b/addon-sdk/source/python-lib/mozrunner/killableprocess.py index 892ed87fc85b..92f7ac3779fe 100644 --- a/addon-sdk/source/python-lib/mozrunner/killableprocess.py +++ b/addon-sdk/source/python-lib/mozrunner/killableprocess.py @@ -257,7 +257,8 @@ class Popen(subprocess.Popen): self.kill(group) else: - if (sys.platform == 'linux2') or (sys.platform in ('sunos5', 'solaris')): + if sys.platform in ('linux2', 'sunos5', 'solaris') \ + or sys.platform.startswith('freebsd'): def group_wait(timeout): try: os.waitpid(self.pid, 0) diff --git a/addon-sdk/source/test/tabs/test-firefox-tabs.js b/addon-sdk/source/test/tabs/test-firefox-tabs.js index 1d5346ac783a..fd59222719fc 100644 --- a/addon-sdk/source/test/tabs/test-firefox-tabs.js +++ b/addon-sdk/source/test/tabs/test-firefox-tabs.js @@ -949,40 +949,6 @@ exports.testOnLoadEventWithImage = function(test) { }); }; -exports.testOnPageShowEvent = function (test) { - test.waitUntilDone(); - - let firstUrl = 'data:text/html;charset=utf-8,First'; - let secondUrl = 'data:text/html;charset=utf-8,Second'; - - openBrowserWindow(function(window, browser) { - let counter = 0; - tabs.on('pageshow', function onPageShow(tab, persisted) { - counter++; - if (counter === 1) { - test.assert(!persisted, 'page should not be cached on initial load'); - tab.url = secondUrl; - } - else if (counter === 2) { - test.assert(!persisted, 'second test page should not be cached either'); - tab.attach({ - contentScript: 'setTimeout(function () { window.history.back(); }, 0)' - }); - } - else { - test.assert(persisted, 'when we get back to the fist page, it has to' + - 'come from cache'); - tabs.removeListener('pageshow', onPageShow); - closeBrowserWindow(window, function() test.done()); - } - }); - - tabs.open({ - url: firstUrl - }); - }); -}; - exports.testFaviconGetterDeprecation = function (test) { const { LoaderWithHookedConsole } = require("sdk/test/loader"); let { loader, messages } = LoaderWithHookedConsole(module); diff --git a/addon-sdk/source/test/test-api-utils.js b/addon-sdk/source/test/test-api-utils.js index f0a0849ac4b4..f26682b6f989 100644 --- a/addon-sdk/source/test/test-api-utils.js +++ b/addon-sdk/source/test/test-api-utils.js @@ -6,92 +6,94 @@ const apiUtils = require("sdk/deprecated/api-utils"); -exports.testPublicConstructor = function (test) { +exports.testPublicConstructor = function (assert) { function PrivateCtor() {} PrivateCtor.prototype = {}; let PublicCtor = apiUtils.publicConstructor(PrivateCtor); - test.assert( + assert.ok( PrivateCtor.prototype.isPrototypeOf(PublicCtor.prototype), "PrivateCtor.prototype should be prototype of PublicCtor.prototype" ); function testObj(useNew) { let obj = useNew ? new PublicCtor() : PublicCtor(); - test.assert(obj instanceof PublicCtor, + assert.ok(obj instanceof PublicCtor, "Object should be instance of PublicCtor"); - test.assert(obj instanceof PrivateCtor, + assert.ok(obj instanceof PrivateCtor, "Object should be instance of PrivateCtor"); - test.assert(PublicCtor.prototype.isPrototypeOf(obj), + assert.ok(PublicCtor.prototype.isPrototypeOf(obj), "PublicCtor's prototype should be prototype of object"); - test.assertEqual(obj.constructor, PublicCtor, + assert.equal(obj.constructor, PublicCtor, "Object constructor should be PublicCtor"); } testObj(true); testObj(false); }; -exports.testValidateOptionsEmpty = function (test) { +exports.testValidateOptionsEmpty = function (assert) { let val = apiUtils.validateOptions(null, {}); - assertObjsEqual(test, val, {}); + + assert.deepEqual(val, {}); val = apiUtils.validateOptions(null, { foo: {} }); - assertObjsEqual(test, val, {}); + assert.deepEqual(val, {}); val = apiUtils.validateOptions({}, {}); - assertObjsEqual(test, val, {}); + assert.deepEqual(val, {}); val = apiUtils.validateOptions({}, { foo: {} }); - assertObjsEqual(test, val, {}); + assert.deepEqual(val, {}); }; -exports.testValidateOptionsNonempty = function (test) { +exports.testValidateOptionsNonempty = function (assert) { let val = apiUtils.validateOptions({ foo: 123 }, {}); - assertObjsEqual(test, val, {}); + assert.deepEqual(val, {}); val = apiUtils.validateOptions({ foo: 123, bar: 456 }, { foo: {}, bar: {}, baz: {} }); - assertObjsEqual(test, val, { foo: 123, bar: 456 }); + + assert.deepEqual(val, { foo: 123, bar: 456 }); }; -exports.testValidateOptionsMap = function (test) { +exports.testValidateOptionsMap = function (assert) { let val = apiUtils.validateOptions({ foo: 3, bar: 2 }, { foo: { map: function (v) v * v }, bar: { map: function (v) undefined } }); - assertObjsEqual(test, val, { foo: 9, bar: undefined }); + assert.deepEqual(val, { foo: 9, bar: undefined }); }; -exports.testValidateOptionsMapException = function (test) { +exports.testValidateOptionsMapException = function (assert) { let val = apiUtils.validateOptions({ foo: 3 }, { foo: { map: function () { throw new Error(); }} }); - assertObjsEqual(test, val, { foo: 3 }); + assert.deepEqual(val, { foo: 3 }); }; -exports.testValidateOptionsOk = function (test) { +exports.testValidateOptionsOk = function (assert) { let val = apiUtils.validateOptions({ foo: 3, bar: 2, baz: 1 }, { foo: { ok: function (v) v }, bar: { ok: function (v) v } }); - assertObjsEqual(test, val, { foo: 3, bar: 2 }); + assert.deepEqual(val, { foo: 3, bar: 2 }); - test.assertRaises( + assert.throws( function () apiUtils.validateOptions({ foo: 2, bar: 2 }, { bar: { ok: function (v) v > 2 } }), - 'The option "bar" is invalid.', + /^The option "bar" is invalid/, "ok should raise exception on invalid option" ); - test.assertRaises( + assert.throws( function () apiUtils.validateOptions(null, { foo: { ok: function (v) v }}), - 'The option "foo" is invalid.', + /^The option "foo" is invalid/, "ok should raise exception on invalid option" ); }; -exports.testValidateOptionsIs = function (test) { +exports.testValidateOptionsIs = function (assert) { let opts = { array: [], boolean: true, @@ -114,18 +116,137 @@ exports.testValidateOptionsIs = function (test) { undef2: { is: ["undefined"] } }; let val = apiUtils.validateOptions(opts, requirements); - assertObjsEqual(test, val, opts); + assert.deepEqual(val, opts); - test.assertRaises( + assert.throws( function () apiUtils.validateOptions(null, { foo: { is: ["object", "number"] } }), - 'The option "foo" must be one of the following types: object, number', + /^The option "foo" must be one of the following types: object, number/, "Invalid type should raise exception" ); }; -exports.testValidateOptionsMapIsOk = function (test) { +exports.testValidateOptionsIsWithExportedValue = function (assert) { + let { string, number, boolean, object } = apiUtils; + + let opts = { + boolean: true, + number: 1337, + object: {}, + string: "foo" + }; + let requirements = { + string: { is: string }, + number: { is: number }, + boolean: { is: boolean }, + object: { is: object } + }; + let val = apiUtils.validateOptions(opts, requirements); + assert.deepEqual(val, opts); + + // Test the types are optional by default + val = apiUtils.validateOptions({foo: 'bar'}, requirements); + assert.deepEqual(val, {}); +}; + +exports.testValidateOptionsIsWithEither = function (assert) { + let { string, number, boolean, either } = apiUtils; + let text = { is: either(string, number) }; + + let requirements = { + text: text, + boolOrText: { is: either(text, boolean) } + }; + + let val = apiUtils.validateOptions({text: 12}, requirements); + assert.deepEqual(val, {text: 12}); + + val = apiUtils.validateOptions({text: "12"}, requirements); + assert.deepEqual(val, {text: "12"}); + + val = apiUtils.validateOptions({boolOrText: true}, requirements); + assert.deepEqual(val, {boolOrText: true}); + + val = apiUtils.validateOptions({boolOrText: "true"}, requirements); + assert.deepEqual(val, {boolOrText: "true"}); + + val = apiUtils.validateOptions({boolOrText: 1}, requirements); + assert.deepEqual(val, {boolOrText: 1}); + + assert.throws( + () => apiUtils.validateOptions({text: true}, requirements), + /^The option "text" must be one of the following types/, + "Invalid type should raise exception" + ); + + assert.throws( + () => apiUtils.validateOptions({boolOrText: []}, requirements), + /^The option "boolOrText" must be one of the following types/, + "Invalid type should raise exception" + ); +}; + +exports.testValidateOptionsWithRequiredAndOptional = function (assert) { + let { string, number, required, optional } = apiUtils; + + let opts = { + number: 1337, + string: "foo" + }; + + let requirements = { + string: required(string), + number: number + }; + + let val = apiUtils.validateOptions(opts, requirements); + assert.deepEqual(val, opts); + + val = apiUtils.validateOptions({string: "foo"}, requirements); + assert.deepEqual(val, {string: "foo"}); + + assert.throws( + () => apiUtils.validateOptions({number: 10}, requirements), + /^The option "string" must be one of the following types/, + "Invalid type should raise exception" + ); + + // Makes string optional + requirements.string = optional(requirements.string); + + val = apiUtils.validateOptions({number: 10}, requirements), + assert.deepEqual(val, {number: 10}); + +}; + + + +exports.testValidateOptionsWithExportedValue = function (assert) { + let { string, number, boolean, object } = apiUtils; + + let opts = { + boolean: true, + number: 1337, + object: {}, + string: "foo" + }; + let requirements = { + string: string, + number: number, + boolean: boolean, + object: object + }; + let val = apiUtils.validateOptions(opts, requirements); + assert.deepEqual(val, opts); + + // Test the types are optional by default + val = apiUtils.validateOptions({foo: 'bar'}, requirements); + assert.deepEqual(val, {}); +}; + + +exports.testValidateOptionsMapIsOk = function (assert) { let [map, is, ok] = [false, false, false]; let val = apiUtils.validateOptions({ foo: 1337 }, { foo: { @@ -134,48 +255,48 @@ exports.testValidateOptionsMapIsOk = function (test) { ok: function (v) v.length > 0 } }); - assertObjsEqual(test, val, { foo: "1337" }); + assert.deepEqual(val, { foo: "1337" }); let requirements = { foo: { is: ["object"], - ok: function () test.fail("is should have caused us to throw by now") + ok: function () assert.fail("is should have caused us to throw by now") } }; - test.assertRaises( + assert.throws( function () apiUtils.validateOptions(null, requirements), - 'The option "foo" must be one of the following types: object', + /^The option "foo" must be one of the following types: object/, "is should be used before ok is called" ); }; -exports.testValidateOptionsErrorMsg = function (test) { - test.assertRaises( +exports.testValidateOptionsErrorMsg = function (assert) { + assert.throws( function () apiUtils.validateOptions(null, { foo: { ok: function (v) v, msg: "foo!" } }), - "foo!", + /^foo!/, "ok should raise exception with customized message" ); }; -exports.testValidateMapWithMissingKey = function (test) { +exports.testValidateMapWithMissingKey = function (assert) { let val = apiUtils.validateOptions({ }, { foo: { map: function (v) v || "bar" } }); - assertObjsEqual(test, val, { foo: "bar" }); + assert.deepEqual(val, { foo: "bar" }); val = apiUtils.validateOptions({ }, { foo: { map: function (v) { throw "bar" } } }); - assertObjsEqual(test, val, { }); + assert.deepEqual(val, { }); }; -exports.testValidateMapWithMissingKeyAndThrown = function (test) { +exports.testValidateMapWithMissingKeyAndThrown = function (assert) { let val = apiUtils.validateOptions({}, { bar: { map: function(v) { throw "bar" } @@ -184,10 +305,10 @@ exports.testValidateMapWithMissingKeyAndThrown = function (test) { map: function(v) "foo" } }); - assertObjsEqual(test, val, { baz: "foo" }); + assert.deepEqual(val, { baz: "foo" }); }; -exports.testAddIterator = function testAddIterator(test) { +exports.testAddIterator = function testAddIterator (assert) { let obj = {}; let keys = ["foo", "bar", "baz"]; let vals = [1, 2, 3]; @@ -203,34 +324,20 @@ exports.testAddIterator = function testAddIterator(test) { let keysItr = []; for (let key in obj) keysItr.push(key); - test.assertEqual(keysItr.length, keys.length, + + assert.equal(keysItr.length, keys.length, "the keys iterator returns the correct number of items"); for (let i = 0; i < keys.length; i++) - test.assertEqual(keysItr[i], keys[i], "the key is correct"); + assert.equal(keysItr[i], keys[i], "the key is correct"); let valsItr = []; for each (let val in obj) valsItr.push(val); - test.assertEqual(valsItr.length, vals.length, + assert.equal(valsItr.length, vals.length, "the vals iterator returns the correct number of items"); for (let i = 0; i < vals.length; i++) - test.assertEqual(valsItr[i], vals[i], "the val is correct"); + assert.equal(valsItr[i], vals[i], "the val is correct"); }; -function assertObjsEqual(test, obj1, obj2) { - var items = 0; - for (let key in obj1) { - items++; - test.assert(key in obj2, "obj1 key should be present in obj2"); - test.assertEqual(obj2[key], obj1[key], "obj1 value should match obj2 value"); - } - for (let key in obj2) { - items++; - test.assert(key in obj1, "obj2 key should be present in obj1"); - test.assertEqual(obj1[key], obj2[key], "obj2 value should match obj1 value"); - } - if (!items) - test.assertEqual(JSON.stringify(obj1), JSON.stringify(obj2), - "obj1 should have same JSON representation as obj2"); -} +require('test').run(exports); diff --git a/addon-sdk/source/test/test-array.js b/addon-sdk/source/test/test-array.js index af26770edbbe..8a9cb4402be8 100644 --- a/addon-sdk/source/test/test-array.js +++ b/addon-sdk/source/test/test-array.js @@ -5,67 +5,67 @@ const array = require('sdk/util/array'); -exports.testHas = function(test) { +exports.testHas = function(assert) { var testAry = [1, 2, 3]; - test.assertEqual(array.has([1, 2, 3], 1), true); - test.assertEqual(testAry.length, 3); - test.assertEqual(testAry[0], 1); - test.assertEqual(testAry[1], 2); - test.assertEqual(testAry[2], 3); - test.assertEqual(array.has(testAry, 2), true); - test.assertEqual(array.has(testAry, 3), true); - test.assertEqual(array.has(testAry, 4), false); - test.assertEqual(array.has(testAry, '1'), false); + assert.equal(array.has([1, 2, 3], 1), true); + assert.equal(testAry.length, 3); + assert.equal(testAry[0], 1); + assert.equal(testAry[1], 2); + assert.equal(testAry[2], 3); + assert.equal(array.has(testAry, 2), true); + assert.equal(array.has(testAry, 3), true); + assert.equal(array.has(testAry, 4), false); + assert.equal(array.has(testAry, '1'), false); }; -exports.testHasAny = function(test) { +exports.testHasAny = function(assert) { var testAry = [1, 2, 3]; - test.assertEqual(array.hasAny([1, 2, 3], [1]), true); - test.assertEqual(array.hasAny([1, 2, 3], [1, 5]), true); - test.assertEqual(array.hasAny([1, 2, 3], [5, 1]), true); - test.assertEqual(array.hasAny([1, 2, 3], [5, 2]), true); - test.assertEqual(array.hasAny([1, 2, 3], [5, 3]), true); - test.assertEqual(array.hasAny([1, 2, 3], [5, 4]), false); - test.assertEqual(testAry.length, 3); - test.assertEqual(testAry[0], 1); - test.assertEqual(testAry[1], 2); - test.assertEqual(testAry[2], 3); - test.assertEqual(array.hasAny(testAry, [2]), true); - test.assertEqual(array.hasAny(testAry, [3]), true); - test.assertEqual(array.hasAny(testAry, [4]), false); - test.assertEqual(array.hasAny(testAry), false); - test.assertEqual(array.hasAny(testAry, '1'), false); + assert.equal(array.hasAny([1, 2, 3], [1]), true); + assert.equal(array.hasAny([1, 2, 3], [1, 5]), true); + assert.equal(array.hasAny([1, 2, 3], [5, 1]), true); + assert.equal(array.hasAny([1, 2, 3], [5, 2]), true); + assert.equal(array.hasAny([1, 2, 3], [5, 3]), true); + assert.equal(array.hasAny([1, 2, 3], [5, 4]), false); + assert.equal(testAry.length, 3); + assert.equal(testAry[0], 1); + assert.equal(testAry[1], 2); + assert.equal(testAry[2], 3); + assert.equal(array.hasAny(testAry, [2]), true); + assert.equal(array.hasAny(testAry, [3]), true); + assert.equal(array.hasAny(testAry, [4]), false); + assert.equal(array.hasAny(testAry), false); + assert.equal(array.hasAny(testAry, '1'), false); }; -exports.testAdd = function(test) { +exports.testAdd = function(assert) { var testAry = [1]; - test.assertEqual(array.add(testAry, 1), false); - test.assertEqual(testAry.length, 1); - test.assertEqual(testAry[0], 1); - test.assertEqual(array.add(testAry, 2), true); - test.assertEqual(testAry.length, 2); - test.assertEqual(testAry[0], 1); - test.assertEqual(testAry[1], 2); + assert.equal(array.add(testAry, 1), false); + assert.equal(testAry.length, 1); + assert.equal(testAry[0], 1); + assert.equal(array.add(testAry, 2), true); + assert.equal(testAry.length, 2); + assert.equal(testAry[0], 1); + assert.equal(testAry[1], 2); }; -exports.testRemove = function(test) { +exports.testRemove = function(assert) { var testAry = [1, 2]; - test.assertEqual(array.remove(testAry, 3), false); - test.assertEqual(testAry.length, 2); - test.assertEqual(testAry[0], 1); - test.assertEqual(testAry[1], 2); - test.assertEqual(array.remove(testAry, 2), true); - test.assertEqual(testAry.length, 1); - test.assertEqual(testAry[0], 1); + assert.equal(array.remove(testAry, 3), false); + assert.equal(testAry.length, 2); + assert.equal(testAry[0], 1); + assert.equal(testAry[1], 2); + assert.equal(array.remove(testAry, 2), true); + assert.equal(testAry.length, 1); + assert.equal(testAry[0], 1); }; -exports.testFlatten = function(test) { - test.assertEqual(array.flatten([1, 2, 3]).length, 3); - test.assertEqual(array.flatten([1, [2, 3]]).length, 3); - test.assertEqual(array.flatten([1, [2, [3]]]).length, 3); - test.assertEqual(array.flatten([[1], [[2, [3]]]]).length, 3); +exports.testFlatten = function(assert) { + assert.equal(array.flatten([1, 2, 3]).length, 3); + assert.equal(array.flatten([1, [2, 3]]).length, 3); + assert.equal(array.flatten([1, [2, [3]]]).length, 3); + assert.equal(array.flatten([[1], [[2, [3]]]]).length, 3); }; -exports.testUnique = function(test) { +exports.testUnique = function(assert) { var Class = function () {}; var A = {}; var B = new Class(); @@ -73,23 +73,31 @@ exports.testUnique = function(test) { var D = {}; var E = new Class(); - compareArray(array.unique([1,2,3,1,2]), [1,2,3]); - compareArray(array.unique([1,1,1,4,9,5,5]), [1,4,9,5]); - compareArray(array.unique([A, A, A, B, B, D]), [A,B,D]); - compareArray(array.unique([A, D, A, E, E, D, A, A, C]), [A, D, E, C]) - - function compareArray (a, b) { - test.assertEqual(a.length, b.length); - for (let i = 0; i < a.length; i++) { - test.assertEqual(a[i], b[i]); - } - } + assert.deepEqual(array.unique([1,2,3,1,2]), [1,2,3]); + assert.deepEqual(array.unique([1,1,1,4,9,5,5]), [1,4,9,5]); + assert.deepEqual(array.unique([A, A, A, B, B, D]), [A,B,D]); + assert.deepEqual(array.unique([A, D, A, E, E, D, A, A, C]), [A, D, E, C]) }; -exports.testFind = function(test) { +exports.testUnion = function(assert) { + var Class = function () {}; + var A = {}; + var B = new Class(); + var C = [ 1, 2, 3 ]; + var D = {}; + var E = new Class(); + + assert.deepEqual(array.union([1, 2, 3],[7, 1, 2]), [1, 2, 3, 7]); + assert.deepEqual(array.union([1, 1, 1, 4, 9, 5, 5], [10, 1, 5]), [1, 4, 9, 5, 10]); + assert.deepEqual(array.union([A, B], [A, D]), [A, B, D]); + assert.deepEqual(array.union([A, D], [A, E], [E, D, A], [A, C]), [A, D, E, C]); +}; + +exports.testFind = function(assert) { let isOdd = (x) => x % 2; - test.assertEqual(array.find([2, 4, 5, 7, 8, 9], isOdd), 5); - test.assertEqual(array.find([2, 4, 6, 8], isOdd), undefined); - test.assertEqual(array.find([2, 4, 6, 8], isOdd, null), null); + assert.equal(array.find([2, 4, 5, 7, 8, 9], isOdd), 5); + assert.equal(array.find([2, 4, 6, 8], isOdd), undefined); + assert.equal(array.find([2, 4, 6, 8], isOdd, null), null); }; +require('test').run(exports); diff --git a/addon-sdk/source/test/test-browser-events.js b/addon-sdk/source/test/test-browser-events.js index afa7180977d5..b4d8a1b016da 100644 --- a/addon-sdk/source/test/test-browser-events.js +++ b/addon-sdk/source/test/test-browser-events.js @@ -4,6 +4,12 @@ "use strict"; +module.metadata = { + engines: { + "Firefox": "*" + } +}; + const { Loader } = require("sdk/test/loader"); const { open, getMostRecentBrowserWindow, getOuterId } = require("sdk/window/utils"); const { setTimeout } = require("sdk/timers"); diff --git a/addon-sdk/source/test/test-content-events.js b/addon-sdk/source/test/test-content-events.js index 43d4fadf9847..f4a7b1644792 100644 --- a/addon-sdk/source/test/test-content-events.js +++ b/addon-sdk/source/test/test-content-events.js @@ -44,13 +44,17 @@ exports["test multiple tabs"] = function(assert, done) { on(events, "data", function({type, target, timeStamp}) { // ignore about:blank pages and *-document-global-created // events that are not very consistent. + // ignore http:// requests, as Fennec's `about:home` page + // displays add-ons a user could install if (target.URL !== "about:blank" && + target.URL !== "about:home" && + !target.URL.match(/^https?:\/\//i) && type !== "chrome-document-global-created" && type !== "content-document-global-created") actual.push(type + " -> " + target.URL) }); - let window = getMostRecentBrowserWindow(); + let window = getMostRecentBrowserWindow(); let firstTab = open("data:text/html,first-tab", window); when("pageshow", firstTab). diff --git a/addon-sdk/source/test/test-tabs-common.js b/addon-sdk/source/test/test-tabs-common.js index b225b8ef47ab..9f7b32fb7607 100644 --- a/addon-sdk/source/test/test-tabs-common.js +++ b/addon-sdk/source/test/test-tabs-common.js @@ -457,3 +457,52 @@ exports.testTabReload = function(test) { } }); }; + +exports.testOnPageShowEvent = function (test) { + test.waitUntilDone(); + + let events = []; + let firstUrl = 'data:text/html;charset=utf-8,First'; + let secondUrl = 'data:text/html;charset=utf-8,Second'; + + let counter = 0; + function onPageShow (tab, persisted) { + events.push('pageshow'); + counter++; + if (counter === 1) { + test.assertEqual(persisted, false, 'page should not be cached on initial load'); + tab.url = secondUrl; + } + else if (counter === 2) { + test.assertEqual(persisted, false, 'second test page should not be cached either'); + tab.attach({ + contentScript: 'setTimeout(function () { window.history.back(); }, 0)' + }); + } + else { + test.assertEqual(persisted, true, 'when we get back to the fist page, it has to' + + 'come from cache'); + tabs.removeListener('pageshow', onPageShow); + tabs.removeListener('open', onOpen); + tabs.removeListener('ready', onReady); + tab.close(() => { + ['open', 'ready', 'pageshow', 'ready', + 'pageshow', 'pageshow'].map((type, i) => { + test.assertEqual(type, events[i], 'correct ordering of events'); + }); + test.done() + }); + } + } + + function onOpen () events.push('open'); + function onReady () events.push('ready'); + + tabs.on('pageshow', onPageShow); + tabs.on('open', onOpen); + tabs.on('ready', onReady); + tabs.open({ + url: firstUrl + }); +}; + diff --git a/addon-sdk/source/test/test-url.js b/addon-sdk/source/test/test-url.js index 6f383a37b237..bfd55572a546 100644 --- a/addon-sdk/source/test/test-url.js +++ b/addon-sdk/source/test/test-url.js @@ -3,7 +3,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 'use strict'; -const { URL, toFilename, fromFilename, isValidURI, getTLD, DataURL } = require('sdk/url'); +const { + URL, + toFilename, + fromFilename, + isValidURI, + getTLD, + DataURL, + isLocalURL } = require('sdk/url'); + const { pathFor } = require('sdk/system'); const file = require('sdk/io/file'); const tabs = require('sdk/tabs'); @@ -63,11 +71,11 @@ exports.testParseHttpSearchAndHash = function (assert) { var info = URL('https://www.moz.com/some/page.html'); assert.equal(info.hash, ''); assert.equal(info.search, ''); - + var hashOnly = URL('https://www.sub.moz.com/page.html#justhash'); assert.equal(hashOnly.search, ''); assert.equal(hashOnly.hash, '#justhash'); - + var queryOnly = URL('https://www.sub.moz.com/page.html?my=query'); assert.equal(queryOnly.search, '?my=query'); assert.equal(queryOnly.hash, ''); @@ -75,11 +83,11 @@ exports.testParseHttpSearchAndHash = function (assert) { var qMark = URL('http://www.moz.org?'); assert.equal(qMark.search, ''); assert.equal(qMark.hash, ''); - + var hash = URL('http://www.moz.org#'); assert.equal(hash.search, ''); assert.equal(hash.hash, ''); - + var empty = URL('http://www.moz.org?#'); assert.equal(hash.search, ''); assert.equal(hash.hash, ''); @@ -347,6 +355,39 @@ exports.testWindowLocationMatch = function (assert, done) { }) }; +exports.testURLInRegExpTest = function(assert) { + let url = 'https://mozilla.org'; + assert.equal((new RegExp(url).test(URL(url))), true, 'URL instances work in a RegExp test'); +} + +exports.testLocalURL = function(assert) { + [ + 'data:text/html;charset=utf-8,foo and bar', + 'data:text/plain,foo and bar', + 'resource://gre/modules/commonjs/', + 'chrome://browser/content/browser.xul' + ].forEach(aUri => { + assert.ok(isLocalURL(aUri), aUri + ' is a Local URL'); + }) + +} + +exports.testLocalURLwithRemoteURL = function(assert) { + validURIs().filter(url => !url.startsWith('data:')).forEach(aUri => { + assert.ok(!isLocalURL(aUri), aUri + ' is an invalid Local URL'); + }); +} + +exports.testLocalURLwithInvalidURL = function(assert) { + invalidURIs().concat([ + 'data:foo and bar', + 'resource:// must fail', + 'chrome:// here too' + ]).forEach(aUri => { + assert.ok(!isLocalURL(aUri), aUri + ' is an invalid Local URL'); + }); +} + function validURIs() { return [ 'http://foo.com/blah_blah',