зеркало из https://github.com/mozilla/gecko-dev.git
Bug 901239 - Uplift Add-on SDK to Firefox r=me
This commit is contained in:
Родитель
3bd4539a82
Коммит
60bea9af38
|
@ -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 ###
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = [];
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
Загрузка…
Ссылка в новой задаче