Bug 947501 - Uplift Addon SDK to Firefox

This commit is contained in:
Erik Vold 2013-12-19 14:50:18 -08:00
parent f13e427458
commit 74b51a6914
15 changed files with 2402 additions and 43 deletions

View File

@ -24,6 +24,7 @@ const { Worker } = require("./content/worker");
const { EventTarget } = require("./event/target");
const { emit } = require('./event/core');
const { when } = require('./system/unload');
const selection = require('./selection');
// All user items we add have this class.
const ITEM_CLASS = "addon-context-menu-item";
@ -204,6 +205,66 @@ let URLContext = Class({
});
exports.URLContext = URLContext;
// Matches when the user-supplied predicate returns true
let PredicateContext = Class({
extends: Context,
initialize: function initialize(predicate) {
let options = validateOptions({ predicate: predicate }, {
predicate: {
is: ["function"],
msg: "predicate must be a function."
}
});
internal(this).predicate = options.predicate;
},
isCurrent: function isCurrent(popupNode) {
return internal(this).predicate(populateCallbackNodeData(popupNode));
}
});
exports.PredicateContext = PredicateContext;
// List all editable types of inputs. Or is it better to have a list
// of non-editable inputs?
let editableInputs = {
email: true,
number: true,
password: true,
search: true,
tel: true,
text: true,
textarea: true,
url: true
};
function populateCallbackNodeData(node) {
let window = node.ownerDocument.defaultView;
let data = {};
data.documentType = node.ownerDocument.contentType;
data.documentURL = node.ownerDocument.location.href;
data.targetName = node.nodeName.toLowerCase();
data.targetID = node.id || null ;
if ((data.targetName === 'input' && editableInputs[node.type]) ||
data.targetName === 'textarea') {
data.isEditable = !node.readOnly && !node.disabled;
}
else {
data.isEditable = node.isContentEditable;
}
data.selectionText = selection.text;
data.srcURL = node.src || null;
data.linkURL = node.href || null;
data.value = node.value || null;
return data;
}
function removeItemFromArray(array, item) {
return array.filter(function(i) i !== item);
}

View File

@ -14,6 +14,7 @@ const suites = require('@test/options').allTestModules;
const { Loader } = require("sdk/test/loader");
const cuddlefish = require("sdk/loader/cuddlefish");
let loader = Loader(module);
const NOT_TESTS = ['setup', 'teardown'];
var TestFinder = exports.TestFinder = function TestFinder(options) {
@ -51,11 +52,10 @@ TestFinder.prototype = {
} else
filter = function() {return true};
suites.forEach(
function(suite) {
suites.forEach(function(suite) {
// Load each test file as a main module in its own loader instance
// `suite` is defined by cuddlefish/manifest.py:ManifestBuilder.build
let loader = Loader(module);
let suiteModule;
try {

View File

@ -129,6 +129,12 @@ function isArguments(value) {
}
exports.isArguments = isArguments;
let isMap = value => Object.prototype.toString.call(value) === "[object Map]"
exports.isMap = isMap;
let isSet = value => Object.prototype.toString.call(value) === "[object Set]"
exports.isSet = isSet;
/**
* Returns true if it is a primitive `value`. (null, undefined, number,
* boolean, string)

View File

@ -315,14 +315,24 @@ function getElementWithSelection() {
if (!element)
return null;
let { value, selectionStart, selectionEnd } = element;
try {
// Accessing selectionStart and selectionEnd on e.g. a button
// results in an exception thrown as per the HTML5 spec. See
// http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#textFieldSelection
let hasSelection = typeof value === "string" &&
let { value, selectionStart, selectionEnd } = element;
let hasSelection = typeof value === "string" &&
!isNaN(selectionStart) &&
!isNaN(selectionEnd) &&
selectionStart !== selectionEnd;
return hasSelection ? element : null;
return hasSelection ? element : null;
}
catch (err) {
return null;
}
}
/**

View File

@ -0,0 +1,576 @@
/* 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 = {
"stability": "experimental"
};
// Disclamer:
// In this module we'll have some common argument / variable names
// to hint their type or behavior.
//
// - `f` stands for "function" that is intended to be side effect
// free.
// - `p` stands for "predicate" that is function which returns logical
// true or false and is intended to be side effect free.
// - `x` / `y` single item of the sequence.
// - `xs` / `ys` sequence of `x` / `y` items where `x` / `y` signifies
// type of the items in sequence, so sequence is not of the same item.
// - `_` used for argument(s) or variable(s) who's values are ignored.
const { complement, flip, identity } = require("../lang/functional");
const { iteratorSymbol } = require("../util/iteration");
const { isArray, isArguments, isMap, isSet,
isString, isBoolean, isNumber } = require("../lang/type");
const Sequence = function Sequence(iterator) {
if (iterator.isGenerator && iterator.isGenerator())
this[iteratorSymbol] = iterator;
else
throw TypeError("Expected generator argument");
};
exports.Sequence = Sequence;
const polymorphic = dispatch => x =>
x === null ? dispatch.null(null) :
x === void(0) ? dispatch.void(void(0)) :
isArray(x) ? (dispatch.array || dispatch.indexed)(x) :
isString(x) ? (dispatch.string || dispatch.indexed)(x) :
isArguments(x) ? (dispatch.arguments || dispatch.indexed)(x) :
isMap(x) ? dispatch.map(x) :
isSet(x) ? dispatch.set(x) :
isNumber(x) ? dispatch.number(x) :
isBoolean(x) ? dispatch.boolean(x) :
dispatch.default(x);
const nogen = function*() {};
const empty = () => new Sequence(nogen);
exports.empty = empty;
const seq = polymorphic({
null: empty,
void: empty,
array: identity,
string: identity,
arguments: identity,
map: identity,
set: identity,
default: x => x instanceof Sequence ? x : new Sequence(x)
});
exports.seq = seq;
// Function to cast seq to string.
const string = (...etc) => "".concat(...etc);
exports.string = string;
// Function for casting seq to plain object.
const object = (...pairs) => {
let result = {};
for (let [key, value] of pairs)
result[key] = value;
return result;
};
exports.object = object;
// Takes `getEnumerator` function that returns `nsISimpleEnumerator`
// and creates lazy sequence of it's items. Note that function does
// not take `nsISimpleEnumerator` itslef because that would allow
// single iteration, which would not be consistent with rest of the
// lazy sequences.
const fromEnumerator = getEnumerator => seq(function* () {
const enumerator = getEnumerator();
while (enumerator.hasMoreElements())
yield enumerator.getNext();
});
exports.fromEnumerator = fromEnumerator;
// Takes `object` and returns lazy sequence of own `[key, value]`
// pairs (does not include inherited and non enumerable keys).
const pairs = polymorphic({
null: empty,
void: empty,
map: identity,
indexed: indexed => seq(function* () {
const count = indexed.length;
let index = 0;
while (index < count) {
yield [index, indexed[index]];
index = index + 1;
}
}),
default: object => seq(function* () {
for (let key of Object.keys(object))
yield [key, object[key]];
})
});
exports.pairs = pairs;
const keys = polymorphic({
null: empty,
void: empty,
indexed: indexed => seq(function* () {
const count = indexed.length;
let index = 0;
while (index < count) {
yield index;
index = index + 1;
}
}),
map: map => seq(function* () {
for (let [key, _] of map)
yield key;
}),
default: object => seq(function* () {
for (let key of Object.keys(object))
yield key;
})
});
exports.keys = keys;
const values = polymorphic({
null: empty,
void: empty,
set: identity,
indexed: indexed => seq(function* () {
const count = indexed.length;
let index = 0;
while (index < count) {
yield indexed[index];
index = index + 1;
}
}),
map: map => seq(function* () {
for (let [_, value] of map) yield value;
}),
default: object => seq(function* () {
for (let key of Object.keys(object)) yield object[key];
})
});
exports.values = values;
// Returns a lazy sequence of `x`, `f(x)`, `f(f(x))` etc.
// `f` must be free of side-effects. Note that returned
// sequence is infinite so it must be consumed partially.
//
// Implements clojure iterate:
// http://clojuredocs.org/clojure_core/clojure.core/iterate
const iterate = (f, x) => seq(function* () {
let state = x;
while (true) {
yield state;
state = f(state);
}
});
exports.iterate = iterate;
// Returns a lazy sequence of the items in sequence for which `p(item)`
// returns `true`. `p` must be free of side-effects.
//
// Implements clojure filter:
// http://clojuredocs.org/clojure_core/clojure.core/filter
const filter = (p, sequence) => seq(function* () {
if (sequence !== null && sequence !== void(0)) {
for (let item of sequence) {
if (p(item))
yield item;
}
}
});
exports.filter = filter;
// Returns a lazy sequence consisting of the result of applying `f` to the
// set of first items of each sequence, followed by applying f to the set
// of second items in each sequence, until any one of the sequences is
// exhausted. Any remaining items in other sequences are ignored. Function
// `f` should accept number-of-sequences arguments.
//
// Implements clojure map:
// http://clojuredocs.org/clojure_core/clojure.core/map
const map = (f, ...sequences) => seq(function* () {
const count = sequences.length;
// Optimize a single sequence case
if (count === 1) {
let [sequence] = sequences;
if (sequence !== null && sequence !== void(0)) {
for (let item of sequence)
yield f(item);
}
}
else {
// define args array that will be recycled on each
// step to aggregate arguments to be passed to `f`.
let args = [];
// define inputs to contain started generators.
let inputs = [];
let index = 0;
while (index < count) {
inputs[index] = sequences[index][iteratorSymbol]();
index = index + 1;
}
// Run loop yielding of applying `f` to the set of
// items at each step until one of the `inputs` is
// exhausted.
let done = false;
while (!done) {
let index = 0;
let value = void(0);
while (index < count && !done) {
({ done, value }) = inputs[index].next();
// If input is not exhausted yet store value in args.
if (!done) {
args[index] = value;
index = index + 1;
}
}
// If none of the inputs is exhasted yet, `args` contain items
// from each input so we yield application of `f` over them.
if (!done)
yield f(...args);
}
}
});
exports.map = map;
// Returns a lazy sequence of the intermediate values of the reduction (as
// per reduce) of sequence by `f`, starting with `initial` value if provided.
//
// Implements clojure reductions:
// http://clojuredocs.org/clojure_core/clojure.core/reductions
const reductions = (...params) => {
const count = params.length;
let hasInitial = false;
let f, initial, source;
if (count === 2) {
([f, source]) = params;
}
else if (count === 3) {
([f, initial, source]) = params;
hasInitial = true;
}
else {
throw Error("Invoked with wrong number of arguments: " + count);
}
const sequence = seq(source);
return seq(function* () {
let started = hasInitial;
let result = void(0);
// If initial is present yield it.
if (hasInitial)
yield (result = initial);
// For each item of the sequence accumulate new result.
for (let item of sequence) {
// If nothing has being yield yet set result to first
// item and yield it.
if (!started) {
started = true;
yield (result = item);
}
// Otherwise accumulate new result and yield it.
else {
yield (result = f(result, item));
}
}
// If nothing has being yield yet it's empty sequence and no
// `initial` was provided in which case we need to yield `f()`.
if (!started)
yield f();
});
};
exports.reductions = reductions;
// `f` should be a function of 2 arguments. If `initial` is not supplied,
// returns the result of applying `f` to the first 2 items in sequence, then
// applying `f` to that result and the 3rd item, etc. If sequence contains no
// items, `f` must accept no arguments as well, and reduce returns the
// result of calling f with no arguments. If sequence has only 1 item, it
// is returned and `f` is not called. If `initial` is supplied, returns the
// result of applying `f` to `initial` and the first item in sequence, then
// applying `f` to that result and the 2nd item, etc. If sequence contains no
// items, returns `initial` and `f` is not called.
//
// Implements clojure reduce:
// http://clojuredocs.org/clojure_core/clojure.core/reduce
const reduce = (...args) => {
const xs = reductions(...args);
let x;
for (x of xs) void(0);
return x;
};
exports.reduce = reduce;
const each = (f, sequence) => {
for (let x of seq(sequence)) void(f(x));
};
exports.each = each;
const inc = x => x + 1;
// Returns the number of items in the sequence. `count(null)` && `count()`
// returns `0`. Also works on strings, arrays, Maps & Sets.
// Implements clojure count:
// http://clojuredocs.org/clojure_core/clojure.core/count
const count = polymorphic({
null: _ => 0,
void: _ => 0,
indexed: indexed => indexed.length,
map: map => map.size,
set: set => set.size,
default: xs => reduce(inc, 0, xs)
});
exports.count = count;
// Returns `true` if sequence has no items.
// Implements clojure empty?:
// http://clojuredocs.org/clojure_core/clojure.core/empty_q
const isEmpty = sequence => {
// Treat `null` and `undefined` as empty sequences.
if (sequence === null || sequence === void(0))
return true;
// If contains any item non empty so return `false`.
for (let _ of sequence)
return false;
// If has not returned yet, there was nothing to iterate
// so it's empty.
return true;
};
exports.isEmpty = isEmpty;
const and = (a, b) => a && b;
// Returns true if `p(x)` is logical `true` for every `x` in sequence, else
// `false`.
//
// Implements clojure every?:
// http://clojuredocs.org/clojure_core/clojure.core/every_q
const isEvery = (p, sequence) => {
if (sequence !== null && sequence !== void(0)) {
for (let item of sequence) {
if (!p(item))
return false;
}
}
return true;
};
exports.isEvery = isEvery;
// Returns the first logical true value of (p x) for any x in sequence,
// else `null`.
//
// Implements clojure some:
// http://clojuredocs.org/clojure_core/clojure.core/some
const some = (p, sequence) => {
if (sequence !== null && sequence !== void(0)) {
for (let item of sequence) {
if (p(item))
return true;
}
}
return null;
};
exports.some = some;
// Returns a lazy sequence of the first `n` items in sequence, or all items if
// there are fewer than `n`.
//
// Implements clojure take:
// http://clojuredocs.org/clojure_core/clojure.core/take
const take = (n, sequence) => n <= 0 ? empty() : seq(function* () {
let count = n;
for (let item of sequence) {
yield item;
count = count - 1;
if (count === 0) break;
}
});
exports.take = take;
// Returns a lazy sequence of successive items from sequence while
// `p(item)` returns `true`. `p` must be free of side-effects.
//
// Implements clojure take-while:
// http://clojuredocs.org/clojure_core/clojure.core/take-while
const takeWhile = (p, sequence) => seq(function* () {
for (let item of sequence) {
if (!p(item))
break;
yield item;
}
});
exports.takeWhile = takeWhile;
// Returns a lazy sequence of all but the first `n` items in
// sequence.
//
// Implements clojure drop:
// http://clojuredocs.org/clojure_core/clojure.core/drop
const drop = (n, sequence) => seq(function* () {
if (sequence !== null && sequence !== void(0)) {
let count = n;
for (let item of sequence) {
if (count > 0)
count = count - 1;
else
yield item;
}
}
});
exports.drop = drop;
// Returns a lazy sequence of the items in sequence starting from the
// first item for which `p(item)` returns falsy value.
//
// Implements clojure drop-while:
// http://clojuredocs.org/clojure_core/clojure.core/drop-while
const dropWhile = (p, sequence) => seq(function* () {
let keep = false;
for (let item of sequence) {
keep = keep || !p(item);
if (keep) yield item;
}
});
exports.dropWhile = dropWhile;
// Returns a lazy sequence representing the concatenation of the
// suplied sequences.
//
// Implements clojure conact:
// http://clojuredocs.org/clojure_core/clojure.core/concat
const concat = (...sequences) => seq(function* () {
for (let sequence of sequences)
for (let item of sequence)
yield item;
});
exports.concat = concat;
// Returns the first item in the sequence.
//
// Implements clojure first:
// http://clojuredocs.org/clojure_core/clojure.core/first
const first = sequence => {
if (sequence !== null && sequence !== void(0)) {
for (let item of sequence)
return item;
}
return null;
};
exports.first = first;
// Returns a possibly empty sequence of the items after the first.
//
// Implements clojure rest:
// http://clojuredocs.org/clojure_core/clojure.core/rest
const rest = sequence => drop(1, sequence);
exports.rest = rest;
// Returns the value at the index. Returns `notFound` or `undefined`
// if index is out of bounds.
const nth = (xs, n, notFound) => {
if (n >= 0) {
if (isArray(xs) || isArguments(xs) || isString(xs)) {
return n < xs.length ? xs[n] : notFound;
}
else if (xs !== null && xs !== void(0)) {
let count = n;
for (let x of xs) {
if (count <= 0)
return x;
count = count - 1;
}
}
}
return notFound;
};
exports.nth = nth;
// Return the last item in sequence, in linear time.
// If `sequence` is an array or string or arguments
// returns in constant time.
// Implements clojure last:
// http://clojuredocs.org/clojure_core/clojure.core/last
const last = polymorphic({
null: _ => null,
void: _ => null,
indexed: indexed => indexed[indexed.length - 1],
map: xs => reduce((_, x) => x, xs),
set: xs => reduce((_, x) => x, xs),
default: xs => reduce((_, x) => x, xs)
});
exports.last = last;
// Return a lazy sequence of all but the last `n` (default 1) items
// from the give `xs`.
//
// Implements clojure drop-last:
// http://clojuredocs.org/clojure_core/clojure.core/drop-last
const dropLast = flip((xs, n=1) => seq(function* () {
let ys = [];
for (let x of xs) {
ys.push(x);
if (ys.length > n)
yield ys.shift();
}
}));
exports.dropLast = dropLast;
// Returns a lazy sequence of the elements of `xs` with duplicates
// removed
//
// Implements clojure distinct
// http://clojuredocs.org/clojure_core/clojure.core/distinct
const distinct = sequence => seq(function* () {
let items = new Set();
for (let item of sequence) {
if (!items.has(item)) {
items.add(item);
yield item;
}
}
});
exports.distinct = distinct;
// Returns a lazy sequence of the items in `xs` for which
// `p(x)` returns false. `p` must be free of side-effects.
//
// Implements clojure remove
// http://clojuredocs.org/clojure_core/clojure.core/remove
const remove = (p, xs) => filter(complement(p), xs);
exports.remove = remove;
// Returns the result of applying concat to the result of
// `map(f, xs)`. Thus function `f` should return a sequence.
//
// Implements clojure mapcat
// http://clojuredocs.org/clojure_core/clojure.core/mapcat
const mapcat = (f, sequence) => seq(function* () {
const sequences = map(f, sequence);
for (let sequence of sequences)
for (let item of sequence)
yield item;
});
exports.mapcat = mapcat;

View File

@ -26,14 +26,12 @@ usage = """
%prog [options] command [command-specific options]
Supported Commands:
docs - view web-based documentation
init - create a sample addon in an empty directory
test - run tests
run - run program
xpi - generate an xpi
Internal Commands:
sdocs - export static documentation
testcfx - test the cfx tool
testex - test all example code
testpkgs - test all installed packages
@ -223,11 +221,6 @@ parser_groups = (
metavar=None,
default=False,
cmds=['test', 'testex', 'testpkgs'])),
(("", "--override-version",), dict(dest="override_version",
help="Pass in a version string to use in generated docs",
metavar=None,
default=False,
cmds=['sdocs'])),
(("", "--check-memory",), dict(dest="check_memory",
help="attempts to detect leaked compartments after a test run",
action="store_true",
@ -238,6 +231,10 @@ parser_groups = (
help="Where to put the finished .xpi",
default=None,
cmds=['xpi'])),
(("", "--manifest-overload",), dict(dest="manifest_overload",
help="JSON file to overload package.json properties",
default=None,
cmds=['xpi'])),
]
),
@ -249,12 +246,6 @@ parser_groups = (
default=None,
cmds=['test', 'run', 'testex', 'testpkgs',
'testall'])),
(("", "--baseurl",), dict(dest="baseurl",
help=("root of static docs tree: "
"for example: 'http://me.com/the_docs/'"),
metavar=None,
default='',
cmds=['sdocs'])),
(("", "--test-runner-pkg",), dict(dest="test_runner_pkg",
help=("name of package "
"containing test runner "
@ -626,23 +617,6 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None,
return
test_cfx(env_root, options.verbose)
return
elif command == "docs":
from cuddlefish.docs import generate
if len(args) > 1:
docs_home = generate.generate_named_file(env_root, filename_and_path=args[1])
else:
docs_home = generate.generate_local_docs(env_root)
webbrowser.open(docs_home)
return
elif command == "sdocs":
from cuddlefish.docs import generate
filename=""
if options.override_version:
filename = generate.generate_static_docs(env_root, override_version=options.override_version)
else:
filename = generate.generate_static_docs(env_root)
print >>stdout, "Wrote %s." % filename
return
elif command not in ["xpi", "test", "run"]:
print >>sys.stderr, "Unknown command: %s" % command
print >>sys.stderr, "Try using '--help' for assistance."
@ -666,6 +640,10 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None,
target_cfg_json = os.path.join(options.pkgdir, 'package.json')
target_cfg = packaging.get_config_in_dir(options.pkgdir)
if options.manifest_overload:
for k, v in packaging.load_json_file(options.manifest_overload).items():
target_cfg[k] = v
# At this point, we're either building an XPI or running Jetpack code in
# a Mozilla application (which includes running tests).

View File

@ -0,0 +1,3 @@
{
"version": "1.0-nightly"
}

View File

@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import os, unittest, shutil
import zipfile
from StringIO import StringIO
from cuddlefish import initializer
from cuddlefish.templates import TEST_MAIN_JS, PACKAGE_JSON
@ -196,6 +197,21 @@ class TestCfxQuits(unittest.TestCase):
self.assertIn("1 of 1 tests passed.", err)
self.assertIn("Program terminated successfully.", err)
def test_cfx_xpi(self):
addon_path = os.path.join(tests_path,
"addons", "simplest-test")
rc, out, err = self.run_cfx(addon_path, \
["xpi", "--manifest-overload", "manifest-overload.json"])
self.assertEqual(rc, 0)
# Ensure that the addon version from our manifest overload is used
# in install.rdf
xpi_path = os.path.join(addon_path, "simplest-test.xpi")
xpi = zipfile.ZipFile(xpi_path, "r")
manifest = xpi.read("install.rdf")
self.assertIn("<em:version>1.0-nightly</em:version>", manifest)
xpi.close()
os.remove(xpi_path)
def test_cfx_init(self):
# Create an empty test directory
addon_path = os.path.abspath(os.path.join(".test_tmp", "test-cfx-init"))

View File

@ -0,0 +1,24 @@
/* 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';
const { Cc, Ci, Cu, Cm, components } = require('chrome');
const self = require('sdk/self');
const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {});
exports.testTranslators = function(assert, done) {
AddonManager.getAddonByID(self.id, function(addon) {
let count = 0;
addon.translators.forEach(function({ name }) {
count++;
assert.equal(name, 'Erik Vold', 'The translator keys are correct');
});
assert.equal(count, 1, 'The translator key count is correct');
assert.equal(addon.translators.length, 1, 'The translator key length is correct');
done();
});
}
require('sdk/test/runner').runTestsFromModule(module);

View File

@ -0,0 +1,6 @@
{
"id": "test-translators",
"translators": [
"Erik Vold"
]
}

View File

@ -6,6 +6,9 @@
<head>
<meta charset="UTF-8">
<title>Context menu test</title>
<style>
p { display: inline-block; }
</style>
</head>
<body>
<p>
@ -52,5 +55,27 @@
<p>
<input type="submit" id="button">
</p>
<p>
<a class="predicate-test-a" href="#test">
A link with no ID and an anchor, used by PredicateContext tests.
</a>
</p>
<p>
<input type="text" id="textbox" value="test value">
</p>
<p>
<input type="text" id="readonly-textbox" readonly="true" value="readonly value">
</p>
<p>
<input type="text" id="disabled-textbox" disabled="true" value="disabled value">
</p>
<p>
<p contenteditable="true" id="editable">This content is editable.</p>
</p>
</body>
</html>

View File

@ -3135,6 +3135,488 @@ exports.testSelectionInOuterFrameNoMatch = function (assert, done) {
});
};
// Test that the return value of the predicate function determines if
// item is shown
exports.testPredicateContextControl = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let itemTrue = loader.cm.Item({
label: "visible",
context: loader.cm.PredicateContext(function () { return true; })
});
let itemFalse = loader.cm.Item({
label: "hidden",
context: loader.cm.PredicateContext(function () { return false; })
});
test.showMenu(null, function (popup) {
test.checkMenu([itemTrue, itemFalse], [itemFalse], []);
test.done();
});
};
// Test that the data object has the correct document type
exports.testPredicateContextDocumentType = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.equal(data.documentType, 'text/html');
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(null, function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has the correct document URL
exports.testPredicateContextDocumentURL = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.equal(data.documentURL, TEST_DOC_URL);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(null, function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has the correct element name
exports.testPredicateContextTargetName = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.targetName, "input");
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("button"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has the correct ID
exports.testPredicateContextTargetIDSet = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.targetID, "button");
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("button"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has the correct ID
exports.testPredicateContextTargetIDNotSet = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.targetID, null);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementsByClassName("predicate-test-a")[0], function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object is showing editable correctly for regular text inputs
exports.testPredicateContextTextBoxIsEditable = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.isEditable, true);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("textbox"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object is showing editable correctly for readonly text inputs
exports.testPredicateContextReadonlyTextBoxIsNotEditable = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.isEditable, false);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("readonly-textbox"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object is showing editable correctly for disabled text inputs
exports.testPredicateContextDisabledTextBoxIsNotEditable = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.isEditable, false);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("disabled-textbox"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object is showing editable correctly for text areas
exports.testPredicateContextTextAreaIsEditable = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.isEditable, true);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("textfield"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that non-text inputs are not considered editable
exports.testPredicateContextButtonIsNotEditable = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.isEditable, false);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("button"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object is showing editable correctly
exports.testPredicateContextNonInputIsNotEditable = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.isEditable, false);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("image"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object is showing editable correctly for HTML contenteditable elements
exports.testPredicateContextEditableElement = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.isEditable, true);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("editable"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object does not have a selection when there is none
exports.testPredicateContextNoSelectionInPage = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.selectionText, null);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(null, function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object includes the selected page text
exports.testPredicateContextSelectionInPage = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
// since we might get whitespace
assert.ok(data.selectionText && data.selectionText.search(/^\s*Some text.\s*$/) != -1,
'Expected "Some text.", got "' + data.selectionText + '"');
return true;
})
})];
test.withTestDoc(function (window, doc) {
window.getSelection().selectAllChildren(doc.getElementById("text"));
test.showMenu(null, function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object includes the selected input text
exports.testPredicateContextSelectionInTextBox = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
// since we might get whitespace
assert.strictEqual(data.selectionText, "t v");
return true;
})
})];
test.withTestDoc(function (window, doc) {
let textbox = doc.getElementById("textbox");
textbox.focus();
textbox.setSelectionRange(3, 6);
test.showMenu(textbox, function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has the correct src for an image
exports.testPredicateContextTargetSrcSet = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let image;
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.srcURL, image.src);
return true;
})
})];
test.withTestDoc(function (window, doc) {
image = doc.getElementById("image");
test.showMenu(image, function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has no src for a link
exports.testPredicateContextTargetSrcNotSet = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.srcURL, null);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("link"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has the correct link set
exports.testPredicateContextTargetLinkSet = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let image;
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.linkURL, TEST_DOC_URL + "#test");
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementsByClassName("predicate-test-a")[0], function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has no link for an image
exports.testPredicateContextTargetLinkNotSet = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.linkURL, null);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("image"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has the value for an input textbox
exports.testPredicateContextTargetValueSet = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let image;
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.value, "test value");
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("textbox"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has no value for an image
exports.testPredicateContextTargetValueNotSet = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.value, null);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("image"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// NO TESTS BELOW THIS LINE! ///////////////////////////////////////////////////
// This makes it easier to run tests by handling things like opening the menu,

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@ const { getTabForWindow } = require('sdk/tabs/helpers');
const app = require("sdk/system/xul-app");
const { viewFor } = require("sdk/view/core");
const { getTabId } = require("sdk/tabs/utils");
const { defer } = require("sdk/lang/functional");
// The primary test tab
var primaryTab;
@ -139,14 +140,16 @@ exports["test behavior on close"] = function(assert, done) {
};
exports["test viewFor(tab)"] = (assert, done) => {
tabs.once("open", tab => {
// Note we defer handlers as length collection is updated after
// handler is invoked, so if test is finished before counnts are
// updated wrong length will show up in followup tests.
tabs.once("open", defer(tab => {
const view = viewFor(tab);
assert.ok(view, "view is returned");
assert.equal(getTabId(view), tab.id, "tab has a same id");
tab.close();
done();
});
tab.close(defer(done));
}));
tabs.open({ url: "about:mozilla" });
}

View File

@ -8,6 +8,8 @@ const { browserWindows } = require('sdk/windows');
const { viewFor } = require('sdk/view/core');
const { Ci } = require("chrome");
const { isBrowser, getWindowTitle } = require("sdk/window/utils");
const { defer } = require("sdk/lang/functional");
// TEST: browserWindows Iterator
exports.testBrowserWindowsIterator = function(assert) {
@ -30,6 +32,7 @@ exports.testBrowserWindowsIterator = function(assert) {
}
};
exports.testWindowTabsObject_alt = function(assert, done) {
let window = browserWindows.activeWindow;
window.tabs.open({
@ -58,6 +61,7 @@ exports.testWindowActivateMethod_simple = function(assert) {
'Active tab is active after window.activate() call');
};
exports["test getView(window)"] = function(assert, done) {
browserWindows.once("open", window => {
const view = viewFor(window);
@ -68,9 +72,11 @@ exports["test getView(window)"] = function(assert, done) {
"window has a right title");
window.close();
window.destroy();
assert.equal(viewFor(window), null, "window view is gone");
done();
// Defer handler cause window is destroyed after event is dispatched.
browserWindows.once("close", defer(_ => {
assert.equal(viewFor(window), null, "window view is gone");
done();
}));
});