Bug 947501 - Uplift Addon SDK to Firefox
authorErik Vold <evold@mozilla.com>
Thu, 19 Dec 2013 14:50:18 -0800
changeset 161377 f42afc1fab5034cbd08901b94c29239a612b3f55
parent 161376 fc063fb8efdcaef01ba3e8f34892622c31053b15
child 161378 599100c4ebfe11908fd26b394030e1cb970d2b88
child 161397 96816b5d72995dd8172e3132f9fe6bad6d211579
push id25878
push userkwierso@gmail.com
push dateFri, 20 Dec 2013 03:09:21 +0000
treeherdermozilla-central@599100c4ebfe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs947501
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 947501 - Uplift Addon SDK to Firefox
addon-sdk/source/lib/sdk/context-menu.js
addon-sdk/source/lib/sdk/deprecated/unit-test-finder.js
addon-sdk/source/lib/sdk/lang/type.js
addon-sdk/source/lib/sdk/selection.js
addon-sdk/source/lib/sdk/util/sequence.js
addon-sdk/source/python-lib/cuddlefish/__init__.py
addon-sdk/source/python-lib/cuddlefish/tests/addons/simplest-test/manifest-overload.json
addon-sdk/source/python-lib/cuddlefish/tests/test_init.py
addon-sdk/source/test/addons/translators/main.js
addon-sdk/source/test/addons/translators/package.json
addon-sdk/source/test/test-context-menu.html
addon-sdk/source/test/test-context-menu.js
addon-sdk/source/test/test-sequence.js
addon-sdk/source/test/test-tab.js
addon-sdk/source/test/test-windows-common.js
--- a/addon-sdk/source/lib/sdk/context-menu.js
+++ b/addon-sdk/source/lib/sdk/context-menu.js
@@ -19,16 +19,17 @@ const { URL, isValidURI } = require("./u
 const { WindowTracker, browserWindowIterator } = require("./deprecated/window-utils");
 const { isBrowser, getInnerId } = require("./window/utils");
 const { Ci } = require("chrome");
 const { MatchPattern } = require("./util/match-pattern");
 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";
 
 // Items in the top-level context menu also have this class.
 const TOPLEVEL_ITEM_CLASS = "addon-context-menu-item-toplevel";
 
 // Items in the overflow submenu also have this class.
@@ -199,16 +200,76 @@ let URLContext = Class({
 
   isCurrent: function isCurrent(popupNode) {
     let url = popupNode.ownerDocument.URL;
     return internal(this).patterns.some(function (p) p.test(url));
   }
 });
 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);
 }
 
 // Converts anything that isn't false, null or undefined into a string
 function stringOrNull(val) val ? String(val) : val;
 
 // Shared option validation rules for Item and Menu
--- a/addon-sdk/source/lib/sdk/deprecated/unit-test-finder.js
+++ b/addon-sdk/source/lib/sdk/deprecated/unit-test-finder.js
@@ -9,16 +9,17 @@ module.metadata = {
 };
 
 const file = require("../io/file");
 const memory = require('./memory');
 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) {
   memory.track(this);
   this.filter = options.filter;
   this.testInProcess = options.testInProcess === false ? false : true;
   this.testOutOfProcess = options.testOutOfProcess === true ? true : false;
 };
@@ -46,21 +47,20 @@ TestFinder.prototype = {
       filter = function(filename, testname) {
         return filterFileRegex.test(filename) &&
                ((testname && filterNameRegex) ? filterNameRegex.test(testname)
                                               : true);
       };
     } 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 {
           suiteModule = cuddlefish.main(loader, suite);
         }
         catch (e) {
           if (!/^Unsupported Application/.test(e.message))
             throw e;
--- a/addon-sdk/source/lib/sdk/lang/type.js
+++ b/addon-sdk/source/lib/sdk/lang/type.js
@@ -124,16 +124,22 @@ exports.isArray = isArray;
  *    (function(){ return isArguments(arguments); })(1, 2, 3); // true
  *    isArguments([1,2,3]); // false
  */
 function isArguments(value) {
   Object.prototype.toString.call(value) === "[object Arguments]";
 }
 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)
  * @examples
  *    isPrimitive(3) // true
  *    isPrimitive('foo') // true
  *    isPrimitive({ bar: 3 }) // false
  */
--- a/addon-sdk/source/lib/sdk/selection.js
+++ b/addon-sdk/source/lib/sdk/selection.js
@@ -310,24 +310,34 @@ function safeGetRange(selection, rangeNu
  *   for text selected in a form field (see bug 85686)
  */
 function getElementWithSelection() {
   let element = getFocusedElement();
 
   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;
+  }
+
 }
 
 /**
  * Adds the Selection Listener to the content's window given
  */
 function addSelectionListener(window) {
   let selection = window.getSelection();
 
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/util/sequence.js
@@ -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;
--- a/addon-sdk/source/python-lib/cuddlefish/__init__.py
+++ b/addon-sdk/source/python-lib/cuddlefish/__init__.py
@@ -21,24 +21,22 @@ on your system. Please specify one using
 
 UPDATE_RDF_FILENAME = "%s.update.rdf"
 XPI_FILENAME = "%s.xpi"
 
 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
   testall    - test whole environment
 
 Experimental and internal commands and options are not supported and may be
 changed or removed in the future.
 """
@@ -218,48 +216,41 @@ parser_groups = (
                                          default=[],
                                          cmds=['xpi'])),
         (("", "--stop-on-error",), dict(dest="stopOnError",
                                   help="Stop running tests after the first failure",
                                   action="store_true",
                                   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",
                                        default=False,
                                        cmds=['test', 'testpkgs', 'testaddons',
                                              'testall'])),
         (("", "--output-file",), dict(dest="output_file",
                                       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'])),
         ]
      ),
 
     ("Internal Command-Specific Options", [
         (("", "--addons",), dict(dest="addons",
                                  help=("paths of addons to install, "
                                        "comma-separated"),
                                  metavar=None,
                                  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 "
                                                 "program (default is "
                                                 "test-harness)"),
                                           default="addon-sdk",
                                           cmds=['test', 'testex', 'testpkgs',
                                                 'testall'])),
@@ -621,33 +612,16 @@ def run(arguments=sys.argv[1:], target_c
         test_all(env_root, defaults=options.__dict__)
         return
     elif command == "testcfx":
         if options.filter:
             print >>sys.stderr, "The filter option is not valid with the testcfx command"
             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."
         sys.exit(1)
 
     target_cfg_json = None
     if not target_cfg:
         if not options.pkgdir:
@@ -661,16 +635,20 @@ def run(arguments=sys.argv[1:], target_c
         if not os.path.exists(os.path.join(options.pkgdir, 'package.json')):
             print >>sys.stderr, ("cannot find 'package.json' in"
                                  " %s." % options.pkgdir)
             sys.exit(1)
 
         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).
 
     use_main = False
     inherited_options = ['verbose', 'enable_e10s', 'parseable', 'check_memory']
     enforce_timeouts = False
 
     if command == "xpi":
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/python-lib/cuddlefish/tests/addons/simplest-test/manifest-overload.json
@@ -0,0 +1,3 @@
+{
+  "version": "1.0-nightly"
+}
--- a/addon-sdk/source/python-lib/cuddlefish/tests/test_init.py
+++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_init.py
@@ -1,13 +1,14 @@
 # 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/.
 
 import os, unittest, shutil
+import zipfile
 from StringIO import StringIO
 from cuddlefish import initializer
 from cuddlefish.templates import TEST_MAIN_JS, PACKAGE_JSON
 
 tests_path = os.path.abspath(os.path.dirname(__file__))
 
 class TestInit(unittest.TestCase):
 
@@ -191,16 +192,31 @@ class TestCfxQuits(unittest.TestCase):
     def test_cfx_test(self):
         addon_path = os.path.join(tests_path,
                                   "addons", "simplest-test")
         rc, out, err = self.run_cfx(addon_path, ["test"])
         self.assertEqual(rc, 0)
         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"))
         if os.path.isdir(addon_path):
             shutil.rmtree(addon_path)
         os.makedirs(addon_path)
 
         # Fake a call to cfx init
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/translators/main.js
@@ -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);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/translators/package.json
@@ -0,0 +1,6 @@
+{
+  "id": "test-translators",
+  "translators": [
+    "Erik Vold"
+  ]
+}
--- a/addon-sdk/source/test/test-context-menu.html
+++ b/addon-sdk/source/test/test-context-menu.html
@@ -1,16 +1,19 @@
 <!-- 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/. -->
 
 <html>
   <head>
     <meta charset="UTF-8">
     <title>Context menu test</title>
+    <style>
+      p { display: inline-block; }
+    </style>
   </head>
   <body>
     <p>
       <img id="image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==">
     </p>
 
     <p>
       <a id="link" href="">
@@ -47,10 +50,32 @@
       <a id="targetlink" target="_blank" href="">
         A targetted link.
       </a>
     </p>
 
     <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>
--- a/addon-sdk/source/test/test-context-menu.js
+++ b/addon-sdk/source/test/test-context-menu.js
@@ -3130,16 +3130,498 @@ exports.testSelectionInOuterFrameNoMatch
 
     test.showMenu(frame.contentDocument.getElementById("text"), function (popup) {
       test.checkMenu(items, items, []);
       test.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,
 // opening new windows, making assertions, etc.  Methods on |test| can be called
 // on instances of this class.  Don't forget to call done() to end the test!
 // WARNING: This looks up items in popups by comparing labels, so don't give two
 // items the same label.
 function TestHelper(assert, done) {
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/test-sequence.js
@@ -0,0 +1,1163 @@
+/* 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";
+
+let { seq, iterate, filter, map, reductions, reduce, count,
+      isEmpty, every, isEvery, some, take, takeWhile, drop,
+      dropWhile, concat, first, rest, nth, last, dropLast,
+      distinct, remove, mapcat, fromEnumerator, string,
+      object, pairs, keys, values, each
+    } = require("sdk/util/sequence");
+
+const boom = () => { throw new Error("Boom!"); };
+const broken = seq(function*() {
+  yield 1;
+  throw new Error("Boom!");
+});
+
+exports["test seq"] = assert => {
+  let xs = seq(function*() {
+    yield 1;
+    yield 2;
+    yield 3;
+  });
+
+  assert.deepEqual([...seq(null)], [], "seq of null is empty");
+  assert.deepEqual([...seq(void(0))], [], "seq of void is empty");
+  assert.deepEqual([...xs], [1, 2, 3], "seq of 1 2 3");
+  assert.deepEqual([...seq(xs)], [1, 2, 3], "seq of seq is seq");
+
+  assert.deepEqual([...seq([])], [], "seq of emtpy array is empty");
+  assert.deepEqual([...seq([1])], [1], "seq of lonly array is single element");
+  assert.deepEqual([...seq([1, 2, 3])], [1, 2, 3], "seq of array is it's elements");
+
+  assert.deepEqual([...seq("")], [], "seq of emtpy string is empty");
+  assert.deepEqual([...seq("o")], ["o"], "seq of char is single char seq");
+  assert.deepEqual([...seq("hello")], ["h", "e", "l", "l", "o"],
+                   "seq of string are chars");
+
+  assert.deepEqual([...seq(new Set())], [], "seq of emtpy set is empty");
+  assert.deepEqual([...seq(new Set([1]))], [1], "seq of lonely set is single");
+  assert.deepEqual([...seq(new Set([1, 2, 3]))], [1, 2, 3], "seq of lonely set is single");
+
+  assert.deepEqual([...seq(new Map())], [], "seq of emtpy map is empty");
+  assert.deepEqual([...seq(new Map([[1, 2]]))], [[1, 2]], "seq single mapping is that mapping");
+  assert.deepEqual([...seq(new Map([[1, 2], [3, 4], [5, 6]]))],
+                   [[1, 2], [3, 4], [5, 6]],
+                   "seq of map is key value mappings");
+
+  [function(){}, 1, /foo/, true].forEach(x => {
+    assert.throws(() => seq(x), "Type is not seq-able");
+  });
+
+  assert.throws(() => [...broken],
+                /Boom/,
+                "broken sequence errors propagate");
+};
+
+exports["test seq casting"] = assert => {
+  const xs = seq(function*() { yield 1; yield 2; yield 3; });
+  const ys = seq(function*() { yield 1; });
+  const zs = seq(function*() {});
+  const kvs = seq(function*() { yield ["a", 1]; yield ["b", 2]; });
+  const kv = seq(function*() { yield ["a", 1]; });
+
+  assert.deepEqual([...xs], [1, 2, 3], "cast to array");
+  assert.deepEqual([...ys], [1], "cast to of one element");
+  assert.deepEqual([...zs], [], "cast empty array");
+
+  assert.deepEqual(string(...xs), "123", "cast to string");
+  assert.deepEqual(string(...ys), "1", "cast to char");
+  assert.deepEqual(string(...zs), "", "cast to empty string");
+
+  assert.deepEqual(new Set(xs), new Set([1, 2, 3]),
+                   "cast to set of items");
+  assert.deepEqual(new Set(ys), new Set([1]),
+                   "cast to set of one item");
+  assert.deepEqual(new Set(zs), new Set(),
+                   "cast to set of one item");
+
+  assert.deepEqual(new Map(kvs), new Map([["a", 1], ["b", 2]]),
+                   "cast to map");
+  assert.deepEqual(new Map(kv), new Map([["a", 1]]),
+                   "cast to single mapping");
+  assert.deepEqual(new Map(zs), new Map(),
+                   "cast to empty map");
+
+  assert.deepEqual(object(...kvs), {a: 1, b: 2},
+                  "cast to object");
+  assert.deepEqual(object(...kv), {a: 1},
+                  "cast to single pair");
+  assert.deepEqual(object(...zs), {},
+                  "cast to empty object");
+};
+
+exports["test pairs"] = assert => {
+  assert.deepEqual([...pairs(null)], [], "pairs on null is empty");
+  assert.deepEqual([...pairs(void(0))], [], "pairs on void is empty");
+  assert.deepEqual([...pairs({})], [], "empty sequence");
+  assert.deepEqual([...pairs({a: 1})], [["a", 1]], "single pair");
+  assert.deepEqual([...pairs({a: 1, b: 2, c: 3})].sort(),
+                   [["a", 1], ["b", 2], ["c", 3]],
+                   "creates pairs");
+  let items = [];
+  for (let [key, value] of pairs({a: 1, b: 2, c: 3}))
+    items.push([key, value]);
+
+  assert.deepEqual(items.sort(),
+                   [["a", 1], ["b", 2], ["c", 3]],
+                   "for of works on pairs");
+
+
+  assert.deepEqual([...pairs([])], [], "pairs on empty array is empty");
+  assert.deepEqual([...pairs([1])], [[0, 1]], "pairs on array is [index, element]");
+  assert.deepEqual([...pairs([1, 2, 3])],
+                   [[0, 1], [1, 2], [2, 3]],
+                   "for arrays it pair of [index, element]");
+
+  assert.deepEqual([...pairs("")], [], "pairs on empty string is empty");
+  assert.deepEqual([...pairs("a")], [[0, "a"]], "pairs on char is [0, char]");
+  assert.deepEqual([...pairs("hello")],
+                   [[0, "h"], [1, "e"], [2, "l"], [3, "l"], [4, "o"]],
+                   "for strings it's pair of [index, char]");
+
+  assert.deepEqual([...pairs(new Map())],
+                   [],
+                   "pairs on empty map is empty");
+  assert.deepEqual([...pairs(new Map([[1, 3]]))],
+                   [[1, 3]],
+                   "pairs on single mapping single mapping");
+  assert.deepEqual([...pairs(new Map([[1, 2], [3, 4]]))],
+                   [[1, 2], [3, 4]],
+                   "pairs on map returs key vaule pairs");
+
+  assert.throws(() => pairs(new Set()),
+                "can't pair set");
+
+  assert.throws(() => pairs(4),
+                "can't pair number");
+
+  assert.throws(() => pairs(true),
+                "can't pair boolean");
+};
+
+exports["test keys"] = assert => {
+  assert.deepEqual([...keys(null)], [], "keys on null is empty");
+  assert.deepEqual([...keys(void(0))], [], "keys on void is empty");
+  assert.deepEqual([...keys({})], [], "empty sequence");
+  assert.deepEqual([...keys({a: 1})], ["a"], "single key");
+  assert.deepEqual([...keys({a: 1, b: 2, c: 3})].sort(),
+                   ["a", "b", "c"],
+                   "all keys");
+
+  let items = [];
+  for (let key of keys({a: 1, b: 2, c: 3}))
+    items.push(key);
+
+  assert.deepEqual(items.sort(),
+                   ["a", "b", "c"],
+                   "for of works on keys");
+
+
+  assert.deepEqual([...keys([])], [], "keys on empty array is empty");
+  assert.deepEqual([...keys([1])], [0], "keys on array is indexes");
+  assert.deepEqual([...keys([1, 2, 3])],
+                   [0, 1, 2],
+                   "keys on arrays returns indexes");
+
+  assert.deepEqual([...keys("")], [], "keys on empty string is empty");
+  assert.deepEqual([...keys("a")], [0], "keys on char is 0");
+  assert.deepEqual([...keys("hello")],
+                   [0, 1, 2, 3, 4],
+                   "keys on strings is char indexes");
+
+  assert.deepEqual([...keys(new Map())],
+                   [],
+                   "keys on empty map is empty");
+  assert.deepEqual([...keys(new Map([[1, 3]]))],
+                   [1],
+                   "keys on single mapping single mapping is single key");
+  assert.deepEqual([...keys(new Map([[1, 2], [3, 4]]))],
+                   [1, 3],
+                   "keys on map is keys from map");
+
+  assert.throws(() => keys(new Set()),
+                "can't keys set");
+
+  assert.throws(() => keys(4),
+                "can't keys number");
+
+  assert.throws(() => keys(true),
+                "can't keys boolean");
+};
+
+exports["test values"] = assert => {
+  assert.deepEqual([...values({})], [], "empty sequence");
+  assert.deepEqual([...values({a: 1})], [1], "single value");
+  assert.deepEqual([...values({a: 1, b: 2, c: 3})].sort(),
+                   [1, 2, 3],
+                   "all values");
+
+  let items = [];
+  for (let value of values({a: 1, b: 2, c: 3}))
+    items.push(value);
+
+  assert.deepEqual(items.sort(),
+                   [1, 2, 3],
+                   "for of works on values");
+
+  assert.deepEqual([...values([])], [], "values on empty array is empty");
+  assert.deepEqual([...values([1])], [1], "values on array elements");
+  assert.deepEqual([...values([1, 2, 3])],
+                   [1, 2, 3],
+                   "values on arrays returns elements");
+
+  assert.deepEqual([...values("")], [], "values on empty string is empty");
+  assert.deepEqual([...values("a")], ["a"], "values on char is char");
+  assert.deepEqual([...values("hello")],
+                   ["h", "e", "l", "l", "o"],
+                   "values on strings is chars");
+
+  assert.deepEqual([...values(new Map())],
+                   [],
+                   "values on empty map is empty");
+  assert.deepEqual([...values(new Map([[1, 3]]))],
+                   [3],
+                   "keys on single mapping single mapping is single key");
+  assert.deepEqual([...values(new Map([[1, 2], [3, 4]]))],
+                   [2, 4],
+                   "values on map is values from map");
+
+  assert.deepEqual([...values(new Set())], [], "values on empty set is empty");
+  assert.deepEqual([...values(new Set([1]))], [1], "values on set is it's items");
+  assert.deepEqual([...values(new Set([1, 2, 3]))],
+                   [1, 2, 3],
+                   "values on set is it's items");
+
+
+  assert.throws(() => values(4),
+                "can't values number");
+
+  assert.throws(() => values(true),
+                "can't values boolean");
+};
+
+exports["test fromEnumerator"] = assert => {
+  const { Cc, Ci } = require("chrome");
+  const { enumerateObservers,
+          addObserver,
+          removeObserver } = Cc["@mozilla.org/observer-service;1"].
+                               getService(Ci.nsIObserverService);
+
+
+  const topic = "sec:" + Math.random().toString(32).substr(2);
+  const [a, b, c] = [{wrappedJSObject: {}},
+                     {wrappedJSObject: {}},
+                     {wrappedJSObject: {}}];
+  const unwrap = x => x.wrappedJSObject;
+
+  [a, b, c].forEach(x => addObserver(x, topic, false));
+
+  const xs = fromEnumerator(() => enumerateObservers(topic));
+  const ys = map(unwrap, xs);
+
+  assert.deepEqual([...ys], [a, b, c].map(unwrap),
+                   "all observers are there");
+
+  removeObserver(b, topic);
+
+  assert.deepEqual([...ys], [a, c].map(unwrap),
+                   "b was removed");
+
+  removeObserver(a, topic);
+
+  assert.deepEqual([...ys], [c].map(unwrap),
+                   "a was removed");
+
+  removeObserver(c, topic);
+
+  assert.deepEqual([...ys], [],
+                   "c was removed, now empty");
+
+  addObserver(a, topic, false);
+
+  assert.deepEqual([...ys], [a].map(unwrap),
+                   "a was added");
+
+  removeObserver(a, topic);
+
+  assert.deepEqual([...ys], [].map(unwrap),
+                   "a was removed, now empty");
+
+};
+
+exports["test filter"] = assert => {
+  const isOdd = x => x % 2;
+  const odds = seq(function*() { yield 1; yield 3; yield 5; });
+  const evens = seq(function*() { yield 2; yield 4; yield 6; });
+  const mixed = seq(function*() {
+    yield 1;
+    yield 2;
+    yield 3;
+    yield 4;
+  });
+
+  assert.deepEqual([...filter(isOdd, mixed)], [1, 3],
+                   "filtered odds");
+  assert.deepEqual([...filter(isOdd, odds)], [1, 3, 5],
+                   "kept all");
+  assert.deepEqual([...filter(isOdd, evens)], [],
+                   "kept none");
+
+
+  let xs = filter(boom, mixed);
+  assert.throws(() => [...xs], /Boom/, "errors propagate");
+
+  assert.throws(() => [...filter(isOdd, broken)], /Boom/,
+                "sequence errors propagate");
+};
+
+exports["test filter array"] = assert => {
+  let isOdd = x => x % 2;
+  let xs = filter(isOdd, [1, 2, 3, 4]);
+  let ys = filter(isOdd, [1, 3, 5]);
+  let zs = filter(isOdd, [2, 4, 6]);
+
+  assert.deepEqual([...xs], [1, 3], "filteres odds");
+  assert.deepEqual([...ys], [1, 3, 5], "kept all");
+  assert.deepEqual([...zs], [], "kept none");
+  assert.ok(!Array.isArray(xs));
+};
+
+exports["test filter set"] = assert => {
+  let isOdd = x => x % 2;
+  let xs = filter(isOdd, new Set([1, 2, 3, 4]));
+  let ys = filter(isOdd, new Set([1, 3, 5]));
+  let zs = filter(isOdd, new Set([2, 4, 6]));
+
+  assert.deepEqual([...xs], [1, 3], "filteres odds");
+  assert.deepEqual([...ys], [1, 3, 5], "kept all");
+  assert.deepEqual([...zs], [], "kept none");
+};
+
+exports["test filter string"] = assert => {
+  let isUpperCase = x => x.toUpperCase() === x;
+  let xs = filter(isUpperCase, "aBcDe");
+  let ys = filter(isUpperCase, "ABC");
+  let zs = filter(isUpperCase, "abcd");
+
+  assert.deepEqual([...xs], ["B", "D"], "filteres odds");
+  assert.deepEqual([...ys], ["A", "B", "C"], "kept all");
+  assert.deepEqual([...zs], [], "kept none");
+};
+
+exports["test filter lazy"] = assert => {
+  const x = 1;
+  let y = 2;
+
+  const xy = seq(function*() { yield x; yield y; });
+  const isOdd = x => x % 2;
+  const actual = filter(isOdd, xy);
+
+  assert.deepEqual([...actual], [1], "only one odd number");
+  y = 3;
+  assert.deepEqual([...actual], [1, 3], "filter is lazy");
+};
+
+exports["test filter non sequences"] = assert => {
+  const False = _ => false;
+  assert.throws(() => [...filter(False, 1)],
+                "can't iterate number");
+  assert.throws(() => [...filter(False, {a: 1, b:2})],
+                "can't iterate object");
+};
+
+exports["test map"] = assert => {
+  let inc = x => x + 1;
+  let xs = seq(function*() { yield 1; yield 2; yield 3; });
+  let ys = map(inc, xs);
+
+  assert.deepEqual([...ys], [2, 3, 4], "incremented each item");
+
+  assert.deepEqual([...map(inc, null)], [], "mapping null is empty");
+  assert.deepEqual([...map(inc, void(0))], [], "mapping void is empty");
+  assert.deepEqual([...map(inc, new Set([1, 2, 3]))], [2, 3, 4], "maps set items");
+};
+
+exports["test map two inputs"] = assert => {
+  let sum = (x, y) => x + y;
+  let xs = seq(function*() { yield 1; yield 2; yield 3; });
+  let ys = seq(function*() { yield 4; yield 5; yield 6; });
+
+  let zs = map(sum, xs, ys);
+
+  assert.deepEqual([...zs], [5, 7, 9], "summed numbers");
+};
+
+exports["test map diff sized inputs"] = assert => {
+  let sum = (x, y) => x + y;
+  let xs = seq(function*() { yield 1; yield 2; yield 3; });
+  let ys = seq(function*() { yield 4; yield 5; yield 6; yield 7; yield 8; });
+
+  let zs = map(sum, xs, ys);
+
+  assert.deepEqual([...zs], [5, 7, 9], "summed numbers");
+  assert.deepEqual([...map(sum, ys, xs)], [5, 7, 9],
+                   "index of exhasting input is irrelevant");
+};
+
+exports["test map multi"] = assert => {
+  let sum = (x, y, z, w) => x + y + z + w;
+  let xs = seq(function*() { yield 1; yield 2; yield 3; yield 4; });
+  let ys = seq(function*() { yield 4; yield 5; yield 6; yield 7; yield 8; });
+  let zs = seq(function*() { yield 10; yield 11; yield 12; });
+  let ws = seq(function*() { yield 0; yield 20; yield 40; yield 60; });
+
+  let actual = map(sum, xs, ys, zs, ws);
+
+  assert.deepEqual([...actual], [15, 38, 61], "summed numbers");
+};
+
+exports["test map errors"] = assert => {
+  assert.deepEqual([...map(boom, [])], [],
+                   "won't throw if empty");
+
+  const xs = map(boom, [1, 2, 4]);
+
+  assert.throws(() => [...xs], /Boom/, "propagates errors");
+
+  assert.throws(() => [...map(x => x, broken)], /Boom/,
+                "sequence errors propagate");
+};
+
+exports["test reductions"] = assert => {
+  let sum = (...xs) => xs.reduce((x, y) => x + y, 0);
+
+  assert.deepEqual([...reductions(sum, [1, 1, 1, 1])],
+                   [1, 2, 3, 4],
+                   "works with arrays");
+  assert.deepEqual([...reductions(sum, 5, [1, 1, 1, 1])],
+                   [5, 6, 7, 8, 9],
+                   "array with initial");
+
+  assert.deepEqual([...reductions(sum, seq(function*() {
+    yield 1;
+    yield 2;
+    yield 3;
+  }))],
+  [1, 3, 6],
+  "works with sequences");
+
+  assert.deepEqual([...reductions(sum, 10, seq(function*() {
+    yield 1;
+    yield 2;
+    yield 3;
+  }))],
+  [10, 11, 13, 16],
+  "works with sequences");
+
+  assert.deepEqual([...reductions(sum, [])], [0],
+                   "invokes accumulator with no args");
+
+  assert.throws(() => [...reductions(boom, 1, [1])],
+                /Boom/,
+                "arg errors errors propagate");
+  assert.throws(() => [...reductions(sum, 1, broken)],
+                /Boom/,
+                "sequence errors propagate");
+};
+
+exports["test reduce"] = assert => {
+ let sum = (...xs) => xs.reduce((x, y) => x + y, 0);
+
+  assert.deepEqual(reduce(sum, [1, 2, 3, 4, 5]),
+                   15,
+                   "works with arrays");
+
+  assert.deepEqual(reduce(sum, seq(function*() {
+                     yield 1;
+                     yield 2;
+                     yield 3;
+                   })),
+                   6,
+                   "works with sequences");
+
+  assert.deepEqual(reduce(sum, 10, [1, 2, 3, 4, 5]),
+                   25,
+                   "works with array & initial");
+
+  assert.deepEqual(reduce(sum, 5, seq(function*() {
+                     yield 1;
+                     yield 2;
+                     yield 3;
+                   })),
+                   11,
+                   "works with sequences & initial");
+
+  assert.deepEqual(reduce(sum, []), 0, "reduce with no args");
+  assert.deepEqual(reduce(sum, "a", []), "a", "reduce with initial");
+  assert.deepEqual(reduce(sum, 1, [1]), 2, "reduce with single & initial");
+
+  assert.throws(() => [...reduce(boom, 1, [1])],
+                /Boom/,
+                "arg errors errors propagate");
+  assert.throws(() => [...reduce(sum, 1, broken)],
+                /Boom/,
+                "sequence errors propagate");
+};
+
+exports["test each"] = assert => {
+  const collect = xs => {
+    let result = [];
+    each((...etc) => result.push(...etc), xs);
+    return result;
+  };
+
+  assert.deepEqual(collect(null), [], "each ignores null");
+  assert.deepEqual(collect(void(0)), [], "each ignores void");
+
+  assert.deepEqual(collect([]), [], "each ignores empty");
+  assert.deepEqual(collect([1]), [1], "each works on single item arrays");
+  assert.deepEqual(collect([1, 2, 3, 4, 5]),
+                   [1, 2, 3, 4, 5],
+                   "works with arrays");
+
+  assert.deepEqual(collect(seq(function*() {
+                     yield 1;
+                     yield 2;
+                     yield 3;
+                   })),
+                   [1, 2, 3],
+                   "works with sequences");
+
+  assert.deepEqual(collect(""), [], "ignores empty strings");
+  assert.deepEqual(collect("a"), ["a"], "works on chars");
+  assert.deepEqual(collect("hello"), ["h", "e", "l", "l", "o"],
+                   "works on strings");
+
+  assert.deepEqual(collect(new Set()), [], "ignores empty sets");
+  assert.deepEqual(collect(new Set(["a"])), ["a"],
+                   "works on single item sets");
+  assert.deepEqual(collect(new Set([1, 2, 3])), [1, 2, 3],
+                   "works on muti item tests");
+
+  assert.deepEqual(collect(new Map()), [], "ignores empty maps");
+  assert.deepEqual(collect(new Map([["a", 1]])), [["a", 1]],
+                   "works on single mapping maps");
+  assert.deepEqual(collect(new Map([[1, 2], [3, 4], [5, 6]])),
+                   [[1, 2], [3, 4], [5, 6]],
+                   "works on muti mapping maps");
+
+  assert.throws(() => collect({}), "objects arn't supported");
+  assert.throws(() => collect(1), "numbers arn't supported");
+  assert.throws(() => collect(true), "booleas arn't supported");
+};
+
+exports["test count"] = assert => {
+  assert.equal(count(null), 0, "null counts to 0");
+  assert.equal(count(), 0, "undefined counts to 0");
+  assert.equal(count([]), 0, "empty array");
+  assert.equal(count([1, 2, 3]), 3, "non-empty array");
+  assert.equal(count(""), 0, "empty string");
+  assert.equal(count("hello"), 5, "non-empty string");
+  assert.equal(count(new Map()), 0, "empty map");
+  assert.equal(count(new Map([[1, 2], [2, 3]])), 2, "non-empty map");
+  assert.equal(count(new Set()), 0, "empty set");
+  assert.equal(count(new Set([1, 2, 3, 4])), 4, "non-empty set");
+  assert.equal(count(seq(function*() {})), 0, "empty sequence");
+  assert.equal(count(seq(function*() { yield 1; yield 2; })), 2,
+               "non-empty sequence");
+
+  assert.throws(() => count(broken),
+                /Boom/,
+                "sequence errors propagate");
+};
+
+exports["test isEmpty"] = assert => {
+  assert.equal(isEmpty(null), true, "null is empty");
+  assert.equal(isEmpty(), true, "undefined is empty");
+  assert.equal(isEmpty([]), true, "array is array");
+  assert.equal(isEmpty([1, 2, 3]), false, "array isn't empty");
+  assert.equal(isEmpty(""), true, "string is empty");
+  assert.equal(isEmpty("hello"), false, "non-empty string");
+  assert.equal(isEmpty(new Map()), true, "empty map");
+  assert.equal(isEmpty(new Map([[1, 2], [2, 3]])), false, "non-empty map");
+  assert.equal(isEmpty(new Set()), true, "empty set");
+  assert.equal(isEmpty(new Set([1, 2, 3, 4])), false , "non-empty set");
+  assert.equal(isEmpty(seq(function*() {})), true, "empty sequence");
+  assert.equal(isEmpty(seq(function*() { yield 1; yield 2; })), false,
+               "non-empty sequence");
+
+  assert.equal(isEmpty(broken), false, "hasn't reached error");
+};
+
+exports["test isEvery"] = assert => {
+  let isOdd = x => x % 2;
+  let isTrue = x => x === true;
+  let isFalse = x => x === false;
+
+  assert.equal(isEvery(isOdd, seq(function*() {
+    yield 1;
+    yield 3;
+    yield 5;
+  })), true, "all are odds");
+
+  assert.equal(isEvery(isOdd, seq(function*() {
+    yield 1;
+    yield 2;
+    yield 3;
+  })), false, "contains even");
+
+  assert.equal(isEvery(isTrue, seq(function*() {})), true, "true if empty");
+  assert.equal(isEvery(isFalse, seq(function*() {})), true, "true if empty");
+
+  assert.equal(isEvery(isTrue, null), true, "true for null");
+  assert.equal(isEvery(isTrue, undefined), true, "true for undefined");
+
+  assert.throws(() => isEvery(boom, [1, 2]),
+                /Boom/,
+                "arg errors errors propagate");
+  assert.throws(() => isEvery(x => true, broken),
+                /Boom/,
+                "sequence errors propagate");
+
+  assert.equal(isEvery(x => false, broken), false,
+              "hasn't reached error");
+};
+
+exports["test some"] = assert => {
+  let isOdd = x => x % 2;
+  let isTrue = x => x === true;
+  let isFalse = x => x === false;
+
+  assert.equal(some(isOdd, seq(function*() {
+    yield 2;
+    yield 4;
+    yield 6;
+  })), null, "all are even");
+
+  assert.equal(some(isOdd, seq(function*() {
+    yield 2;
+    yield 3;
+    yield 4;
+  })), true, "contains odd");
+
+  assert.equal(some(isTrue, seq(function*() {})), null,
+               "null if empty")
+  assert.equal(some(isFalse, seq(function*() {})), null,
+               "null if empty")
+
+  assert.equal(some(isTrue, null), null, "null for null");
+  assert.equal(some(isTrue, undefined), null, "null for undefined");
+
+  assert.throws(() => some(boom, [1, 2]),
+                /Boom/,
+                "arg errors errors propagate");
+  assert.throws(() => some(x => false, broken),
+                /Boom/,
+                "sequence errors propagate");
+
+  assert.equal(some(x => true, broken), true,
+              "hasn't reached error");
+};
+
+exports["test take"] = assert => {
+  let xs = seq(function*() {
+    yield 1;
+    yield 2;
+    yield 3;
+    yield 4;
+    yield 5;
+    yield 6;
+  });
+
+  assert.deepEqual([...take(3, xs)], [1, 2, 3], "took 3 items");
+  assert.deepEqual([...take(3, [1, 2, 3, 4, 5])], [1, 2, 3],
+                   "took 3 from array");
+
+  let ys = seq(function*() { yield 1; yield 2; });
+  assert.deepEqual([...take(3, ys)], [1, 2], "takes at max n");
+  assert.deepEqual([...take(3, [1, 2])], [1, 2],
+                   "takes at max n from arary");
+
+  let empty = seq(function*() {});
+  assert.deepEqual([...take(5, empty)], [], "nothing to take");
+
+  assert.throws(() => [...take(3, broken)],
+                /Boom/,
+                "sequence errors propagate");
+
+  assert.deepEqual([...take(1, broken)], [1],
+                   "hasn't reached error");
+};
+
+exports["test iterate"] = assert => {
+  let inc = x => x + 1;
+  let nums = iterate(inc, 0);
+
+  assert.deepEqual([...take(5, nums)], [0, 1, 2, 3, 4], "took 5");
+  assert.deepEqual([...take(10, nums)], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "took 10");
+
+  let xs = iterate(x => x * 3, 2);
+  assert.deepEqual([...take(4, xs)], [2, 6, 18, 54], "took 4");
+
+  assert.throws(() => [...iterate(boom, 0)],
+                /Boom/,
+                "function exceptions propagate");
+};
+
+exports["test takeWhile"] = assert => {
+  let isNegative = x => x < 0;
+  let xs = seq(function*() {
+    yield -2;
+    yield -1;
+    yield 0;
+    yield 1;
+    yield 2;
+    yield 3;
+  });
+
+  assert.deepEqual([...takeWhile(isNegative, xs)], [-2, -1],
+                   "took until 0");
+
+  let ys = seq(function*() {});
+  assert.deepEqual([...takeWhile(isNegative, ys)], [],
+                   "took none");
+
+  let zs = seq(function*() {
+    yield 0;
+    yield 1;
+    yield 2;
+    yield 3;
+  });
+
+  assert.deepEqual([...takeWhile(isNegative, zs)], [],
+                   "took none");
+
+  assert.throws(() => [...takeWhile(boom, zs)],
+                /Boom/,
+                "function errors errors propagate");
+  assert.throws(() => [...takeWhile(x => true, broken)],
+                /Boom/,
+                "sequence errors propagate");
+
+  assert.deepEqual([...takeWhile(x => false, broken)],
+                   [],
+                   "hasn't reached error");
+};
+
+exports["test drop"] = assert => {
+  let testDrop = xs => {
+    assert.deepEqual([...drop(2, xs)],
+                     [3, 4],
+                     "dropped two elements");
+
+    assert.deepEqual([...drop(1, xs)],
+                     [2, 3, 4],
+                     "dropped one");
+
+    assert.deepEqual([...drop(0, xs)],
+                     [1, 2, 3, 4],
+                     "dropped 0");
+
+    assert.deepEqual([...drop(-2, xs)],
+                     [1, 2, 3, 4],
+                     "dropped 0 on negative `n`");
+
+    assert.deepEqual([...drop(5, xs)],
+                     [],
+                     "dropped all items");
+  };
+
+  testDrop([1, 2, 3, 4]);
+  testDrop(seq(function*() {
+    yield 1;
+    yield 2;
+    yield 3;
+    yield 4;
+  }));
+
+  assert.throws(() => [...drop(1, broken)],
+                /Boom/,
+                "sequence errors propagate");
+};
+
+
+exports["test dropWhile"] = assert => {
+  let isNegative = x => x < 0;
+  let True = _ => true;
+  let False = _ => false;
+
+  let test = xs => {
+    assert.deepEqual([...dropWhile(isNegative, xs)],
+                     [0, 1, 2],
+                     "dropped negative");
+
+    assert.deepEqual([...dropWhile(True, xs)],
+                     [],
+                     "drop all");
+
+    assert.deepEqual([...dropWhile(False, xs)],
+                     [-2, -1, 0, 1, 2],
+                     "keep all");
+  };
+
+  test([-2, -1, 0, 1, 2]);
+  test(seq(function*() {
+    yield -2;
+    yield -1;
+    yield 0;
+    yield 1;
+    yield 2;
+  }));
+
+  assert.throws(() => [...dropWhile(boom, [1, 2, 3])],
+                /Boom/,
+                "function errors errors propagate");
+  assert.throws(() => [...dropWhile(x => true, broken)],
+                /Boom/,
+                "sequence errors propagate");
+};
+
+
+exports["test concat"] = assert => {
+  let test = (a, b, c, d) => {
+    assert.deepEqual([...concat()],
+                     [],
+                     "nothing to concat");
+    assert.deepEqual([...concat(a)],
+                     [1, 2, 3],
+                     "concat with nothing returns same as first");
+    assert.deepEqual([...concat(a, b)],
+                     [1, 2, 3, 4, 5],
+                     "concat items from both");
+    assert.deepEqual([...concat(a, b, a)],
+                     [1, 2, 3, 4, 5, 1, 2, 3],
+                     "concat itself");
+    assert.deepEqual([...concat(c)],
+                     [],
+                     "concat of empty is empty");
+    assert.deepEqual([...concat(a, c)],
+                     [1, 2, 3],
+                     "concat with empty");
+    assert.deepEqual([...concat(c, c, c)],
+                     [],
+                     "concat of empties is empty");
+    assert.deepEqual([...concat(c, b)],
+                     [4, 5],
+                     "empty can be in front");
+    assert.deepEqual([...concat(d)],
+                     [7],
+                     "concat singular");
+    assert.deepEqual([...concat(d, d)],
+                     [7, 7],
+                     "concat singulars");
+
+    assert.deepEqual([...concat(a, a, b, c, d, c, d, d)],
+                     [1, 2, 3, 1, 2, 3, 4, 5, 7, 7, 7],
+                     "many concats");
+
+    let ab = concat(a, b);
+    let abcd = concat(ab, concat(c, d));
+    let cdabcd = concat(c, d, abcd);
+
+    assert.deepEqual([...cdabcd],
+                     [7, 1, 2, 3, 4, 5, 7],
+                     "nested concats");
+  };
+
+  test([1, 2, 3],
+       [4, 5],
+       [],
+       [7]);
+
+  test(seq(function*() { yield 1; yield 2; yield 3; }),
+       seq(function*() { yield 4; yield 5; }),
+       seq(function*() { }),
+       seq(function*() { yield 7; }));
+
+  assert.throws(() => [...concat(broken, [1, 2, 3])],
+                /Boom/,
+                "function errors errors propagate");
+};
+
+
+exports["test first"] = assert => {
+  let test = (xs, empty) => {
+    assert.equal(first(xs), 1, "returns first");
+    assert.equal(first(empty), null, "returns null empty");
+  };
+
+  test("1234", "");
+  test([1, 2, 3], []);
+  test([1, 2, 3], null);
+  test([1, 2, 3], undefined);
+  test(seq(function*() { yield 1; yield 2; yield 3; }),
+       seq(function*() { }));
+  assert.equal(first(broken), 1, "did not reached error");
+};
+
+exports["test rest"] = assert => {
+  let test = (xs, x, nil) => {
+    assert.deepEqual([...rest(xs)], ["b", "c"],
+                     "rest items");
+    assert.deepEqual([...rest(x)], [],
+                     "empty when singular");
+    assert.deepEqual([...rest(nil)], [],
+                     "empty when empty");
+  };
+
+  test("abc", "a", "");
+  test(["a", "b", "c"], ["d"], []);
+  test(seq(function*() { yield "a"; yield "b"; yield "c"; }),
+       seq(function*() { yield "d"; }),
+       seq(function*() {}));
+  test(["a", "b", "c"], ["d"], null);
+  test(["a", "b", "c"], ["d"], undefined);
+
+  assert.throws(() => [...rest(broken)],
+                /Boom/,
+                "sequence errors propagate");
+};
+
+
+exports["test nth"] = assert => {
+  let notFound = {};
+  let test = xs => {
+    assert.equal(nth(xs, 0), "h", "first");
+    assert.equal(nth(xs, 1), "e", "second");
+    assert.equal(nth(xs, 5), void(0), "out of bound");
+    assert.equal(nth(xs, 5, notFound), notFound, "out of bound");
+    assert.equal(nth(xs, -1), void(0), "out of bound");
+    assert.equal(nth(xs, -1, notFound), notFound, "out of bound");
+    assert.equal(nth(xs, 4), "o", "5th");
+  };
+
+  let testEmpty = xs => {
+    assert.equal(nth(xs, 0), void(0), "no first in empty");
+    assert.equal(nth(xs, 5), void(0), "no 5th in empty");
+    assert.equal(nth(xs, 0, notFound), notFound, "notFound on out of bound");
+  };
+
+  test("hello");
+  test(["h", "e", "l", "l", "o"]);
+  test(seq(function*() {
+    yield "h";
+    yield "e";
+    yield "l";
+    yield "l";
+    yield "o";
+  }));
+  testEmpty(null);
+  testEmpty(undefined);
+  testEmpty([]);
+  testEmpty("");
+  testEmpty(seq(function*() {}));
+
+
+  assert.throws(() => nth(broken, 1),
+                /Boom/,
+                "sequence errors propagate");
+  assert.equal(nth(broken, 0), 1, "have not reached error");
+};
+
+
+exports["test last"] = assert => {
+  assert.equal(last(null), null, "no last in null");
+  assert.equal(last(void(0)), null, "no last in undefined");
+  assert.equal(last([]), null, "no last in []");
+  assert.equal(last(""), null, "no last in ''");
+  assert.equal(last(seq(function*() { })), null, "no last in empty");
+
+  assert.equal(last("hello"), "o", "last from string");
+  assert.equal(last([1, 2, 3]), 3, "last from array");
+  assert.equal(last([1]), 1, "last from singular");
+  assert.equal(last(seq(function*() {
+    yield 1;
+    yield 2;
+    yield 3;
+  })), 3, "last from sequence");
+
+  assert.throws(() => last(broken),
+                /Boom/,
+                "sequence errors propagate");
+};
+
+
+exports["test dropLast"] = assert => {
+  let test = xs => {
+    assert.deepEqual([...dropLast(xs)],
+                     [1, 2, 3, 4],
+                     "dropped last");
+    assert.deepEqual([...dropLast(0, xs)],
+                     [1, 2, 3, 4, 5],
+                     "dropped none on 0");
+    assert.deepEqual([...dropLast(-3, xs)],
+                     [1, 2, 3, 4, 5],
+                     "drop none on negative");
+    assert.deepEqual([...dropLast(3, xs)],
+                     [1, 2],
+                     "dropped given number");
+    assert.deepEqual([...dropLast(5, xs)],
+                     [],
+                     "dropped all");
+  };
+
+  let testEmpty = xs => {
+    assert.deepEqual([...dropLast(xs)],
+                     [],
+                     "nothing to drop");
+    assert.deepEqual([...dropLast(0, xs)],
+                     [],
+                     "dropped none on 0");
+    assert.deepEqual([...dropLast(-3, xs)],
+                     [],
+                     "drop none on negative");
+    assert.deepEqual([...dropLast(3, xs)],
+                     [],
+                     "nothing to drop");
+  };
+
+  test([1, 2, 3, 4, 5]);
+  test(seq(function*() {
+    yield 1;
+    yield 2;
+    yield 3;
+    yield 4;
+    yield 5;
+  }));
+  testEmpty([]);
+  testEmpty("");
+  testEmpty(seq(function*() {}));
+
+  assert.throws(() => [...dropLast(broken)],
+                /Boom/,
+                "sequence errors propagate");
+};
+
+
+exports["test distinct"] = assert => {
+  let test = (xs, message) => {
+    assert.deepEqual([...distinct(xs)],
+                     [1, 2, 3, 4, 5],
+                     message);
+  };
+
+  test([1, 2, 1, 3, 1, 4, 1, 5], "works with arrays");
+  test(seq(function*() {
+    yield 1;
+    yield 2;
+    yield 1;
+    yield 3;
+    yield 1;
+    yield 4;
+    yield 1;
+    yield 5;
+  }), "works with sequences");
+  test(new Set([1, 2, 1, 3, 1, 4, 1, 5]),
+       "works with sets");
+  test(seq(function*() {
+    yield 1;
+    yield 2;
+    yield 2;
+    yield 2;
+    yield 1;
+    yield 3;
+    yield 1;
+    yield 4;
+    yield 4;
+    yield 4;
+    yield 1;
+    yield 5;
+  }), "works with multiple repeatitions");
+  test([1, 2, 3, 4, 5], "work with distinct arrays");
+  test(seq(function*() {
+    yield 1;
+    yield 2;
+    yield 3;
+    yield 4;
+    yield 5;
+  }), "works with distinct seqs");
+};
+
+
+exports["test remove"] = assert => {
+  let isPositive = x => x > 0;
+  let test = xs => {
+    assert.deepEqual([...remove(isPositive, xs)],
+                     [-2, -1, 0],
+                     "removed positives");
+  };
+
+  test([1, -2, 2, -1, 3, 7, 0]);
+  test(seq(function*() {
+    yield 1;
+    yield -2;
+    yield 2;
+    yield -1;
+    yield 3;
+    yield 7;
+    yield 0;
+  }));
+
+  assert.throws(() => [...distinct(broken)],
+                /Boom/,
+                "sequence errors propagate");
+};
+
+
+exports["test mapcat"] = assert => {
+  let upto = n => seq(function* () {
+    let index = 0;
+    while (index < n) {
+      yield index;
+      index = index + 1;
+    }
+  });
+
+  assert.deepEqual([...mapcat(upto, [1, 2, 3, 4])],
+                   [0, 0, 1, 0, 1, 2, 0, 1, 2, 3],
+                   "expands given sequence");
+
+  assert.deepEqual([...mapcat(upto, [0, 1, 2, 0])],
+                   [0, 0, 1],
+                   "expands given sequence");
+
+  assert.deepEqual([...mapcat(upto, [0, 0, 0])],
+                   [],
+                   "expands given sequence");
+
+  assert.deepEqual([...mapcat(upto, [])],
+                   [],
+                   "nothing to expand");
+
+  assert.deepEqual([...mapcat(upto, null)],
+                   [],
+                   "nothing to expand");
+
+  assert.deepEqual([...mapcat(upto, void(0))],
+                   [],
+                   "nothing to expand");
+
+  let xs = seq(function*() {
+    yield 0;
+    yield 1;
+    yield 0;
+    yield 2;
+    yield 0;
+  });
+
+  assert.deepEqual([...mapcat(upto, xs)],
+                   [0, 0, 1],
+                   "expands given sequence");
+
+  assert.throws(() => [...mapcat(boom, xs)],
+                /Boom/,
+                "function errors errors propagate");
+  assert.throws(() => [...mapcat(upto, broken)],
+                /Boom/,
+                "sequence errors propagate");
+};
+
+require("sdk/test").run(exports);
--- a/addon-sdk/source/test/test-tab.js
+++ b/addon-sdk/source/test/test-tab.js
@@ -4,16 +4,17 @@
 
 
 const tabs = require("sdk/tabs"); // From addon-kit
 const windowUtils = require("sdk/deprecated/window-utils");
 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;
 
 // We have an auxiliary tab to test background tabs.
 var auxTab;
 
 // The window for the outer iframe in the primary test page
@@ -134,21 +135,23 @@ exports["test behavior on close"] = func
 
         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" });
 }
 
 require("test").run(exports);
--- a/addon-sdk/source/test/test-windows-common.js
+++ b/addon-sdk/source/test/test-windows-common.js
@@ -3,16 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 'use strict';
 
 const { Loader } = require('sdk/test/loader');
 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) {
   let activeWindowCount = 0;
   let windows = [];
   let i = 0;
   for each (let window in browserWindows) {
     if (window === browserWindows.activeWindow)
@@ -25,16 +27,17 @@ exports.testBrowserWindowsIterator = fun
   assert.equal(activeWindowCount, 1, 'activeWindow was found in the iterator');
 
   i = 0;
   for (let j in browserWindows) {
     assert.equal(j, i++, 'for (x in browserWindows) works');
   }
 };
 
+
 exports.testWindowTabsObject_alt = function(assert, done) {
   let window = browserWindows.activeWindow;
   window.tabs.open({
     url: 'data:text/html;charset=utf-8,<title>tab 2</title>',
     inBackground: true,
     onReady: function onReady(tab) {
       assert.equal(tab.title, 'tab 2', 'Correct new tab title');
       assert.notEqual(window.tabs.activeTab, tab, 'Correct active tab');
@@ -53,28 +56,31 @@ exports.testWindowActivateMethod_simple 
   window.activate();
 
   assert.equal(browserWindows.activeWindow, window,
                'Active window is active after window.activate() call');
   assert.equal(window.tabs.activeTab, tab,
                'Active tab is active after window.activate() call');
 };
 
+
 exports["test getView(window)"] = function(assert, done) {
   browserWindows.once("open", window => {
     const view = viewFor(window);
 
     assert.ok(view instanceof Ci.nsIDOMWindow, "view is a window");
     assert.ok(isBrowser(view), "view is a browser window");
     assert.equal(getWindowTitle(view), window.title,
                  "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();
+    }));
   });
 
 
   browserWindows.open({ url: "data:text/html,<title>yo</title>" });
 };
 
 require('sdk/test').run(exports);