aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.7/packages/api-utils/lib
diff options
context:
space:
mode:
Diffstat (limited to 'tools/addon-sdk-1.7/packages/api-utils/lib')
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/api-utils.js153
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/app-strings.js63
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/array.js69
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/base.js177
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/byte-streams.js102
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/channel.js46
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/collection.js108
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/content.js11
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/content/loader.js179
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/content/symbiont.js183
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/content/worker.js491
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/cortex.js109
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/cuddlefish.js302
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/dom/events.js136
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/dom/events/keys.js60
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/env!.js20
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/environment.js54
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/errors.js60
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/event/core.js147
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/event/target.js76
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/events.js178
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/events/assembler.js53
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/file.js192
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/find-tests.js5
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/frame/utils.js59
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/functional.js159
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/globals!.js93
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/hidden-frame.js166
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/httpd.js5166
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/keyboard/hotkeys.js107
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/keyboard/observer.js53
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/keyboard/utils.js186
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/l10n/locale.js122
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/l10n/plural-rules.js387
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/light-traits.js596
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/list.js114
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/match-pattern.js103
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/memory.js114
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/message-manager.js203
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/namespace.js43
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/observer-service.js175
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/passwords/utils.js101
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/plain-text-console.js82
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/preferences-service.js124
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/process.js76
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/promise.js219
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/querystring.js117
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/runtime.js15
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/sandbox.js46
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/self!.js48
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/system.js120
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/tab-browser.js727
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/tabs/events.js26
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/tabs/observer.js93
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/tabs/tab.js264
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/tabs/utils.js59
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/test.js108
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/test/assert.js331
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/text-streams.js240
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/timer.js78
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/traceback.js123
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/traits.js183
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/traits/core.js317
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/type.js340
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/unit-test-finder.js76
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/unit-test.js440
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/unload.js63
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/url.js113
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/utils/data.js71
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/utils/object.js46
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/utils/registry.js57
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/utils/thumbnail.js43
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/uuid.js13
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/window-utils.js278
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/window/utils.js110
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/windows/dom.js27
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/windows/loader.js120
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/windows/observer.js53
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/windows/tabs.js178
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/xhr.js150
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/xpcom.js207
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/lib/xul-app.js63
82 files changed, 16765 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/api-utils.js b/tools/addon-sdk-1.7/packages/api-utils/lib/api-utils.js
new file mode 100644
index 0000000..94d13b7
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/api-utils.js
@@ -0,0 +1,153 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 memory = require("api-utils/memory");
+// The possible return values of getTypeOf.
+const VALID_TYPES = [
+ "array",
+ "boolean",
+ "function",
+ "null",
+ "number",
+ "object",
+ "string",
+ "undefined",
+];
+
+/**
+ * Returns a function C that creates instances of privateCtor. C may be called
+ * with or without the new keyword. The prototype of each instance returned
+ * from C is C.prototype, and C.prototype is an object whose prototype is
+ * privateCtor.prototype. Instances returned from C will therefore be instances
+ * of both C and privateCtor. Additionally, the constructor of each instance
+ * returned from C is C.
+ *
+ * @param privateCtor
+ * A constructor.
+ * @return A function that makes new instances of privateCtor.
+ */
+exports.publicConstructor = function publicConstructor(privateCtor) {
+ function PublicCtor() {
+ let obj = { constructor: PublicCtor, __proto__: PublicCtor.prototype };
+ memory.track(obj, privateCtor.name);
+ privateCtor.apply(obj, arguments);
+ return obj;
+ }
+ PublicCtor.prototype = { __proto__: privateCtor.prototype };
+ return PublicCtor;
+};
+
+/**
+ * Returns a validated options dictionary given some requirements. If any of
+ * the requirements are not met, an exception is thrown.
+ *
+ * @param options
+ * An object, the options dictionary to validate. It's not modified.
+ * If it's null or otherwise falsey, an empty object is assumed.
+ * @param requirements
+ * An object whose keys are the expected keys in options. Any key in
+ * options that is not present in requirements is ignored. Each value
+ * in requirements is itself an object describing the requirements of
+ * its key. There are four optional keys in this object:
+ * map: A function that's passed the value of the key in options.
+ * map's return value is taken as the key's value in the final
+ * validated options, is, and ok. If map throws an exception
+ * it's caught and discarded, and the key's value is its value in
+ * options.
+ * is: An array containing any number of the typeof type names. If
+ * the key's value is none of these types, it fails validation.
+ * Arrays and null are identified by the special type names
+ * "array" and "null"; "object" will not match either. No type
+ * coercion is done.
+ * ok: A function that's passed the key's value. If it returns
+ * false, the value fails validation.
+ * msg: If the key's value fails validation, an exception is thrown.
+ * This string will be used as its message. If undefined, a
+ * generic message is used, unless is is defined, in which case
+ * the message will state that the value needs to be one of the
+ * given types.
+ * @return An object whose keys are those keys in requirements that are also in
+ * options and whose values are the corresponding return values of map
+ * or the corresponding values in options. Note that any keys not
+ * shared by both requirements and options are not in the returned
+ * object.
+ */
+exports.validateOptions = function validateOptions(options, requirements) {
+ options = options || {};
+ let validatedOptions = {};
+ let mapThrew = false;
+
+ for (let [key, req] in Iterator(requirements)) {
+ let [optsVal, keyInOpts] = (key in options) ?
+ [options[key], true] :
+ [undefined, false];
+ if (req.map) {
+ try {
+ optsVal = req.map(optsVal);
+ }
+ catch (err) {
+ mapThrew = true;
+ }
+ }
+ if (req.is) {
+ // Sanity check the caller's type names.
+ req.is.forEach(function (typ) {
+ if (VALID_TYPES.indexOf(typ) < 0) {
+ let msg = 'Internal error: invalid requirement type "' + typ + '".';
+ throw new Error(msg);
+ }
+ });
+ if (req.is.indexOf(getTypeOf(optsVal)) < 0)
+ throw requirementError(key, req);
+ }
+ if (req.ok && !req.ok(optsVal))
+ throw requirementError(key, req);
+
+ if (keyInOpts || (req.map && !mapThrew))
+ validatedOptions[key] = optsVal;
+ }
+
+ return validatedOptions;
+};
+
+exports.addIterator = function addIterator(obj, keysValsGenerator) {
+ obj.__iterator__ = function(keysOnly, keysVals) {
+ let keysValsIterator = keysValsGenerator.call(this);
+
+ // "for (.. in ..)" gets only keys, "for each (.. in ..)" gets values,
+ // and "for (.. in Iterator(..))" gets [key, value] pairs.
+ let index = keysOnly ? 0 : 1;
+ while (true)
+ yield keysVals ? keysValsIterator.next() : keysValsIterator.next()[index];
+ };
+};
+
+// Similar to typeof, except arrays and null are identified by "array" and
+// "null", not "object".
+let getTypeOf = exports.getTypeOf = function getTypeOf(val) {
+ let typ = typeof(val);
+ if (typ === "object") {
+ if (!val)
+ return "null";
+ if (Array.isArray(val))
+ return "array";
+ }
+ return typ;
+}
+
+// Returns a new Error with a nice message.
+function requirementError(key, requirement) {
+ let msg = requirement.msg;
+ if (!msg) {
+ msg = 'The option "' + key + '" ';
+ msg += requirement.is ?
+ "must be one of the following types: " + requirement.is.join(", ") :
+ "is invalid.";
+ }
+ return new Error(msg);
+}
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/app-strings.js b/tools/addon-sdk-1.7/packages/api-utils/lib/app-strings.js
new file mode 100644
index 0000000..c03ae7c
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/app-strings.js
@@ -0,0 +1,63 @@
+/* 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} = require("chrome");
+const apiUtils = require("./api-utils");
+
+/**
+ * A bundle of strings.
+ *
+ * @param url {String}
+ * the URL of the string bundle
+ */
+exports.StringBundle = apiUtils.publicConstructor(function StringBundle(url) {
+
+ let stringBundle = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(url);
+
+ this.__defineGetter__("url", function () url);
+
+ /**
+ * Get a string from the bundle.
+ *
+ * @param name {String}
+ * the name of the string to get
+ * @param args {array} [optional]
+ * an array of arguments that replace occurrences of %S in the string
+ *
+ * @returns {String} the value of the string
+ */
+ this.get = function strings_get(name, args) {
+ try {
+ if (args)
+ return stringBundle.formatStringFromName(name, args, args.length);
+ else
+ return stringBundle.GetStringFromName(name);
+ }
+ catch(ex) {
+ // f.e. "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE)
+ // [nsIStringBundle.GetStringFromName]"
+ throw new Error("String '" + name + "' could not be retrieved from the " +
+ "bundle due to an unknown error (it doesn't exist?).");
+ }
+ },
+
+ /**
+ * Iterate the strings in the bundle.
+ *
+ */
+ apiUtils.addIterator(
+ this,
+ function keysValsGen() {
+ let enumerator = stringBundle.getSimpleEnumeration();
+ while (enumerator.hasMoreElements()) {
+ let elem = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
+ yield [elem.key, elem.value];
+ }
+ }
+ );
+});
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/array.js b/tools/addon-sdk-1.7/packages/api-utils/lib/array.js
new file mode 100644
index 0000000..dadcfa6
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/array.js
@@ -0,0 +1,69 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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";
+
+/**
+ * Returns `true` if given `array` contain given `element` or `false`
+ * otherwise.
+ * @param {Array} array
+ * Target array.
+ * @param {Object|String|Number|Boolean} element
+ * Element being looked up.
+ * @returns {Boolean}
+ */
+var has = exports.has = function has(array, element) {
+ // shorter and faster equivalent of `array.indexOf(element) >= 0`
+ return !!~array.indexOf(element);
+};
+
+/**
+ * Adds given `element` to the given `array` if it does not contain it yet.
+ * `true` is returned if element was added otherwise `false` is returned.
+ * @param {Array} array
+ * Target array.
+ * @param {Object|String|Number|Boolean} element
+ * Element to be added.
+ * @returns {Boolean}
+ */
+var add = exports.add = function add(array, element) {
+ var result;
+ if ((result = !has(array, element)))
+ array.push(element);
+
+ return result;
+};
+
+/**
+ * Removes first occurrence of the given `element` from the given `array`. If
+ * `array` does not contain given `element` `false` is returned otherwise
+ * `true` is returned.
+ * @param {Array} array
+ * Target array.
+ * @param {Object|String|Number|Boolean} element
+ * Element to be removed.
+ * @returns {Boolean}
+ */
+exports.remove = function remove(array, element) {
+ var result;
+ if ((result = has(array, element)))
+ array.splice(array.indexOf(element), 1);
+
+ return result;
+};
+
+/**
+ * Produces a duplicate-free version of the given `array`.
+ * @param {Array} array
+ * Source array.
+ * @returns {Array}
+ */
+exports.unique = function unique(array) {
+ var value = [];
+ return array.forEach(function(element) {
+ add(value, element);
+ });
+ return value;
+};
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/base.js b/tools/addon-sdk-1.7/packages/api-utils/lib/base.js
new file mode 100644
index 0000000..63acabc
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/base.js
@@ -0,0 +1,177 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* 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";
+
+// Instead of inheriting from `Object.prototype` we copy all interesting
+// properties from it and then freeze. This way we can guarantee integrity
+// of components build on top.
+exports.Base = Object.freeze(Object.create(null, {
+ toString: { value: Object.prototype.toString },
+ toLocaleString: { value: Object.prototype.toLocaleString },
+ toSource: { value: Object.prototype.toSource },
+ valueOf: { value: Object.prototype.valueOf },
+ isPrototypeOf: { value: Object.prototype.isPrototypeOf },
+ /**
+ * Creates an object that inherits from `this` object (Analog of
+ * `new Object()`).
+ * @examples
+ *
+ * var Dog = Base.extend({
+ * bark: function bark() {
+ * return 'Ruff! Ruff!'
+ * }
+ * });
+ * var dog = Dog.new();
+ */
+ new: { value: function create() {
+ var object = Object.create(this);
+ object.initialize.apply(object, arguments);
+ return object;
+ }},
+ /**
+ * When new instance of the this prototype is created it's `initialize`
+ * method is called with all the arguments passed to the `new`. You can
+ * override `initialize` to set up an instance.
+ */
+ initialize: { value: function initialize() {
+ }},
+ /**
+ * Merges all the properties of the passed objects into `this` instance (This
+ * method can be used on instances only as prototype objects are frozen).
+ *
+ * If two or more argument objects have own properties with the same name,
+ * the property is overridden, with precedence from right to left, implying,
+ * that properties of the object on the left are overridden by a same named
+ * property of the object on the right.
+ *
+ * @examples
+ *
+ * var Pet = Dog.extend({
+ * initialize: function initialize(options) {
+ * // this.name = options.name -> would have thrown (frozen prototype)
+ * this.merge(options) // will override all properties.
+ * },
+ * call: function(name) {
+ * return this.name === name ? this.bark() : ''
+ * },
+ * name: null
+ * })
+ * var pet = Pet.new({ name: 'Benzy', breed: 'Labrador' })
+ * pet.call('Benzy') // 'Ruff! Ruff!'
+ */
+ merge: { value: function merge() {
+ var descriptor = {};
+ Array.prototype.forEach.call(arguments, function (properties) {
+ Object.getOwnPropertyNames(properties).forEach(function(name) {
+ descriptor[name] = Object.getOwnPropertyDescriptor(properties, name);
+ });
+ });
+ Object.defineProperties(this, descriptor);
+ return this;
+ }},
+ /**
+ * Takes any number of argument objects and returns frozen, composite object
+ * that inherits from `this` object and combines all of the own properties of
+ * the argument objects. (Objects returned by this function are frozen as
+ * they are intended to be used as types).
+ *
+ * If two or more argument objects have own properties with the same name,
+ * the property is overridden, with precedence from right to left, implying,
+ * that properties of the object on the left are overridden by a same named
+ * property of the object on the right.
+ * @examples
+ *
+ * // ## Object composition ##
+ *
+ * var HEX = Base.extend({
+ * hex: function hex() {
+ * return '#' + this.color;
+ * }
+ * })
+ *
+ * var RGB = Base.extend({
+ * red: function red() {
+ * return parseInt(this.color.substr(0, 2), 16);
+ * },
+ * green: function green() {
+ * return parseInt(this.color.substr(2, 2), 16);
+ * },
+ * blue: function blue() {
+ * return parseInt(this.color.substr(4, 2), 16);
+ * }
+ * })
+ *
+ * var CMYK = Base.extend(RGB, {
+ * black: function black() {
+ * var color = Math.max(Math.max(this.red(), this.green()), this.blue());
+ * return (1 - color / 255).toFixed(4);
+ * },
+ * cyan: function cyan() {
+ * var K = this.black();
+ * return (((1 - this.red() / 255).toFixed(4) - K) / (1 - K)).toFixed(4);
+ * },
+ * magenta: function magenta() {
+ * var K = this.black();
+ * return (((1 - this.green() / 255).toFixed(4) - K) / (1 - K)).toFixed(4);
+ * },
+ * yellow: function yellow() {
+ * var K = this.black();
+ * return (((1 - this.blue() / 255).toFixed(4) - K) / (1 - K)).toFixed(4);
+ * }
+ * })
+ *
+ * var Color = Base.extend(HEX, RGB, CMYK, {
+ * initialize: function Color(color) {
+ * this.color = color;
+ * }
+ * });
+ *
+ * // ## Prototypal inheritance ##
+ *
+ * var Pixel = Color.extend({
+ * initialize: function Pixel(x, y, hex) {
+ * Color.initialize.call(this, hex);
+ * this.x = x;
+ * this.y = y;
+ * },
+ * toString: function toString() {
+ * return this.x + ':' + this.y + '@' + this.hex();
+ * }
+ * });
+ *
+ * var pixel = Pixel.new(11, 23, 'CC3399')
+ * pixel.toString(); // 11:23@#CC3399
+ *
+ * pixel.red(); // 204
+ * pixel.green(); // 51
+ * pixel.blue(); // 153
+ *
+ * pixel.cyan(); // 0.0000
+ * pixel.magenta(); // 0.7500
+ * pixel.yellow(); // 0.2500
+ *
+ */
+ extend: { value: function extend() {
+ return Object.freeze(this.merge.apply(Object.create(this), arguments));
+ }}
+}));
+
+/**
+ * Function takes prototype object that implements `initialize` method, and
+ * returns `constructor` function (with correct prototype property), that can
+ * be used for simulating classes for given prototypes.
+ */
+exports.Class = Object.freeze(function Class(prototype) {
+ function constructor() {
+ var instance = Object.create(prototype);
+ prototype.initialize.apply(instance, arguments);
+ return instance;
+ }
+ return Object.freeze(Object.defineProperties(constructor, {
+ prototype: { value: prototype },
+ new: { value: constructor }
+ }));
+});
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/byte-streams.js b/tools/addon-sdk-1.7/packages/api-utils/lib/byte-streams.js
new file mode 100644
index 0000000..edc8407
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/byte-streams.js
@@ -0,0 +1,102 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * 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";
+
+exports.ByteReader = ByteReader;
+exports.ByteWriter = ByteWriter;
+
+const {Cc, Ci} = require("chrome");
+
+// This just controls the maximum number of bytes we read in at one time.
+const BUFFER_BYTE_LEN = 0x8000;
+
+function ByteReader(inputStream) {
+ const self = this;
+
+ let stream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ stream.setInputStream(inputStream);
+
+ let manager = new StreamManager(this, stream);
+
+ this.read = function ByteReader_read(numBytes) {
+ manager.ensureOpened();
+ if (typeof(numBytes) !== "number")
+ numBytes = Infinity;
+
+ let data = "";
+ let read = 0;
+ try {
+ while (true) {
+ let avail = stream.available();
+ let toRead = Math.min(numBytes - read, avail, BUFFER_BYTE_LEN);
+ if (toRead <= 0)
+ break;
+ data += stream.readBytes(toRead);
+ read += toRead;
+ }
+ }
+ catch (err) {
+ throw new Error("Error reading from stream: " + err);
+ }
+
+ return data;
+ };
+}
+
+function ByteWriter(outputStream) {
+ const self = this;
+
+ let stream = Cc["@mozilla.org/binaryoutputstream;1"].
+ createInstance(Ci.nsIBinaryOutputStream);
+ stream.setOutputStream(outputStream);
+
+ let manager = new StreamManager(this, stream);
+
+ this.write = function ByteWriter_write(str) {
+ manager.ensureOpened();
+ try {
+ stream.writeBytes(str, str.length);
+ }
+ catch (err) {
+ throw new Error("Error writing to stream: " + err);
+ }
+ };
+}
+
+
+// This manages the lifetime of stream, a ByteReader or ByteWriter. It defines
+// closed and close() on stream and registers an unload listener that closes
+// rawStream if it's still opened. It also provides ensureOpened(), which
+// throws an exception if the stream is closed.
+function StreamManager(stream, rawStream) {
+ const self = this;
+ this.rawStream = rawStream;
+ this.opened = true;
+
+ stream.__defineGetter__("closed", function stream_closed() {
+ return !self.opened;
+ });
+
+ stream.close = function stream_close() {
+ self.ensureOpened();
+ self.unload();
+ };
+
+ require("./unload").ensure(this);
+}
+
+StreamManager.prototype = {
+ ensureOpened: function StreamManager_ensureOpened() {
+ if (!this.opened)
+ throw new Error("The stream is closed and cannot be used.");
+ },
+ unload: function StreamManager_unload() {
+ this.rawStream.close();
+ this.opened = false;
+ }
+};
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/channel.js b/tools/addon-sdk-1.7/packages/api-utils/lib/channel.js
new file mode 100644
index 0000000..a888af8
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/channel.js
@@ -0,0 +1,46 @@
+/* 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/. */
+
+const { jetpackID } = require('@packaging');
+const { when } = require('./unload');
+
+// TODO: Create a bug report and remove this workaround once it's fixed.
+// Only function needs defined in the context of the message manager window
+// can be registered via `addMessageListener`.
+function listener(callee) {
+ return function listener() { return callee.apply(this, arguments); };
+}
+function messageListener(scope, callee) {
+ return scope ? scope.eval('(' + listener + ')')(callee) : callee
+}
+
+exports.channel = function channel(scope, messageManager, address, raw) {
+ address = jetpackID + ':' + address
+ return {
+ input: function input(next, stop) {
+ let listener = messageListener(scope, function onMessage(message) {
+ if (false === next(raw ? message : message.json) && listener) {
+ messageManager.removeMessageListener(address, listener);
+ listener = null;
+ }
+ });
+ messageManager.addMessageListener(address, listener);
+
+ // Bug 724433: do not leak `listener` on addon disabling
+ when(function () {
+ if (listener) {
+ messageManager.removeMessageListener(address, listener);
+ listener = null;
+ }
+ });
+ },
+ output: function output(data) {
+ messageManager.sendAsyncMessage(address, data);
+ },
+ sync: !messageManager.sendSyncMessage ? null : function sync(data) {
+ messageManager.sendSyncMessage(address, data);
+ }
+ };
+};
+
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/collection.js b/tools/addon-sdk-1.7/packages/api-utils/lib/collection.js
new file mode 100644
index 0000000..b9dd352
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/collection.js
@@ -0,0 +1,108 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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";
+
+exports.Collection = Collection;
+
+/**
+ * Adds a collection property to the given object. Setting the property to a
+ * scalar value empties the collection and adds the value. Setting it to an
+ * array empties the collection and adds all the items in the array.
+ *
+ * @param obj
+ * The property will be defined on this object.
+ * @param propName
+ * The name of the property.
+ * @param array
+ * If given, this will be used as the collection's backing array.
+ */
+exports.addCollectionProperty = function addCollProperty(obj, propName, array) {
+ array = array || [];
+ let publicIface = new Collection(array);
+
+ obj.__defineSetter__(propName, function (itemOrItems) {
+ array.splice(0, array.length);
+ publicIface.add(itemOrItems);
+ });
+
+ obj.__defineGetter__(propName, function () {
+ return publicIface;
+ });
+};
+
+/**
+ * A collection is ordered, like an array, but its items are unique, like a set.
+ *
+ * @param array
+ * The collection is backed by an array. If this is given, it will be
+ * used as the backing array. This way the caller can fully control the
+ * collection. Otherwise a new empty array will be used, and no one but
+ * the collection will have access to it.
+ */
+function Collection(array) {
+ array = array || [];
+
+ /**
+ * Provides iteration over the collection. Items are yielded in the order
+ * they were added.
+ */
+ this.__iterator__ = function Collection___iterator__() {
+ let items = array.slice();
+ for (let i = 0; i < items.length; i++)
+ yield items[i];
+ };
+
+ /**
+ * The number of items in the collection.
+ */
+ this.__defineGetter__("length", function Collection_get_length() {
+ return array.length;
+ });
+
+ /**
+ * Adds a single item or an array of items to the collection. Any items
+ * already contained in the collection are ignored.
+ *
+ * @param itemOrItems
+ * An item or array of items.
+ * @return The collection.
+ */
+ this.add = function Collection_add(itemOrItems) {
+ let items = toArray(itemOrItems);
+ for (let i = 0; i < items.length; i++) {
+ let item = items[i];
+ if (array.indexOf(item) < 0)
+ array.push(item);
+ }
+ return this;
+ };
+
+ /**
+ * Removes a single item or an array of items from the collection. Any items
+ * not contained in the collection are ignored.
+ *
+ * @param itemOrItems
+ * An item or array of items.
+ * @return The collection.
+ */
+ this.remove = function Collection_remove(itemOrItems) {
+ let items = toArray(itemOrItems);
+ for (let i = 0; i < items.length; i++) {
+ let idx = array.indexOf(items[i]);
+ if (idx >= 0)
+ array.splice(idx, 1);
+ }
+ return this;
+ };
+};
+
+function toArray(itemOrItems) {
+ let isArr = itemOrItems &&
+ itemOrItems.constructor &&
+ itemOrItems.constructor.name === "Array";
+ return isArr ? itemOrItems : [itemOrItems];
+}
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/content.js b/tools/addon-sdk-1.7/packages/api-utils/lib/content.js
new file mode 100644
index 0000000..a4acb94
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/content.js
@@ -0,0 +1,11 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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";
+
+exports.Loader = require('./content/loader').Loader;
+exports.Symbiont = require('./content/symbiont').Symbiont;
+exports.Worker = require('./content/worker').Worker;
+
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/content/loader.js b/tools/addon-sdk-1.7/packages/api-utils/lib/content/loader.js
new file mode 100644
index 0000000..62c3ad0
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/content/loader.js
@@ -0,0 +1,179 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { EventEmitter } = require('../events');
+const { validateOptions } = require('../api-utils');
+const { URL } = require('../url');
+const file = require('../file');
+
+const LOCAL_URI_SCHEMES = ['resource', 'data'];
+
+// Returns `null` if `value` is `null` or `undefined`, otherwise `value`.
+function ensureNull(value) {
+ return value == null ? null : value;
+}
+
+// map of property validations
+const valid = {
+ contentURL: {
+ ok: function (value) {
+ try {
+ URL(value);
+ }
+ catch(e) {
+ return false;
+ }
+ return true;
+ },
+ msg: 'The `contentURL` option must be a valid URL.'
+ },
+ contentScriptFile: {
+ is: ['undefined', 'null', 'string', 'array'],
+ map: ensureNull,
+ ok: function(value) {
+ if (value === null)
+ return true;
+
+ value = [].concat(value);
+
+ // Make sure every item is a local file URL.
+ return value.every(function (item) {
+ try {
+ return ~LOCAL_URI_SCHEMES.indexOf(URL(item).scheme);
+ }
+ catch(e) {
+ return false;
+ }
+ });
+
+ },
+ msg: 'The `contentScriptFile` option must be a local URL or an array of URLs.'
+ },
+ contentScript: {
+ is: ['undefined', 'null', 'string', 'array'],
+ map: ensureNull,
+ ok: function(value) {
+ return !Array.isArray(value) || value.every(
+ function(item) { return typeof item === 'string' }
+ );
+ },
+ msg: 'The `contentScript` option must be a string or an array of strings.'
+ },
+ contentScriptWhen: {
+ is: ['string'],
+ ok: function(value) { return ~['start', 'ready', 'end'].indexOf(value) },
+ map: function(value) {
+ return value || 'end';
+ },
+ msg: 'The `contentScriptWhen` option must be either "start", "ready" or "end".'
+ }
+};
+exports.validationAttributes = valid;
+
+/**
+ * Shortcut function to validate property with validation.
+ * @param {Object|Number|String} suspect
+ * value to validate
+ * @param {Object} validation
+ * validation rule passed to `api-utils`
+ */
+function validate(suspect, validation) validateOptions(
+ { $: suspect },
+ { $: validation }
+).$
+
+function Allow(script) ({
+ get script() script,
+ set script(value) script = !!value
+})
+
+/**
+ * Trait is intended to be used in some composition. It provides set of core
+ * properties and bounded validations to them. Trait is useful for all the
+ * compositions providing high level APIs for interaction with content.
+ * Property changes emit `"propertyChange"` events on instances.
+ */
+const Loader = EventEmitter.compose({
+ /**
+ * Permissions for the content, with the following keys:
+ * @property {Object} [allow = { script: true }]
+ * @property {Boolean} [allow.script = true]
+ * Whether or not to execute script in the content. Defaults to true.
+ */
+ get allow() this._allow || (this._allow = Allow(true)),
+ set allow(value) this.allow.script = value && value.script,
+ _allow: null,
+ /**
+ * The content to load. Either a string of HTML or a URL.
+ * @type {String}
+ */
+ get contentURL() this._contentURL,
+ set contentURL(value) {
+ value = validate(value, valid.contentURL);
+ if (this._contentURL != value) {
+ this._emit('propertyChange', {
+ contentURL: this._contentURL = value
+ });
+ }
+ },
+ _contentURL: null,
+ /**
+ * When to load the content scripts.
+ * Possible values are "end" (default), which loads them once all page
+ * contents have been loaded, "ready", which loads them once DOM nodes are
+ * ready (ie like DOMContentLoaded event), and "start", which loads them once
+ * the `window` object for the page has been created, but before any scripts
+ * specified by the page have been loaded.
+ * Property change emits `propertyChange` event on instance with this key
+ * and new value.
+ * @type {'start'|'ready'|'end'}
+ */
+ get contentScriptWhen() this._contentScriptWhen,
+ set contentScriptWhen(value) {
+ value = validate(value, valid.contentScriptWhen);
+ if (value !== this._contentScriptWhen) {
+ this._emit('propertyChange', {
+ contentScriptWhen: this._contentScriptWhen = value
+ });
+ }
+ },
+ _contentScriptWhen: 'end',
+ /**
+ * The URLs of content scripts.
+ * Property change emits `propertyChange` event on instance with this key
+ * and new value.
+ * @type {String[]}
+ */
+ get contentScriptFile() this._contentScriptFile,
+ set contentScriptFile(value) {
+ value = validate(value, valid.contentScriptFile);
+ if (value != this._contentScriptFile) {
+ this._emit('propertyChange', {
+ contentScriptFile: this._contentScriptFile = value
+ });
+ }
+ },
+ _contentScriptFile: null,
+ /**
+ * The texts of content script.
+ * Property change emits `propertyChange` event on instance with this key
+ * and new value.
+ * @type {String|undefined}
+ */
+ get contentScript() this._contentScript,
+ set contentScript(value) {
+ value = validate(value, valid.contentScript);
+ if (value != this._contentScript) {
+ this._emit('propertyChange', {
+ contentScript: this._contentScript = value
+ });
+ }
+ },
+ _contentScript: null
+});
+exports.Loader = Loader;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/content/symbiont.js b/tools/addon-sdk-1.7/packages/api-utils/lib/content/symbiont.js
new file mode 100644
index 0000000..e7c959c
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/content/symbiont.js
@@ -0,0 +1,183 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { Worker } = require('./worker');
+const { Loader } = require('./loader');
+const hiddenFrames = require('../hidden-frame');
+const observers = require('../observer-service');
+const unload = require('../unload');
+
+/**
+ * This trait is layered on top of `Worker` and in contrast to symbiont
+ * Worker constructor requires `content` option that represents content
+ * that will be loaded in the provided frame, if frame is not provided
+ * Worker will create hidden one.
+ */
+const Symbiont = Worker.resolve({
+ constructor: '_initWorker',
+ destroy: '_workerDestroy'
+ }).compose(Loader, {
+
+ /**
+ * The constructor requires all the options that are required by
+ * `require('content').Worker` with the difference that the `frame` option
+ * is optional. If `frame` is not provided, `contentURL` is expected.
+ * @param {Object} options
+ * @param {String} options.contentURL
+ * URL of a content to load into `this._frame` and create worker for.
+ * @param {Element} [options.frame]
+ * iframe element that is used to load `options.contentURL` into.
+ * if frame is not provided hidden iframe will be created.
+ */
+ constructor: function Symbiont(options) {
+ options = options || {};
+
+ if ('contentURL' in options)
+ this.contentURL = options.contentURL;
+ if ('contentScriptWhen' in options)
+ this.contentScriptWhen = options.contentScriptWhen;
+ if ('contentScriptFile' in options)
+ this.contentScriptFile = options.contentScriptFile;
+ if ('contentScript' in options)
+ this.contentScript = options.contentScript;
+ if ('allow' in options)
+ this.allow = options.allow;
+ if ('onError' in options)
+ this.on('error', options.onError);
+ if ('onMessage' in options)
+ this.on('message', options.onMessage);
+ if ('frame' in options) {
+ this._initFrame(options.frame);
+ }
+ else {
+ let self = this;
+ this._hiddenFrame = hiddenFrames.HiddenFrame({
+ onReady: function onFrame() {
+ self._initFrame(this.element);
+ }
+ });
+ hiddenFrames.add(this._hiddenFrame);
+ }
+
+ unload.ensure(this._public, "destroy");
+ },
+
+ destroy: function destroy() {
+ this._workerDestroy();
+ this._unregisterListener();
+ this._frame = null;
+ if (this._hiddenFrame) {
+ hiddenFrames.remove(this._hiddenFrame);
+ this._hiddenFrame = null;
+ }
+ },
+
+ /**
+ * XUL iframe or browser elements with attribute `type` being `content`.
+ * Used to create `ContentSymbiont` from.
+ * @type {nsIFrame|nsIBrowser}
+ */
+ _frame: null,
+
+ /**
+ * Listener to the `'frameReady"` event (emitted when `iframe` is ready).
+ * Removes listener, sets right permissions to the frame and loads content.
+ */
+ _initFrame: function _initFrame(frame) {
+ if (this._loadListener)
+ this._unregisterListener();
+
+ this._frame = frame;
+ frame.docShell.allowJavascript = this.allow.script;
+ frame.setAttribute("src", this._contentURL);
+
+ // Inject `addon` object in document if we load a document from
+ // one of our addon folder and if no content script are defined. bug 612726
+ let isDataResource =
+ typeof this._contentURL == "string" &&
+ this._contentURL.indexOf(require("@packaging").uriPrefix) == 0;
+ let hasContentScript =
+ (Array.isArray(this.contentScript) ? this.contentScript.length > 0
+ : !!this.contentScript) ||
+ (Array.isArray(this.contentScriptFile) ? this.contentScriptFile.length > 0
+ : !!this.contentScriptFile);
+ // If we have to inject `addon` we have to do it before document
+ // script execution, so during `start`:
+ this._injectInDocument = isDataResource && !hasContentScript;
+ if (this._injectInDocument)
+ this.contentScriptWhen = "start";
+
+ if ((frame.contentDocument.readyState == "complete" ||
+ (frame.contentDocument.readyState == "interactive" &&
+ this.contentScriptWhen != 'end' )) &&
+ frame.contentDocument.location == this._contentURL) {
+ // In some cases src doesn't change and document is already ready
+ // (for ex: when the user moves a widget while customizing toolbars.)
+ this._onInit();
+ return;
+ }
+
+ let self = this;
+
+ if ('start' == this.contentScriptWhen) {
+ this._loadEvent = 'start';
+ observers.add('document-element-inserted',
+ this._loadListener = function onStart(doc) {
+
+ let window = doc.defaultView;
+ if (window && window == frame.contentWindow) {
+ self._unregisterListener();
+ self._onInit();
+ }
+
+ });
+ return;
+ }
+
+ let eventName = 'end' == this.contentScriptWhen ? 'load' : 'DOMContentLoaded';
+ let self = this;
+ this._loadEvent = eventName;
+ frame.addEventListener(eventName,
+ this._loadListener = function _onReady(event) {
+
+ if (event.target != frame.contentDocument)
+ return;
+ self._unregisterListener();
+
+ self._onInit();
+
+ }, true);
+
+ },
+
+ /**
+ * Unregister listener that watchs for document being ready to be injected.
+ * This listener is registered in `Symbiont._initFrame`.
+ */
+ _unregisterListener: function _unregisterListener() {
+ if (!this._loadListener)
+ return;
+ if (this._loadEvent == "start") {
+ observers.remove('document-element-inserted', this._loadListener);
+ }
+ else {
+ this._frame.removeEventListener(this._loadEvent, this._loadListener,
+ true);
+ }
+ this._loadListener = null;
+ },
+
+ /**
+ * Called by Symbiont itself when the frame is ready to load
+ * content scripts according to contentScriptWhen. Overloaded by Panel.
+ */
+ _onInit: function () {
+ this._initWorker({ window: this._frame.contentWindow });
+ }
+
+});
+exports.Symbiont = Symbiont;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/content/worker.js b/tools/addon-sdk-1.7/packages/api-utils/lib/content/worker.js
new file mode 100644
index 0000000..c6f35d9
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/content/worker.js
@@ -0,0 +1,491 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { Trait } = require('../traits');
+const { EventEmitter, EventEmitterTrait } = require('../events');
+const { Ci, Cu, Cc } = require('chrome');
+const timer = require('../timer');
+const { URL } = require('../url');
+const unload = require('../unload');
+const observers = require('../observer-service');
+const { Cortex } = require('../cortex');
+const self = require("self");
+const { sandbox, evaluate, load } = require("../sandbox");
+const { merge } = require('../utils/object');
+
+const CONTENT_PROXY_URL = self.data.url("content-proxy.js");
+const CONTENT_WORKER_URL = self.data.url("worker.js");
+
+const JS_VERSION = '1.8';
+
+const ERR_DESTROYED =
+ "The page has been destroyed and can no longer be used.";
+
+/**
+ * This key is not exported and should only be used for proxy tests.
+ * The following `PRIVATE_KEY` is used in addon module scope in order to tell
+ * Worker API to expose `UNWRAP_ACCESS_KEY` in content script.
+ * This key allows test-content-proxy.js to unwrap proxy with valueOf:
+ * let xpcWrapper = proxyWrapper.valueOf(UNWRAP_ACCESS_KEY);
+ */
+const PRIVATE_KEY = {};
+
+
+const WorkerSandbox = EventEmitter.compose({
+
+ /**
+ * Emit a message to the worker content sandbox
+ */
+ emit: function emit() {
+ // First ensure having a regular array
+ // (otherwise, `arguments` would be mapped to an object by `stringify`)
+ let array = Array.slice(arguments);
+ // JSON.stringify is buggy with cross-sandbox values,
+ // it may return "{}" on functions. Use a replacer to match them correctly.
+ function replacer(k, v) {
+ return typeof v === "function" ? undefined : v;
+ }
+ // Ensure having an asynchronous behavior
+ let self = this;
+ timer.setTimeout(function () {
+ self._emitToContent(JSON.stringify(array, replacer));
+ }, 0);
+ },
+
+ /**
+ * Synchronous version of `emit`.
+ * /!\ Should only be used when it is strictly mandatory /!\
+ * Doesn't ensure passing only JSON values.
+ * Mainly used by context-menu in order to avoid breaking it.
+ */
+ emitSync: function emitSync() {
+ return this._emitToContent(Array.slice(arguments));
+ },
+
+ /**
+ * Tells if content script has at least one listener registered for one event,
+ * through `self.on('xxx', ...)`.
+ * /!\ Shouldn't be used. Implemented to avoid breaking context-menu API.
+ */
+ hasListenerFor: function hasListenerFor(name) {
+ return this._hasListenerFor(name);
+ },
+
+ /**
+ * Method called by the worker sandbox when it needs to send a message
+ */
+ _onContentEvent: function onContentEvent(args) {
+ // As `emit`, we ensure having an asynchronous behavior
+ let self = this;
+ timer.setTimeout(function () {
+ // We emit event to chrome/addon listeners
+ self._emit.apply(self, JSON.parse(args));
+ }, 0);
+ },
+
+ /**
+ * Configures sandbox and loads content scripts into it.
+ * @param {Worker} worker
+ * content worker
+ */
+ constructor: function WorkerSandbox(worker) {
+ this._addonWorker = worker;
+
+ // Ensure that `emit` has always the right `this`
+ this.emit = this.emit.bind(this);
+ this.emitSync = this.emitSync.bind(this);
+
+ // We receive a wrapped window, that may be an xraywrapper if it's content
+ let window = worker._window;
+ let proto = window;
+
+ // Instantiate trusted code in another Sandbox in order to prevent content
+ // script from messing with standard classes used by proxy and API code.
+ let apiSanbox = sandbox(window, { wantXrays: true });
+
+ // Build content proxies only if the document has a non-system principal
+ if (window.wrappedJSObject) {
+ apiSanbox.console = console;
+ // Execute the proxy code
+ load(apiSanbox, CONTENT_PROXY_URL);
+ // Get a reference of the window's proxy
+ proto = apiSanbox.create(window);
+ }
+
+ // Create the sandbox and bind it to window in order for content scripts to
+ // have access to all standard globals (window, document, ...)
+ let content = this._sandbox = sandbox(window, {
+ sandboxPrototype: proto,
+ wantXrays: true
+ });
+ merge(content, {
+ // We need "this === window === top" to be true in toplevel scope:
+ get window() content,
+ get top() content,
+ // Use the Greasemonkey naming convention to provide access to the
+ // unwrapped window object so the content script can access document
+ // JavaScript values.
+ // NOTE: this functionality is experimental and may change or go away
+ // at any time!
+ get unsafeWindow() window.wrappedJSObject
+ });
+
+ // Load trusted code that will inject content script API.
+ // We need to expose JS objects defined in same principal in order to
+ // avoid having any kind of wrapper.
+ load(apiSanbox, CONTENT_WORKER_URL);
+
+ // Then call `inject` method and communicate with this script
+ // by trading two methods that allow to send events to the other side:
+ // - `onEvent` called by content script
+ // - `result.emitToContent` called by addon script
+ let chromeAPI = {
+ timers: {
+ setTimeout: timer.setTimeout,
+ setInterval: timer.setInterval,
+ clearTimeout: timer.clearTimeout,
+ clearInterval: timer.clearInterval
+ }
+ };
+ let onEvent = this._onContentEvent.bind(this);
+ // `ContentWorker` is defined in CONTENT_WORKER_URL file
+ let result = apiSanbox.ContentWorker.inject(content, chromeAPI, onEvent);
+ this._emitToContent = result.emitToContent;
+ this._hasListenerFor = result.hasListenerFor;
+
+ // Handle messages send by this script:
+ let self = this;
+ // console.xxx calls
+ this.on("console", function consoleListener(kind) {
+ console[kind].apply(console, Array.slice(arguments, 1));
+ });
+
+ // self.postMessage calls
+ this.on("message", function postMessage(data) {
+ self._addonWorker._emit('message', data);
+ });
+
+ // self.port.emit calls
+ this.on("event", function portEmit(name, args) {
+ self._addonWorker._onContentScriptEvent.apply(self._addonWorker, arguments);
+ });
+
+ // Internal feature that is only used by SDK tests:
+ // Expose unlock key to content script context.
+ // See `PRIVATE_KEY` definition for more information.
+ if (apiSanbox && worker._expose_key)
+ content.UNWRAP_ACCESS_KEY = apiSanbox.UNWRAP_ACCESS_KEY;
+
+ // Inject `addon` global into target document if document is trusted,
+ // `addon` in document is equivalent to `self` in content script.
+ if (worker._injectInDocument) {
+ let win = window.wrappedJSObject ? window.wrappedJSObject : window;
+ Object.defineProperty(win, "addon", {
+ value: content.self
+ }
+ );
+ }
+
+ // The order of `contentScriptFile` and `contentScript` evaluation is
+ // intentional, so programs can load libraries like jQuery from script URLs
+ // and use them in scripts.
+ let contentScriptFile = ('contentScriptFile' in worker) ? worker.contentScriptFile
+ : null,
+ contentScript = ('contentScript' in worker) ? worker.contentScript : null;
+
+ if (contentScriptFile) {
+ if (Array.isArray(contentScriptFile))
+ this._importScripts.apply(this, contentScriptFile);
+ else
+ this._importScripts(contentScriptFile);
+ }
+ if (contentScript) {
+ this._evaluate(
+ Array.isArray(contentScript) ? contentScript.join(';\n') : contentScript
+ );
+ }
+ },
+ destroy: function destroy() {
+ this.emitSync("destroy");
+ this._sandbox = null;
+ this._addonWorker = null;
+ },
+
+ /**
+ * JavaScript sandbox where all the content scripts are evaluated.
+ * {Sandbox}
+ */
+ _sandbox: null,
+
+ /**
+ * Reference to the addon side of the worker.
+ * @type {Worker}
+ */
+ _addonWorker: null,
+
+ /**
+ * Evaluates code in the sandbox.
+ * @param {String} code
+ * JavaScript source to evaluate.
+ * @param {String} [filename='javascript:' + code]
+ * Name of the file
+ */
+ _evaluate: function(code, filename) {
+ try {
+ evaluate(this._sandbox, code, filename || 'javascript:' + code);
+ }
+ catch(e) {
+ this._addonWorker._emit('error', e);
+ }
+ },
+ /**
+ * Imports scripts to the sandbox by reading files under urls and
+ * evaluating its source. If exception occurs during evaluation
+ * `"error"` event is emitted on the worker.
+ * This is actually an analog to the `importScript` method in web
+ * workers but in our case it's not exposed even though content
+ * scripts may be able to do it synchronously since IO operation
+ * takes place in the UI process.
+ */
+ _importScripts: function _importScripts(url) {
+ let urls = Array.slice(arguments, 0);
+ for each (let contentScriptFile in urls) {
+ try {
+ let uri = URL(contentScriptFile);
+ if (uri.scheme === 'resource')
+ load(this._sandbox, String(uri));
+ else
+ throw Error("Unsupported `contentScriptFile` url: " + String(uri));
+ }
+ catch(e) {
+ this._addonWorker._emit('error', e);
+ }
+ }
+ }
+});
+
+/**
+ * Message-passing facility for communication between code running
+ * in the content and add-on process.
+ * @see https://jetpack.mozillalabs.com/sdk/latest/docs/#module/api-utils/content/worker
+ */
+const Worker = EventEmitter.compose({
+ on: Trait.required,
+ _removeAllListeners: Trait.required,
+
+ /**
+ * Sends a message to the worker's global scope. Method takes single
+ * argument, which represents data to be sent to the worker. The data may
+ * be any primitive type value or `JSON`. Call of this method asynchronously
+ * emits `message` event with data value in the global scope of this
+ * symbiont.
+ *
+ * `message` event listeners can be set either by calling
+ * `self.on` with a first argument string `"message"` or by
+ * implementing `onMessage` function in the global scope of this worker.
+ * @param {Number|String|JSON} data
+ */
+ postMessage: function postMessage(data) {
+ if (!this._contentWorker)
+ throw new Error(ERR_DESTROYED);
+ this._contentWorker.emit("message", data);
+ },
+
+ /**
+ * EventEmitter, that behaves (calls listeners) asynchronously.
+ * A way to send customized messages to / from the worker.
+ * Events from in the worker can be observed / emitted via
+ * worker.on / worker.emit.
+ */
+ get port() {
+ // We generate dynamically this attribute as it needs to be accessible
+ // before Worker.constructor gets called. (For ex: Panel)
+
+ // create an event emitter that receive and send events from/to the worker
+ let self = this;
+ this._port = EventEmitterTrait.create({
+ emit: function () self._emitEventToContent(Array.slice(arguments))
+ });
+
+ // expose wrapped port, that exposes only public properties:
+ // We need to destroy this getter in order to be able to set the
+ // final value. We need to update only public port attribute as we never
+ // try to access port attribute from private API.
+ delete this._public.port;
+ this._public.port = Cortex(this._port);
+ // Replicate public port to the private object
+ delete this.port;
+ this.port = this._public.port;
+
+ return this._port;
+ },
+
+ /**
+ * Same object than this.port but private API.
+ * Allow access to _emit, in order to send event to port.
+ */
+ _port: null,
+
+ /**
+ * Emit a custom event to the content script,
+ * i.e. emit this event on `self.port`
+ */
+ _emitEventToContent: function _emitEventToContent(args) {
+ // We need to save events that are emitted before the worker is
+ // initialized
+ if (!this._inited) {
+ this._earlyEvents.push(args);
+ return;
+ }
+
+ // We throw exception when the worker has been destroyed
+ if (!this._contentWorker) {
+ throw new Error(ERR_DESTROYED);
+ }
+
+ // Forward the event to the WorkerSandbox object
+ this._contentWorker.emit.apply(null, ["event"].concat(args));
+ },
+
+ // Is worker connected to the content worker sandbox ?
+ _inited: false,
+
+ // List of custom events fired before worker is initialized
+ get _earlyEvents() {
+ delete this._earlyEvents;
+ this._earlyEvents = [];
+ return this._earlyEvents;
+ },
+
+ constructor: function Worker(options) {
+ options = options || {};
+
+ if ('window' in options)
+ this._window = options.window;
+ if ('contentScriptFile' in options)
+ this.contentScriptFile = options.contentScriptFile;
+ if ('contentScript' in options)
+ this.contentScript = options.contentScript;
+ if ('onError' in options)
+ this.on('error', options.onError);
+ if ('onMessage' in options)
+ this.on('message', options.onMessage);
+ if ('onDetach' in options)
+ this.on('detach', options.onDetach);
+
+ // Internal feature that is only used by SDK unit tests.
+ // See `PRIVATE_KEY` definition for more information.
+ if ('exposeUnlockKey' in options && options.exposeUnlockKey === PRIVATE_KEY)
+ this._expose_key = true;
+
+ // Track document unload to destroy this worker.
+ // We can't watch for unload event on page's window object as it
+ // prevents bfcache from working:
+ // https://developer.mozilla.org/En/Working_with_BFCache
+ this._windowID = this._window.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).
+ currentInnerWindowID;
+ observers.add("inner-window-destroyed",
+ this._documentUnload = this._documentUnload.bind(this));
+
+ unload.ensure(this._public, "destroy");
+
+ // Ensure that worker._port is initialized for contentWorker to be able
+ // to send use event during WorkerSandbox(this)
+ this.port;
+
+ // will set this._contentWorker pointing to the private API:
+ this._contentWorker = WorkerSandbox(this);
+
+ // Mainly enable worker.port.emit to send event to the content worker
+ this._inited = true;
+
+ // Flush all events that have been fired before the worker is initialized.
+ this._earlyEvents.forEach((function (args) this._emitEventToContent(args)).
+ bind(this));
+ },
+
+ _documentUnload: function _documentUnload(subject, topic, data) {
+ let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ if (innerWinID != this._windowID) return false;
+ this._workerCleanup();
+ return true;
+ },
+
+ get url() {
+ // this._window will be null after detach
+ return this._window ? this._window.document.location.href : null;
+ },
+
+ get tab() {
+ if (this._window) {
+ let tab = require("../tabs/tab");
+ // this._window will be null after detach
+ return tab.getTabForWindow(this._window);
+ }
+ return null;
+ },
+
+ /**
+ * Tells content worker to unload itself and
+ * removes all the references from itself.
+ */
+ destroy: function destroy() {
+ this._workerCleanup();
+ this._removeAllListeners();
+ },
+
+ /**
+ * Remove all internal references to the attached document
+ * Tells _port to unload itself and removes all the references from itself.
+ */
+ _workerCleanup: function _workerCleanup() {
+ // maybe unloaded before content side is created
+ // As Symbiont call worker.constructor on document load
+ if (this._contentWorker)
+ this._contentWorker.destroy();
+ this._contentWorker = null;
+ this._window = null;
+ // This method may be called multiple times,
+ // avoid dispatching `detach` event more than once
+ if (this._windowID) {
+ this._windowID = null;
+ observers.remove("inner-window-destroyed", this._documentUnload);
+ this._earlyEvents.slice(0, this._earlyEvents.length);
+ this._emit("detach");
+ }
+ },
+
+ /**
+ * Receive an event from the content script that need to be sent to
+ * worker.port. Provide a way for composed object to catch all events.
+ */
+ _onContentScriptEvent: function _onContentScriptEvent() {
+ this._port._emit.apply(this._port, arguments);
+ },
+
+ /**
+ * Reference to the content side of the worker.
+ * @type {WorkerGlobalScope}
+ */
+ _contentWorker: null,
+
+ /**
+ * Reference to the window that is accessible from
+ * the content scripts.
+ * @type {Object}
+ */
+ _window: null,
+
+ /**
+ * Flag to enable `addon` object injection in document. (bug 612726)
+ * @type {Boolean}
+ */
+ _injectInDocument: false
+});
+exports.Worker = Worker;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/cortex.js b/tools/addon-sdk-1.7/packages/api-utils/lib/cortex.js
new file mode 100644
index 0000000..1c3a1b3
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/cortex.js
@@ -0,0 +1,109 @@
+/* vim:set ts=2 sw=2 sts=2
+ * 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";
+
+// `var` is being used in the module in order to make it reusable in
+// environments in which `let` and `const` is not yet supported.
+
+// Returns `object`'s property value, where `name` is a name of the property.
+function get(object, name) {
+ return object[name];
+}
+
+// Assigns `value` to the `object`'s property, where `name` is the name of the
+// property.
+function set(object, name, value) {
+ return object[name] = value;
+}
+
+/**
+ * Given an `object` containing a property with the given `name`, create
+ * a property descriptor that can be used to define alias/proxy properties
+ * on other objects. A change in the value of an alias will propagate
+ * to the aliased property and vice versa.
+ */
+function createAliasProperty(object, name) {
+ // Getting own property descriptor of an `object` for the given `name` as
+ // we are going to create proxy analog.
+ var property = Object.getOwnPropertyDescriptor(object, name);
+ var descriptor = {
+ configurable: property.configurable,
+ enumerable: property.enumerable,
+ alias: true
+ };
+
+ // If the original property has a getter and/or setter, bind a
+ // corresponding getter/setter in the alias descriptor to the original
+ // object, so the `this` object in the getter/setter is the original object
+ // rather than the alias.
+ if ("get" in property && property.get)
+ descriptor.get = property.get.bind(object);
+ if ("set" in property && property.set)
+ descriptor.set = property.set.bind(object);
+
+ // If original property was a value property.
+ if ("value" in property) {
+ // If original property is a method using it's `object` bounded copy.
+ if (typeof property.value === "function") {
+ descriptor.value = property.value.bind(object);
+ // Also preserving writability of the original property.
+ descriptor.writable = property.writable;
+ }
+
+ // If the original property was just a data property, we create proxy
+ // accessors using our custom get/set functions to propagate changes to the
+ // original `object` and vice versa.
+ else {
+ descriptor.get = get.bind(null, object, name);
+ descriptor.set = set.bind(null, object, name);
+ }
+ }
+ return descriptor;
+}
+
+// Defines property on `object` object with a name `alias` if given if not
+// defaults to `name` that represents an alias of `source[name]`. If aliased
+// property was an assessor or a method `this` pseudo-variable will be `source`
+// when invoked. If aliased property was a data property changes on any of the
+// aliases will propagate to the `source[name]` and also other way round.
+function defineAlias(source, target, name, alias) {
+ return Object.defineProperty(target, alias || name,
+ createAliasProperty(source, name));
+}
+
+/**
+ * Function takes any `object` and returns a proxy for its own public
+ * properties. By default properties are considered to be public if they don't
+ * start with `"_"`, but default behavior can be overridden if needed, by
+ * passing array of public property `names` as a second argument. By default
+ * returned object will be direct decedent of the given `object`'s prototype,
+ * but this can be overridden by passing third optional argument, that will be
+ * used as `prototype` instead.
+ * @param {Object} object
+ * Object to create cortex for.
+ * @param {String[]} [names]
+ * Optional array of public property names.
+ * @param {Object} [prototype]
+ * Optional argument that will be used as `prototype` of the returned object,
+ * if not provided `Object.getPrototypeOf(object)` is used instead.
+ */
+exports.Cortex = function Cortex(object, names, prototype) {
+ // Creating a cortex object from the given `prototype`, if one was not
+ // provided then `prototype` of a given `object` is used. This allows
+ // consumer to define expected behavior `instanceof`. In common case
+ // `prototype` argument can be omitted to preserve same behavior of
+ // `instanceof` as on original `object`.
+ var cortex = Object.create(prototype || Object.getPrototypeOf(object));
+ // Creating alias properties on the `cortex` object for all the own
+ // properties of the original `object` that are contained in `names` array.
+ // If `names` array is not provided then all the properties that don't
+ // start with `"_"` are aliased.
+ Object.getOwnPropertyNames(object).forEach(function (name) {
+ if ((!names && "_" !== name.charAt(0)) || (names && ~names.indexOf(name)))
+ defineAlias(object, cortex, name);
+ });
+ return cortex;
+}
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/cuddlefish.js b/tools/addon-sdk-1.7/packages/api-utils/lib/cuddlefish.js
new file mode 100644
index 0000000..ac01aee
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/cuddlefish.js
@@ -0,0 +1,302 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* 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/. */
+var EXPORTED_SYMBOLS = [ 'Loader' ];
+
+!function(exports) {
+
+"use strict";
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu,
+ results: Cr, manager: Cm } = Components;
+const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
+const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1'].
+ getService(Ci.mozIJSSubScriptLoader);
+
+const Sandbox = {
+ new: function ({ principal, prototype, name, existingSandbox }) {
+ let options = { sandboxPrototype: prototype || Sandbox.prototype,
+ wantXrays: Sandbox.wantXrays };
+ if (name)
+ options.sandboxName = name;
+ if (existingSandbox)
+ options.sameGroupAs = existingSandbox.sandbox;
+ let sandbox = Object.create(Sandbox, {
+ sandbox: { value: Cu.Sandbox(principal || Sandbox.principal, options) }
+ });
+ // There are few properties (dump, Iterator) that by default appear in
+ // sandboxes shadowing properties provided by a prototype. To workaround
+ // this we override all such properties by copying them directly to the
+ // sandbox.
+ Object.keys(prototype).forEach(function onEach(key) {
+ if (sandbox.sandbox[key] !== prototype[key])
+ sandbox.sandbox[key] = prototype[key];
+ });
+ return sandbox;
+ },
+ evaluate: function evaluate(source, uri, lineNumber) {
+ return Cu.evalInSandbox(
+ source,
+ this.sandbox,
+ this.version,
+ uri,
+ lineNumber || this.lineNumber
+ );
+ },
+ load: function load(uri) {
+ scriptLoader.loadSubScript(uri, this.sandbox, 'UTF-8');
+ },
+ merge: function merge(properties) {
+ Object.getOwnPropertyNames(properties).forEach(function(name) {
+ Object.defineProperty(this.sandbox, name,
+ Object.getOwnPropertyDescriptor(properties, name));
+ }, this);
+ },
+ principal: systemPrincipal,
+ version: '1.8',
+ lineNumber: 1,
+ wantXrays: false,
+ prototype: {}
+};
+
+// the Module object made available to CommonJS modules when they are
+// evaluated, along with 'exports' and 'uri'
+const Module = {
+ new: function(id, path, uri) {
+ let module = Object.create(this);
+
+ module.id = id;
+ module.path = path;
+ module.uri = uri;
+ module.exports = {};
+
+ return module;
+ },
+ // TODO: I'd like to remove this, it's not used adds complexity and does
+ // not has much adoption in commonjs either.
+ setExports: function setExports(exports) {
+ this.exports = exports;
+ }
+};
+
+const Loader = {
+ new: function (options) {
+ let loader = Object.create(Loader, {
+ // Manifest generated by a linker, containing map of module url's mapped
+ // to it's requirements, comes from harness-options.json
+ manifest: { value: options.manifest || {} },
+
+ // Following property may be passed in (usually for mocking purposes) in
+ // order to override default modules cache.
+ modules: { value: options.modules || Object.create(Loader.modules) },
+ globals: { value: options.globals || {} },
+
+ uriPrefix: { value: options.uriPrefix },
+
+ sandboxes: { value: {} }
+ });
+ loader.require = this.require.bind(loader, options.loader);
+
+ // some 'magic' modules, that have no corresponding .js file
+ loader.modules['@packaging'] = Object.freeze({
+ id: '@packaging',
+ exports: JSON.parse(JSON.stringify(options))
+ });
+ loader.modules['@loader'] = Object.freeze({
+ exports: Object.freeze({ Loader: Loader }),
+ id: '@loader'
+ });
+
+ // This special module defines globals which will be added to every
+ // module this loader creates
+ let globals = loader.require('api-utils/globals!');
+ Object.getOwnPropertyNames(globals).forEach(function(name) {
+ Object.defineProperty(loader.globals, name,
+ Object.getOwnPropertyDescriptor(globals, name));
+ });
+ // Freeze globals so that modules won't have a chance to mutate scope of
+ // other modules.
+ Object.freeze(globals);
+
+ // Override global `dump` so that it behaves same as in any other module (
+ // currently we override dump to write to a file instead of `stdout` so that
+ // python can read it on windows).
+ dump = globals.dump;
+
+ return Object.freeze(loader);
+ },
+ modules: {
+ 'chrome': Object.freeze({
+ exports: Object.freeze({
+ Cc: Cc,
+ CC: CC,
+ Ci: Ci,
+ Cu: Cu,
+ Cr: Cr,
+ Cm: Cm,
+ components: Components,
+ messageManager: 'addMessageListener' in exports ? exports : null
+ }),
+ id: 'chrome'
+ }),
+ 'self': function self(loader, requirer) {
+ return loader.require('api-utils/self!').create(requirer.path);
+ },
+ },
+
+ // populate a Module by evaluating the CommonJS module code in the sandbox
+ load: function load(module) {
+ let require = Loader.require.bind(this, module.path);
+ require.main = this.main;
+
+ // Get an existing module sandbox, if any, so we can reuse its compartment
+ // when creating the new one to reduce memory consumption.
+ let existingSandbox = [this.sandboxes[p] for (p in this.sandboxes)][0];
+
+ // XXX Always set "principal" to work around bug 705795, which generates
+ // 'reference to undefined property "principal"' warnings when the argument
+ // is deconstructed in the "new" function's parameter list.
+ // FIXME: stop setting "principal" once bug 705795 is fixed.
+ let sandbox = this.sandboxes[module.path] =
+ Sandbox.new({ principal: null,
+ prototype: this.globals,
+ name: module.uri,
+ existingSandbox: existingSandbox });
+ sandbox.merge({
+ require: require,
+ module: module,
+ exports: module.exports
+ });
+
+ sandbox.load(module.uri);
+
+ // Workaround for bug 674195. Freezing objects from other sandboxes fail,
+ // so we create descendant and freeze it instead.
+ if (typeof(module.exports) === 'object') {
+ module.exports = Object.prototype.isPrototypeOf(module.exports) ?
+ Object.freeze(module.exports) :
+ Object.freeze(Object.create(module.exports));
+ }
+ },
+
+ // this require() is the main entry point for regular CommonJS modules. The
+ // bind() in load (above) causes those modules to get a very restricted
+ // form of this require(): one which can only ever reference this one
+ // loader, and which always uses their URI as a "base" (so they're limited
+ // to their own manifest entries, and can't import anything off the
+ // manifest).
+ require: function require(base, id) {
+ let module, manifest = this.manifest[base], requirer = this.modules[base];
+
+ if (!id)
+ throw Error("you must provide a module name when calling require() from "
+ + (requirer && requirer.id), base, id);
+
+ // If we have a manifest for requirer, then all it's requirements have been
+ // registered by linker and we should have a `path` to the required module.
+ // Even pseudo-modules like 'chrome', 'self', '@packaging', and '@loader'
+ // have pseudo-paths: exactly those same names.
+ // details see: Bug-697422.
+ let requirement = manifest && manifest.requirements[id];
+ if (!requirement)
+ throw Error("Module: " + (requirer && requirer.id) + ' located at ' +
+ base + " has no authority to load: " + id);
+ let path = requirement.path;
+
+ if (path in this.modules) {
+ module = this.modules[path];
+ }
+ else {
+ let uri = this.uriPrefix + path;
+ module = this.modules[path] = Module.new(id, path, uri);
+ this.load(module);
+ Object.freeze(module);
+ }
+
+ // "magic" modules which have contents that depend upon who imports them
+ // (like "self") are expressed in the Loader's pre-populated 'modules'
+ // table as callable functions, which are given the reference to this
+ // Loader and a copy of the importer's URI
+ //
+ // TODO: Find a better way to implement `self`.
+ // Maybe something like require('self!path/to/data')
+ if (typeof(module) === 'function')
+ module = module(this, requirer);
+
+ return module.exports;
+ },
+
+ // process.process() will eventually cause a call to main() to be evaluated
+ // in the addon's context. This function loads and executes the addon's
+ // entry point module.
+ main: function main(id, path) {
+ try {
+ let uri = this.uriPrefix + path;
+ let module = this.modules[path] = Module.new(id, path, uri);
+ this.load(module); // this is where the addon's main.js finally runs
+ let program = Object.freeze(module).exports;
+
+ if (typeof(program.onUnload) === 'function')
+ this.require('api-utils/unload').when(program.onUnload);
+
+ if (program.main) {
+ let { exit, staticArgs } = this.require('api-utils/system');
+ let { loadReason } = this.require('@packaging');
+ program.main({ loadReason: loadReason, staticArgs: staticArgs },
+ { print: function($) dump($ + '\n'), quit: exit });
+ }
+ } catch (error) {
+ Cu.reportError(error);
+ if (this.globals.console) this.globals.console.exception(error);
+ throw error;
+ }
+ },
+
+ // This is the main entry-point: bootstrap.js calls this when the add-on is
+ // installed. The order of calls is a bit confusing, but here's what
+ // happens (in temporal order):
+ // * process.spawn creates a new XUL 'browser' element which will house the
+ // main addon code. When e10s is active, this uses a real separate OS
+ // process. When e10s is disabled, this element lives in the one original
+ // process. Either way, its API is the same.
+ // * Grab the channel named "require!" and attach a handler which will load
+ // modules (in the chrome process) when requested to by the addon
+ // process. This handler uses Loader.require to import the module, then
+ // calls the module's .initialize() function to connect a new channel.
+ // The remote caller winds up with a channel reference, which they can
+ // use to send messages to the newly loaded module. This is for e10s.
+ // * After the channel handler is attached, process.process() (invoked by
+ // process.spawn()) will use loadScript() to evaluate code in the
+ // 'browser' element (which is where the main addon code starts running),
+ // to do the following:
+ // * create a Loader, initialized with the same manifest and
+ // harness-options.json that we've got
+ // * invoke it's main() method, with the name and path of the addon's
+ // entry module (which comes from linker via harness-options.js, and is
+ // usually main.js). That executes main(), above.
+ // * main() loads the addon's main.js, which executes all top-level
+ // forms. If the module defines an "exports.main=" function, we invoke
+ // that too. This is where the addon finally gets to run.
+ spawn: function spawn(id, path) {
+ let loader = this;
+ let process = this.require('api-utils/process');
+ process.spawn(id, path)(function(addon) {
+ // Listen to `require!` channel's input messages from the add-on process
+ // and load modules being required.
+ addon.channel('require!').input(function({ requirer: { path }, id }) {
+ try {
+ Loader.require.call(loader, path, id).initialize(addon.channel(id));
+ } catch (error) {
+ this.globals.console.exception(error);
+ }
+ });
+ });
+ },
+ unload: function unload(reason, callback) {
+ this.require('api-utils/unload').send(reason, callback);
+ }
+};
+exports.Loader = Loader;
+
+}(this);
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/dom/events.js b/tools/addon-sdk-1.7/packages/api-utils/lib/dom/events.js
new file mode 100644
index 0000000..2168f42
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/dom/events.js
@@ -0,0 +1,136 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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";
+
+// Utility function that returns copy of the given `text` with last character
+// removed if it is `"s"`.
+function singularify(text) {
+ return text[text.length - 1] === "s" ? text.substr(0, text.length - 1) : text;
+}
+
+// Utility function that takes event type, argument is passed to
+// `document.createEvent` and returns name of the initializer method of the
+// given event. Please note that there are some event types whose initializer
+// methods can't be guessed by this function. For more details see following
+// link: https://developer.mozilla.org/En/DOM/Document.createEvent
+function getInitializerName(category) {
+ return "init" + singularify(category);
+}
+
+/**
+ * Registers an event `listener` on a given `element`, that will be called
+ * when events of specified `type` is dispatched on the `element`.
+ * @param {Element} element
+ * Dom element to register listener on.
+ * @param {String} type
+ * A string representing the
+ * [event type](https://developer.mozilla.org/en/DOM/event.type) to
+ * listen for.
+ * @param {Function} listener
+ * Function that is called whenever an event of the specified `type`
+ * occurs.
+ * @param {Boolean} capture
+ * If true, indicates that the user wishes to initiate capture. After
+ * initiating capture, all events of the specified type will be dispatched
+ * to the registered listener before being dispatched to any `EventTarget`s
+ * beneath it in the DOM tree. Events which are bubbling upward through
+ * the tree will not trigger a listener designated to use capture.
+ * See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow)
+ * for a detailed explanation.
+ */
+function on(element, type, listener, capture) {
+ // `capture` defaults to `false`.
+ capture = capture || false;
+ element.addEventListener(type, listener, capture);
+}
+exports.on = on;
+
+/**
+ * Registers an event `listener` on a given `element`, that will be called
+ * only once, next time event of specified `type` is dispatched on the
+ * `element`.
+ * @param {Element} element
+ * Dom element to register listener on.
+ * @param {String} type
+ * A string representing the
+ * [event type](https://developer.mozilla.org/en/DOM/event.type) to
+ * listen for.
+ * @param {Function} listener
+ * Function that is called whenever an event of the specified `type`
+ * occurs.
+ * @param {Boolean} capture
+ * If true, indicates that the user wishes to initiate capture. After
+ * initiating capture, all events of the specified type will be dispatched
+ * to the registered listener before being dispatched to any `EventTarget`s
+ * beneath it in the DOM tree. Events which are bubbling upward through
+ * the tree will not trigger a listener designated to use capture.
+ * See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow)
+ * for a detailed explanation.
+ */
+function once(element, type, listener, capture) {
+ on(element, type, function selfRemovableListener(event) {
+ removeListener(element, type, selfRemovableListener, capture);
+ listener.apply(this, arguments);
+ }, capture);
+}
+exports.once = once;
+
+/**
+ * Unregisters an event `listener` on a given `element` for the events of the
+ * specified `type`.
+ *
+ * @param {Element} element
+ * Dom element to unregister listener from.
+ * @param {String} type
+ * A string representing the
+ * [event type](https://developer.mozilla.org/en/DOM/event.type) to
+ * listen for.
+ * @param {Function} listener
+ * Function that is called whenever an event of the specified `type`
+ * occurs.
+ * @param {Boolean} capture
+ * If true, indicates that the user wishes to initiate capture. After
+ * initiating capture, all events of the specified type will be dispatched
+ * to the registered listener before being dispatched to any `EventTarget`s
+ * beneath it in the DOM tree. Events which are bubbling upward through
+ * the tree will not trigger a listener designated to use capture.
+ * See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow)
+ * for a detailed explanation.
+ */
+function removeListener(element, type, listener, capture) {
+ element.removeEventListener(type, listener, capture);
+}
+exports.removeListener = removeListener;
+
+/**
+ * Emits event of the specified `type` and `category` on the given `element`.
+ * Specified `settings` are used to initialize event before dispatching it.
+ * @param {Element} element
+ * Dom element to dispatch event on.
+ * @param {String} type
+ * A string representing the
+ * [event type](https://developer.mozilla.org/en/DOM/event.type).
+ * @param {Object} options
+ * Options object containing following properties:
+ * - `category`: String passed to the `document.createEvent`. Option is
+ * optional and defaults to "UIEvents".
+ * - `initializer`: If passed it will be used as name of the method used
+ * to initialize event. If omitted name will be generated from the
+ * `category` field by prefixing it with `"init"` and removing last
+ * character if it matches `"s"`.
+ * - `settings`: Array of settings that are forwarded to the event
+ * initializer after firs `type` argument.
+ * @see https://developer.mozilla.org/En/DOM/Document.createEvent
+ */
+function emit(element, type, { category, initializer, settings }) {
+ category = category || "UIEvents";
+ initializer = initializer || getInitializerName(category);
+ let document = element.ownerDocument;
+ let event = document.createEvent(category);
+ event[initializer].apply(event, [type].concat(settings));
+ element.dispatchEvent(event);
+};
+exports.emit = emit;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/dom/events/keys.js b/tools/addon-sdk-1.7/packages/api-utils/lib/dom/events/keys.js
new file mode 100644
index 0000000..53107ae
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/dom/events/keys.js
@@ -0,0 +1,60 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { emit } = require("../events");
+const { getCodeForKey, toJSON } = require("../../keyboard/utils");
+const { has } = require("../../array");
+const { isString } = require("../../type");
+
+const INITIALIZER = "initKeyEvent";
+const CATEGORY = "KeyboardEvent";
+
+function Options(options) {
+ if (!isString(options))
+ return options;
+
+ var { key, modifiers } = toJSON(options);
+ return {
+ key: key,
+ control: has(modifiers, "control"),
+ alt: has(modifiers, "alt"),
+ shift: has(modifiers, "shift"),
+ meta: has(modifiers, "meta")
+ };
+}
+
+var keyEvent = exports.keyEvent = function keyEvent(element, type, options) {
+
+ emit(element, type, {
+ initializer: INITIALIZER,
+ category: CATEGORY,
+ settings: [
+ !("bubbles" in options) || options.bubbles !== false,
+ !("cancelable" in options) || options.cancelable !== false,
+ "window" in options && options.window ? options.window : null,
+ "control" in options && !!options.control,
+ "alt" in options && !!options.alt,
+ "shift" in options && !!options.shift,
+ "meta" in options && !!options.meta,
+ getCodeForKey(options.key) || 0,
+ options.key.length === 1 ? options.key.charCodeAt(0) : 0
+ ]
+ });
+}
+
+exports.keyDown = function keyDown(element, options) {
+ keyEvent(element, "keydown", Options(options));
+};
+
+exports.keyUp = function keyUp(element, options) {
+ keyEvent(element, "keyup", Options(options));
+};
+
+exports.keyPress = function keyPress(element, options) {
+ keyEvent(element, "keypress", Options(options));
+};
+
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/env!.js b/tools/addon-sdk-1.7/packages/api-utils/lib/env!.js
new file mode 100644
index 0000000..f7144ad
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/env!.js
@@ -0,0 +1,20 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* 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/. */
+
+const { messageManager } = require("chrome");
+const { channel } = require("./channel");
+
+module.exports = function load(module) {
+ return {
+ require: function require(id) {
+ // Load required module on the chrome process.
+ channel(messageManager, messageManager, 'require!').sync({
+ requirer: module,
+ id: id
+ });
+ return channel(messageManager, messageManager, id);
+ }
+ };
+};
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/environment.js b/tools/addon-sdk-1.7/packages/api-utils/lib/environment.js
new file mode 100644
index 0000000..fd95c67
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/environment.js
@@ -0,0 +1,54 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* 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 } = require('chrome');
+const { get, set, exists } = Cc['@mozilla.org/process/environment;1'].
+ getService(Ci.nsIEnvironment);
+
+exports.env = Proxy.create({
+ // XPCOM does not provides a way to enumerate environment variables, so we
+ // just don't support enumeration.
+ getPropertyNames: function() [],
+ getOwnPropertyNames: function() [],
+ enumerate: function() [],
+ keys: function() [],
+ // We do not support freezing, cause it would make it impossible to set new
+ // environment variables.
+ fix: function() undefined,
+ // We present all environment variables as own properties of this object,
+ // so we just delegate this call to `getOwnPropertyDescriptor`.
+ getPropertyDescriptor: function(name) this.getOwnPropertyDescriptor(name),
+ // If environment variable with this name is defined, we generate proprety
+ // descriptor for it, otherwise fall back to `undefined` so that for consumer
+ // this property does not exists.
+ getOwnPropertyDescriptor: function(name) {
+ return !exists(name) ? undefined : {
+ value: get(name),
+ enumerable: false, // Non-enumerable as we don't support enumeration.
+ configurable: true, // Configurable as it may be deleted.
+ writable: true // Writable as we do support set.
+ }
+ },
+
+ // New environment variables can be defined just by defining properties
+ // on this object.
+ defineProperty: function(name, { value }) set(name, value),
+ delete: function(name) set(name, null),
+
+ // We present all properties as own, there for we just delegate to `hasOwn`.
+ has: function(name) this.hasOwn(name),
+ // We do support checks for existence of an environment variable, via `in`
+ // operator on this object.
+ hasOwn: function(name) exists(name),
+
+ // On property get / set we do read / write appropriate environment variables,
+ // please note though, that variables with names of standard object properties
+ // intentionally (so that this behaves as normal object) can not be
+ // read / set.
+ get: function(proxy, name) Object.prototype[name] || get(name) || undefined,
+ set: function(proxy, name, value) Object.prototype[name] || set(name, value)
+});
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/errors.js b/tools/addon-sdk-1.7/packages/api-utils/lib/errors.js
new file mode 100644
index 0000000..7029158
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/errors.js
@@ -0,0 +1,60 @@
+/* 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";
+
+function logToConsole(e) {
+ console.exception(e);
+}
+
+var catchAndLog = exports.catchAndLog = function(callback,
+ defaultResponse,
+ logException) {
+ if (!logException)
+ logException = logToConsole;
+
+ return function() {
+ try {
+ return callback.apply(this, arguments);
+ } catch (e) {
+ logException(e);
+ return defaultResponse;
+ }
+ };
+};
+
+exports.catchAndLogProps = function catchAndLogProps(object,
+ props,
+ defaultResponse,
+ logException) {
+ if (typeof(props) == "string")
+ props = [props];
+ props.forEach(
+ function(property) {
+ object[property] = catchAndLog(object[property],
+ defaultResponse,
+ logException);
+ });
+};
+
+/**
+ * Catch and return an exception while calling the callback. If the callback
+ * doesn't throw, return the return value of the callback in a way that makes it
+ * possible to distinguish between a return value and an exception.
+ *
+ * This function is useful when you need to pass the result of a call across
+ * a process boundary (across which exceptions don't propagate). It probably
+ * doesn't need to be factored out into this module, since it is only used by
+ * a single caller, but putting it here works around bug 625560.
+ */
+exports.catchAndReturn = function(callback) {
+ return function() {
+ try {
+ return { returnValue: callback.apply(this, arguments) };
+ }
+ catch (exception) {
+ return { exception: exception };
+ }
+ };
+};
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/event/core.js b/tools/addon-sdk-1.7/packages/api-utils/lib/event/core.js
new file mode 100644
index 0000000..ed2a0d4
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/event/core.js
@@ -0,0 +1,147 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 UNCAUGHT_ERROR = 'An error event was emitted for which there was no listener.';
+const BAD_LISTENER = 'The event listener must be a function.';
+
+const { ns } = require('../namespace');
+
+const event = ns();
+
+// Utility function to access given event `target` object's event listeners for
+// the specific event `type`. If listeners for this type does not exists they
+// will be created.
+const observers = function observers(target, type) {
+ let listeners = event(target);
+ return type in listeners ? listeners[type] : listeners[type] = [];
+};
+
+/**
+ * Registers an event `listener` that is called every time events of
+ * specified `type` is emitted on the given event `target`.
+ * @param {Object} target
+ * Event target object.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+function on(target, type, listener) {
+ if (typeof(listener) !== 'function')
+ throw new Error(BAD_LISTENER);
+
+ let listeners = observers(target, type);
+ if (!~listeners.indexOf(listener))
+ listeners.push(listener);
+}
+exports.on = on;
+
+/**
+ * Registers an event `listener` that is called only the next time an event
+ * of the specified `type` is emitted on the given event `target`.
+ * @param {Object} target
+ * Event target object.
+ * @param {String} type
+ * The type of the event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+function once(target, type, listener) {
+ on(target, type, function observer() {
+ off(target, type, observer);
+ listener.apply(target, arguments);
+ });
+}
+exports.once = once;
+
+/**
+ * Execute each of the listeners in order with the supplied arguments.
+ * All the exceptions that are thrown by listeners during the emit
+ * are caught and can be handled by listeners of 'error' event. Thrown
+ * exceptions are passed as an argument to an 'error' event listener.
+ * If no 'error' listener is registered exception will be logged into an
+ * error console.
+ * @param {Object} target
+ * Event target object.
+ * @param {String} type
+ * The type of event.
+ * @params {Object|Number|String|Boolean} message
+ * First argument that will be passed to listeners.
+ * @params {Object|Number|String|Boolean} ...
+ * More arguments that will be passed to listeners.
+ */
+function emit(target, type, message /*, ...*/) {
+ for each (let item in emit.lazy.apply(emit.lazy, arguments))
+ item;
+}
+
+/**
+ * This is very experimental feature that you should not use unless absolutely
+ * need it. Also it may be removed at any point without any further notice.
+ *
+ * Creates lazy iterator of return values of listeners. You can think of it
+ * as lazy array of return values of listeners for the `emit` with the given
+ * arguments.
+ */
+emit.lazy = function lazy(target, type, message /*, ...*/) {
+ let args = Array.slice(arguments, 2)
+ let listeners = observers(target, type).slice()
+ while (listeners.length) {
+ try {
+ yield listeners.shift().apply(target, args);
+ }
+ catch (error) {
+ // If exception is not thrown by a error listener and error listener is
+ // registered emit `error` event. Otherwise dump exception to the console.
+ if (type !== 'error' && observers(target, 'error').length)
+ emit(target, 'error', error);
+ else
+ console.exception(error);
+ }
+ }
+}
+exports.emit = emit;
+
+/**
+ * Removes an event `listener` for the given event `type` on the given event
+ * `target`. If no `listener` is passed removes all listeners of the given
+ * `type`. If `type` is not passed removes all the listeners of the given
+ * event `target`.
+ * @param {Object} target
+ * The event target object.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+function off(target, type, listener) {
+ let length = arguments.length;
+ if (length === 3) {
+ let listeners = observers(target, type);
+ let index = listeners.indexOf(listener);
+ if (~index)
+ listeners.splice(index, 1);
+ }
+ else if (length === 2) {
+ observers(target, type).splice(0);
+ }
+ else if (length === 1) {
+ let listeners = event(target);
+ Object.keys(listeners).forEach(function(type) delete listeners[type]);
+ }
+}
+exports.off = off;
+
+/**
+ * Returns a number of event listeners registered for the given event `type`
+ * on the given event `target`.
+ */
+function count(target, type) {
+ return observers(target, type).length;
+}
+exports.count = count;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/event/target.js b/tools/addon-sdk-1.7/packages/api-utils/lib/event/target.js
new file mode 100644
index 0000000..60b19d2
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/event/target.js
@@ -0,0 +1,76 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { on, once, off } = require('./core');
+const { method } = require('../functional');
+const { Base } = require('../base');
+
+const EVENT_TYPE_PATTERN = /^on([A-Z]\w+$)/;
+
+/**
+ * `EventTarget` is an exemplar for creating an objects that can be used to
+ * add / remove event listeners on them. Events on these objects may be emitted
+ * via `emit` function exported by 'event/core' module.
+ */
+const EventTarget = Base.extend({
+ /**
+ * Method initializes `this` event source. It goes through properties of a
+ * given `options` and registers listeners for the ones that look like an
+ * event listeners.
+ */
+ initialize: function initialize(options) {
+ options = options || {};
+ // Go through each property and registers event listeners for those
+ // that have a name matching following pattern (`onEventType`).
+ Object.keys(options).forEach(function onEach(key) {
+ let match = EVENT_TYPE_PATTERN.exec(key);
+ let type = match && match[1].toLowerCase();
+ let listener = options[key];
+
+ if (type && typeof(listener) === 'function')
+ this.on(type, listener);
+ }, this);
+ },
+ /**
+ * Registers an event `listener` that is called every time events of
+ * specified `type` are emitted.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ * @example
+ * worker.on('message', function (data) {
+ * console.log('data received: ' + data)
+ * })
+ */
+ on: method(on),
+ /**
+ * Registers an event `listener` that is called once the next time an event
+ * of the specified `type` is emitted.
+ * @param {String} type
+ * The type of the event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+ once: method(once),
+ /**
+ * Removes an event `listener` for the given event `type`.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+ removeListener: function removeListener(type, listener) {
+ // Note: We can't just wrap `off` in `method` as we do it for other methods
+ // cause skipping a second or third argument will behave very differently
+ // than intended. This way we make sure all arguments are passed and only
+ // one listener is removed at most.
+ off(this, type, listener);
+ }
+});
+exports.EventTarget = EventTarget;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/events.js b/tools/addon-sdk-1.7/packages/api-utils/lib/events.js
new file mode 100644
index 0000000..531a7a3
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/events.js
@@ -0,0 +1,178 @@
+/* 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 ERROR_TYPE = 'error',
+ UNCAUGHT_ERROR = 'An error event was dispatched for which there was'
+ + ' no listener.',
+ BAD_LISTENER = 'The event listener must be a function.';
+/**
+ * This object is used to create an `EventEmitter` that, useful for composing
+ * objects that emit events. It implements an interface like `EventTarget` from
+ * DOM Level 2, which is implemented by Node objects in implementations that
+ * support the DOM Event Model.
+ * @see http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget
+ * @see http://nodejs.org/api.html#EventEmitter
+ * @see http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/events/EventDispatcher.html
+ */
+const eventEmitter = {
+ /**
+ * Registers an event `listener` that is called every time events of
+ * specified `type` are emitted.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ * @example
+ * worker.on('message', function (data) {
+ * console.log('data received: ' + data)
+ * })
+ */
+ on: function on(type, listener) {
+ if ('function' !== typeof listener)
+ throw new Error(BAD_LISTENER);
+ let listeners = this._listeners(type);
+ if (0 > listeners.indexOf(listener))
+ listeners.push(listener);
+ // Use of `_public` is required by the legacy traits code that will go away
+ // once bug-637633 is fixed.
+ return this._public || this;
+ },
+
+ /**
+ * Registers an event `listener` that is called once the next time an event
+ * of the specified `type` is emitted.
+ * @param {String} type
+ * The type of the event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+ once: function once(type, listener) {
+ this.on(type, function selfRemovableListener() {
+ this.removeListener(type, selfRemovableListener);
+ listener.apply(this, arguments);
+ });
+ },
+
+ /**
+ * Unregister `listener` for the specified event type.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+ removeListener: function removeListener(type, listener) {
+ if ('function' !== typeof listener)
+ throw new Error(BAD_LISTENER);
+ let listeners = this._listeners(type),
+ index = listeners.indexOf(listener);
+ if (0 <= index)
+ listeners.splice(index, 1);
+ // Use of `_public` is required by the legacy traits code, that will go away
+ // once bug-637633 is fixed.
+ return this._public || this;
+ },
+
+ /**
+ * Hash of listeners on this EventEmitter.
+ */
+ _events: null,
+
+ /**
+ * Returns an array of listeners for the specified event `type`. This array
+ * can be manipulated, e.g. to remove listeners.
+ * @param {String} type
+ * The type of event.
+ */
+ _listeners: function listeners(type) {
+ let events = this._events || (this._events = {});
+ return events[type] || (events[type] = []);
+ },
+
+ /**
+ * Execute each of the listeners in order with the supplied arguments.
+ * Returns `true` if listener for this event was called, `false` if there are
+ * no listeners for this event `type`.
+ *
+ * All the exceptions that are thrown by listeners during the emit
+ * are caught and can be handled by listeners of 'error' event. Thrown
+ * exceptions are passed as an argument to an 'error' event listener.
+ * If no 'error' listener is registered exception will propagate to a
+ * caller of this method.
+ *
+ * **It's recommended to have a default 'error' listener in all the complete
+ * composition that in worst case may dump errors to the console.**
+ *
+ * @param {String} type
+ * The type of event.
+ * @params {Object|Number|String|Boolean}
+ * Arguments that will be passed to listeners.
+ * @returns {Boolean}
+ */
+ _emit: function _emit(type, event) {
+ let args = Array.slice(arguments);
+ // Use of `_public` is required by the legacy traits code that will go away
+ // once bug-637633 is fixed.
+ args.unshift(this._public || this);
+ return this._emitOnObject.apply(this, args);
+ },
+
+ /**
+ * A version of _emit that lets you specify the object on which listeners are
+ * called. This is a hack that is sometimes necessary when such an object
+ * (exports, for example) cannot be an EventEmitter for some reason, but other
+ * object(s) managing events for the object are EventEmitters. Once bug
+ * 577782 is fixed, this method shouldn't be necessary.
+ *
+ * @param {object} targetObj
+ * The object on which listeners will be called.
+ * @param {string} type
+ * The event name.
+ * @param {value} event
+ * The first argument to pass to listeners.
+ * @param {value} ...
+ * More arguments to pass to listeners.
+ * @returns {boolean}
+ */
+ _emitOnObject: function _emitOnObject(targetObj, type, event /* , ... */) {
+ let listeners = this._listeners(type).slice(0);
+ // If there is no 'error' event listener then throw.
+ if (type === ERROR_TYPE && !listeners.length)
+ console.exception(event);
+ if (!listeners.length)
+ return false;
+ let params = Array.slice(arguments, 2);
+ for each (let listener in listeners) {
+ try {
+ listener.apply(targetObj, params);
+ } catch(e) {
+ // Bug 726967: Ignore exceptions being throws while notifying the error
+ // in order to avoid infinite loops.
+ if (type !== ERROR_TYPE)
+ this._emit(ERROR_TYPE, e);
+ else
+ console.exception("Exception in error event listener " + e);
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Removes all the event listeners for the specified event `type`.
+ * @param {String} type
+ * The type of event.
+ */
+ _removeAllListeners: function _removeAllListeners(type) {
+ if (typeof type == "undefined") {
+ this._events = null;
+ return this;
+ }
+
+ this._listeners(type).splice(0);
+ return this;
+ }
+};
+exports.EventEmitter = require("./traits").Trait.compose(eventEmitter);
+exports.EventEmitterTrait = require('./light-traits').Trait(eventEmitter);
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/events/assembler.js b/tools/addon-sdk-1.7/packages/api-utils/lib/events/assembler.js
new file mode 100644
index 0000000..5d11be2
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/events/assembler.js
@@ -0,0 +1,53 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { Trait } = require("../light-traits");
+const { removeListener, on } = require("../dom/events");
+
+/**
+ * Trait may be used for building objects / composing traits that wish to handle
+ * multiple dom events from multiple event targets in one place. Event targets
+ * can be added / removed by calling `observe / ignore` methods. Composer should
+ * provide array of event types it wishes to handle as property
+ * `supportedEventsTypes` and function for handling all those events as
+ * `handleEvent` property.
+ */
+exports.DOMEventAssembler = Trait({
+ /**
+ * Function that is supposed to handle all the supported events (that are
+ * present in the `supportedEventsTypes`) from all the observed
+ * `eventTargets`.
+ * @param {Event} event
+ * Event being dispatched.
+ */
+ handleEvent: Trait.required,
+ /**
+ * Array of supported event names.
+ * @type {String[]}
+ */
+ supportedEventsTypes: Trait.required,
+ /**
+ * Adds `eventTarget` to the list of observed `eventTarget`s. Listeners for
+ * supported events will be registered on the given `eventTarget`.
+ * @param {EventTarget} eventTarget
+ */
+ observe: function observe(eventTarget) {
+ this.supportedEventsTypes.forEach(function(eventType) {
+ on(eventTarget, eventType, this);
+ }, this);
+ },
+ /**
+ * Removes `eventTarget` from the list of observed `eventTarget`s. Listeners
+ * for all supported events will be unregistered from the given `eventTarget`.
+ * @param {EventTarget} eventTarget
+ */
+ ignore: function ignore(eventTarget) {
+ this.supportedEventsTypes.forEach(function(eventType) {
+ removeListener(eventTarget, eventType, this);
+ }, this);
+ }
+});
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/file.js b/tools/addon-sdk-1.7/packages/api-utils/lib/file.js
new file mode 100644
index 0000000..0b4eac9
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/file.js
@@ -0,0 +1,192 @@
+/* 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,Cr} = require("chrome");
+const byteStreams = require("./byte-streams");
+const textStreams = require("./text-streams");
+
+// Flags passed when opening a file. See nsprpub/pr/include/prio.h.
+const OPEN_FLAGS = {
+ RDONLY: parseInt("0x01"),
+ WRONLY: parseInt("0x02"),
+ CREATE_FILE: parseInt("0x08"),
+ APPEND: parseInt("0x10"),
+ TRUNCATE: parseInt("0x20"),
+ EXCL: parseInt("0x80")
+};
+
+var dirsvc = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties);
+
+function MozFile(path) {
+ var file = Cc['@mozilla.org/file/local;1']
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(path);
+ return file;
+}
+
+function ensureReadable(file) {
+ if (!file.isReadable())
+ throw new Error("path is not readable: " + file.path);
+}
+
+function ensureDir(file) {
+ ensureExists(file);
+ if (!file.isDirectory())
+ throw new Error("path is not a directory: " + file.path);
+}
+
+function ensureFile(file) {
+ ensureExists(file);
+ if (!file.isFile())
+ throw new Error("path is not a file: " + file.path);
+}
+
+function ensureExists(file) {
+ if (!file.exists())
+ throw friendlyError(Cr.NS_ERROR_FILE_NOT_FOUND, file.path);
+}
+
+function friendlyError(errOrResult, filename) {
+ var isResult = typeof(errOrResult) === "number";
+ var result = isResult ? errOrResult : errOrResult.result;
+ switch (result) {
+ case Cr.NS_ERROR_FILE_NOT_FOUND:
+ return new Error("path does not exist: " + filename);
+ }
+ return isResult ? new Error("XPCOM error code: " + errOrResult) : errOrResult;
+}
+
+exports.exists = function exists(filename) {
+ return MozFile(filename).exists();
+};
+
+exports.isFile = function isFile(filename) {
+ return MozFile(filename).isFile();
+};
+
+exports.read = function read(filename, mode) {
+ if (typeof(mode) !== "string")
+ mode = "";
+
+ // Ensure mode is read-only.
+ mode = /b/.test(mode) ? "b" : "";
+
+ var stream = exports.open(filename, mode);
+ try {
+ var str = stream.read();
+ }
+ finally {
+ stream.close();
+ }
+
+ return str;
+};
+
+exports.join = function join(base) {
+ if (arguments.length < 2)
+ throw new Error("need at least 2 args");
+ base = MozFile(base);
+ for (var i = 1; i < arguments.length; i++)
+ base.append(arguments[i]);
+ return base.path;
+};
+
+exports.dirname = function dirname(path) {
+ var parent = MozFile(path).parent;
+ return parent ? parent.path : "";
+};
+
+exports.basename = function basename(path) {
+ var leafName = MozFile(path).leafName;
+
+ // On Windows, leafName when the path is a volume letter and colon ("c:") is
+ // the path itself. But such a path has no basename, so we want the empty
+ // string.
+ return leafName == path ? "" : leafName;
+};
+
+exports.list = function list(path) {
+ var file = MozFile(path);
+ ensureDir(file);
+ ensureReadable(file);
+
+ var entries = file.directoryEntries;
+ var entryNames = [];
+ while(entries.hasMoreElements()) {
+ var entry = entries.getNext();
+ entry.QueryInterface(Ci.nsIFile);
+ entryNames.push(entry.leafName);
+ }
+ return entryNames;
+};
+
+exports.open = function open(filename, mode) {
+ var file = MozFile(filename);
+ if (typeof(mode) !== "string")
+ mode = "";
+
+ // File opened for write only.
+ if (/w/.test(mode)) {
+ if (file.exists())
+ ensureFile(file);
+ var stream = Cc['@mozilla.org/network/file-output-stream;1'].
+ createInstance(Ci.nsIFileOutputStream);
+ var openFlags = OPEN_FLAGS.WRONLY |
+ OPEN_FLAGS.CREATE_FILE |
+ OPEN_FLAGS.TRUNCATE;
+ var permFlags = parseInt("0644"); // u+rw go+r
+ try {
+ stream.init(file, openFlags, permFlags, 0);
+ }
+ catch (err) {
+ throw friendlyError(err, filename);
+ }
+ return /b/.test(mode) ?
+ new byteStreams.ByteWriter(stream) :
+ new textStreams.TextWriter(stream);
+ }
+
+ // File opened for read only, the default.
+ ensureFile(file);
+ stream = Cc['@mozilla.org/network/file-input-stream;1'].
+ createInstance(Ci.nsIFileInputStream);
+ try {
+ stream.init(file, OPEN_FLAGS.RDONLY, 0, 0);
+ }
+ catch (err) {
+ throw friendlyError(err, filename);
+ }
+ return /b/.test(mode) ?
+ new byteStreams.ByteReader(stream) :
+ new textStreams.TextReader(stream);
+};
+
+exports.remove = function remove(path) {
+ var file = MozFile(path);
+ ensureFile(file);
+ file.remove(false);
+};
+
+exports.mkpath = function mkpath(path) {
+ var file = MozFile(path);
+ if (!file.exists())
+ file.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755")); // u+rwx go+rx
+ else if (!file.isDirectory())
+ throw new Error("The path already exists and is not a directory: " + path);
+};
+
+exports.rmdir = function rmdir(path) {
+ var file = MozFile(path);
+ ensureDir(file);
+ try {
+ file.remove(false);
+ }
+ catch (err) {
+ // Bug 566950 explains why we're not catching a specific exception here.
+ throw new Error("The directory is not empty: " + path);
+ }
+};
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/find-tests.js b/tools/addon-sdk-1.7/packages/api-utils/lib/find-tests.js
new file mode 100644
index 0000000..60550bf
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/find-tests.js
@@ -0,0 +1,5 @@
+/* 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/. */
+
+// this file left intentionally blank
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/frame/utils.js b/tools/addon-sdk-1.7/packages/api-utils/lib/frame/utils.js
new file mode 100644
index 0000000..5c9da38
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/frame/utils.js
@@ -0,0 +1,59 @@
+/* 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 XUL = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
+
+/**
+ * Creates a XUL `browser` element in a privileged document.
+ * @params {nsIDOMDocument} document
+ * @params {String} options.type
+ * By default is 'content' for possible values see:
+ * https://developer.mozilla.org/en/XUL/iframe#a-browser.type
+ * @params {String} options.uri
+ * URI of the document to be loaded into created frame.
+ * @params {Boolean} options.remote
+ * If `true` separate process will be used for this frame, also in such
+ * case all the following options are ignored.
+ * @params {Boolean} options.allowAuth
+ * Whether to allow auth dialogs. Defaults to `false`.
+ * @params {Boolean} options.allowJavascript
+ * Whether to allow Javascript execution. Defaults to `false`.
+ * @params {Boolean} options.allowPlugins
+ * Whether to allow plugin execution. Defaults to `false`.
+ */
+function create(document, options) {
+ options = options || {};
+ let remote = 'remote' in options && options.remote === true;
+
+ let frame = document.createElementNS(XUL, 'browser');
+ // Type="content" is mandatory to enable stuff here:
+ // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsFrameLoader.cpp#1776
+ frame.setAttribute('type', options.type || 'content');
+ frame.setAttribute('src', options.uri || 'about:blank');
+
+ // Load in separate process if `options.remote` is `true`.
+ // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsFrameLoader.cpp#1347
+ if (remote) {
+ // We remove XBL binding to avoid execution of code that is not going to
+ // work because browser has no docShell attribute in remote mode
+ // (for example)
+ frame.setAttribute('style', '-moz-binding: none;');
+ frame.setAttribute('remote', 'true');
+ }
+
+ document.documentElement.appendChild(frame);
+
+ // If browser is remote it won't have a `docShell`.
+ if (!remote) {
+ let docShell = frame.docShell;
+ docShell.allowAuth = options.allowAuth || false;
+ docShell.allowJavascript = options.allowJavascript || false;
+ docShell.allowPlugins = options.allowPlugins || false;
+ }
+
+ return frame;
+}
+exports.create = create;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/functional.js b/tools/addon-sdk-1.7/packages/api-utils/lib/functional.js
new file mode 100644
index 0000000..61738d9
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/functional.js
@@ -0,0 +1,159 @@
+/* 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/. */
+
+// Disclaimer: Most of the functions in this module implement APIs from
+// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
+// those goes to him.
+
+"use strict";
+
+const { setTimeout } = require("./timer");
+
+/**
+ * Takes `lambda` function and returns a method. When returned method is
+ * invoked it calls wrapped `lambda` and passes `this` as a first argument
+ * and given argument as rest.
+ */
+function method(lambda) {
+ return function method() {
+ return lambda.apply(null, [this].concat(Array.slice(arguments)));
+ }
+}
+exports.method = method;
+
+/**
+ * Takes a function and returns a wrapped one instead, calling which will call
+ * original function in the next turn of event loop. This is basically utility
+ * to do `setTimeout(function() { ... }, 0)`, with a difference that returned
+ * function is reused, instead of creating a new one each time. This also allows
+ * to use this functions as event listeners.
+ */
+function defer(f) {
+ return function deferred()
+ setTimeout(invoke, 0, f, arguments, this);
+}
+exports.defer = defer;
+// Exporting `remit` alias as `defer` may conflict with promises.
+exports.remit = defer;
+
+/**
+ * Invokes `callee` by passing `params` as an arguments and `self` as `this`
+ * pseudo-variable. Returns value that is returned by a callee.
+ * @param {Function} callee
+ * Function to invoke.
+ * @param {Array} params
+ * Arguments to invoke function with.
+ * @param {Object} self
+ * Object to be passed as a `this` pseudo variable.
+ */
+function invoke(callee, params, self) callee.apply(self, params);
+exports.invoke = invoke;
+
+/**
+ * Curries a function with the arguments given.
+ *
+ * @param {Function} fn
+ * The function to curry
+ *
+ * @returns The function curried
+ */
+function curry(fn) {
+ if (typeof fn !== "function")
+ throw new TypeError(String(fn) + " is not a function");
+
+ let args = Array.slice(arguments, 1);
+
+ return function() fn.apply(this, args.concat(Array.slice(arguments)));
+}
+exports.curry = curry;
+
+/**
+ * Returns the composition of a list of functions, where each function consumes
+ * the return value of the function that follows. In math terms, composing the
+ * functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
+ * @example
+ *
+ * var greet = function(name) { return "hi: " + name; };
+ * var exclaim = function(statement) { return statement + "!"; };
+ * var welcome = compose(exclaim, greet);
+ *
+ * welcome('moe'); // => 'hi: moe!'
+ */
+function compose() {
+ let lambdas = Array.slice(arguments);
+ return function composed() {
+ let args = Array.slice(arguments), index = lambdas.length;
+ while (0 <= --index)
+ args = [ lambdas[index].apply(this, args) ];
+ return args[0];
+ };
+}
+exports.compose = compose;
+
+/*
+ * Returns the first function passed as an argument to the second,
+ * allowing you to adjust arguments, run code before and after, and
+ * conditionally execute the original function.
+ * @example
+ *
+ * var hello = function(name) { return "hello: " + name; };
+ * hello = wrap(hello, function(f) {
+ * return "before, " + f("moe") + ", after";
+ * });
+ *
+ * hello(); // => 'before, hello: moe, after'
+ */
+function wrap(f, wrapper) {
+ return function wrapped()
+ wrapper.apply(this, [ f ].concat(Array.slice(arguments)))
+};
+exports.wrap = wrap;
+
+/**
+ * Returns the same value that is used as the argument. In math: f(x) = x
+ */
+function identity(value) value
+exports.identity = identity;
+
+/**
+ * Memoizes a given function by caching the computed result. Useful for
+ * speeding up slow-running computations. If passed an optional hashFunction,
+ * it will be used to compute the hash key for storing the result, based on
+ * the arguments to the original function. The default hashFunction just uses
+ * the first argument to the memoized function as the key.
+ */
+function memoize(f, hasher) {
+ let memo = Object.create(null);
+ hasher = hasher || identity;
+ return function memoizer() {
+ let key = hasher.apply(this, arguments);
+ return key in memo ? memo[key] : (memo[key] = f.apply(this, arguments));
+ };
+}
+exports.memoize = memoize;
+
+/**
+ * Much like setTimeout, invokes function after wait milliseconds. If you pass
+ * the optional arguments, they will be forwarded on to the function when it is
+ * invoked.
+ */
+function delay(f, ms) {
+ let args = Array.slice(arguments, 2);
+ setTimeout(function(context) { return f.apply(context, args); }, ms, this);
+};
+exports.delay = delay;
+
+/**
+ * Creates a version of the function that can only be called one time. Repeated
+ * calls to the modified function will have no effect, returning the value from
+ * the original call. Useful for initialization functions, instead of having to
+ * set a boolean flag and then check it later.
+ */
+function once(f) {
+ let ran = false, cache;
+ return function() ran ? cache : (ran = true, cache = f.apply(this, arguments))
+};
+exports.once = once;
+// export cache as once will may be conflicting with event once a lot.
+exports.cache = once;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/globals!.js b/tools/addon-sdk-1.7/packages/api-utils/lib/globals!.js
new file mode 100644
index 0000000..01e85b7
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/globals!.js
@@ -0,0 +1,93 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* 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 { Cc, Ci } = require('chrome');
+let { PlainTextConsole } = require('./plain-text-console');
+let options = require('@packaging');
+let consoleService = Cc['@mozilla.org/consoleservice;1'].getService().
+ QueryInterface(Ci.nsIConsoleService);
+
+// On windows dump does not writes into stdout so cfx can't read thous dumps.
+// To workaround this issue we write to a special file from which cfx will
+// read and print to the console.
+// For more details see: bug-673383
+exports.dump = (function define(global) {
+ const PR_WRONLY = 0x02;
+ const PR_CREATE_FILE = 0x08;
+ const PR_APPEND = 0x10;
+ let print = Object.getPrototypeOf(global).dump
+ if (print) return print;
+ if ('logFile' in options) {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+ file.initWithPath(options.logFile);
+ let stream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ stream.init(file, PR_WRONLY|PR_CREATE_FILE|PR_APPEND, -1, 0);
+
+ return function print(message) {
+ message = String(message);
+ stream.write(message, message.length);
+ stream.flush();
+ };
+ }
+ return dump;
+})(this);
+
+// Override the default Iterator function with one that passes
+// a second argument to custom iterator methods that identifies
+// the call as originating from an Iterator function so the custom
+// iterator method can return [key, value] pairs just like default
+// iterators called via the default Iterator function.
+exports.Iterator = (function(DefaultIterator) {
+ return function Iterator(obj, keysOnly) {
+ if ("__iterator__" in obj && !keysOnly)
+ return obj.__iterator__.call(obj, false, true);
+ return DefaultIterator(obj, keysOnly);
+ };
+})(Iterator);
+
+// Bug 718230: We need to send console messages to stdout and JS Console
+function forsakenConsoleDump(msg, level) {
+ exports.dump(msg);
+
+ if (level === "error") {
+ let err = Cc["@mozilla.org/scripterror;1"].
+ createInstance(Ci.nsIScriptError);
+ msg = msg.replace(/^error: /, "");
+ err.init(msg, null, null, 0, 0, 0, "Add-on SDK");
+ consoleService.logMessage(err);
+ }
+ else
+ consoleService.logStringMessage(msg);
+};
+exports.console = new PlainTextConsole(forsakenConsoleDump);
+
+// Provide CommonJS `define` to allow authoring modules in a format that can be
+// loaded both into jetpack and into browser via AMD loaders.
+Object.defineProperty(exports, 'define', {
+ // `define` is provided as a lazy getter that binds below defined `define`
+ // function to the module scope, so that require, exports and module
+ // variables remain accessible.
+ configurable: true,
+ get: (function() {
+ function define(factory) {
+ factory = Array.slice(arguments).pop();
+ factory.call(this, this.require, this.exports, this.module);
+ }
+
+ return function getter() {
+ // Redefine `define` as a static property to make sure that module
+ // gets access to the same function so that `define === define` is
+ // `true`.
+ Object.defineProperty(this, 'define', {
+ configurable: false,
+ value: define.bind(this)
+ });
+ return this.define;
+ }
+ })()
+});
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/hidden-frame.js b/tools/addon-sdk-1.7/packages/api-utils/lib/hidden-frame.js
new file mode 100644
index 0000000..6f0016b
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/hidden-frame.js
@@ -0,0 +1,166 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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} = require("chrome");
+const errors = require("./errors");
+const apiUtils = require("./api-utils");
+const timer = require("./timer");
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+let hostFrame, hostDocument, hiddenWindow, isHostFrameReady = false;
+
+if (!require("./xul-app").isOneOf(["Firefox", "Fennec", "Thunderbird"])) {
+ throw new Error([
+ "The hidden-frame module currently supports only Firefox and Thunderbird. ",
+ "In the future, we would like it to support other applications, however. ",
+ "Please see https://bugzilla.mozilla.org/show_bug.cgi?id=546740 for more ",
+ "information."
+ ].join(""));
+}
+
+let appShellService = Cc["@mozilla.org/appshell/appShellService;1"].
+ getService(Ci.nsIAppShellService);
+hiddenWindow = appShellService.hiddenDOMWindow;
+
+if (!hiddenWindow) {
+ throw new Error([
+ "The hidden-frame module needs an app that supports a hidden window. ",
+ "We would like it to support other applications, however. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=546740 for more information."
+ ].join(""));
+}
+
+// Check if we can use the hidden window itself to host our iframes.
+// If it's not a suitable host, the hostFrame will be lazily created
+// by the first HiddenFrame instance.
+if (hiddenWindow.location.protocol == "chrome:" &&
+ (hiddenWindow.document.contentType == "application/vnd.mozilla.xul+xml" ||
+ hiddenWindow.document.contentType == "application/xhtml+xml")) {
+ hostFrame = hiddenWindow;
+ hostDocument = hiddenWindow.document;
+ isHostFrameReady = true;
+}
+
+function setHostFrameReady() {
+ hostDocument = hostFrame.contentDocument;
+ hostFrame.removeEventListener("DOMContentLoaded", setHostFrameReady, false);
+ isHostFrameReady = true;
+}
+
+// This cache is used to access friend properties between functions
+// without exposing them on the public API.
+let cache = [];
+
+exports.HiddenFrame = apiUtils.publicConstructor(HiddenFrame);
+
+function HiddenFrame(options) {
+ options = options || {};
+ let self = this;
+
+ for each (let [key, val] in Iterator(apiUtils.validateOptions(options, {
+ onReady: {
+ is: ["undefined", "function", "array"],
+ ok: function(v) {
+ if (apiUtils.getTypeOf(v) === "array") {
+ // make sure every item is a function
+ return v.every(function (item) typeof(item) === "function")
+ }
+ return true;
+ }
+ }
+ }))) {
+ if (typeof(val) != "undefined")
+ options[key] = val;
+ }
+
+ require("./collection").addCollectionProperty(this, "onReady");
+ if (options.onReady)
+ this.onReady.add(options.onReady);
+
+ if (!hostFrame) {
+ hostFrame = hiddenWindow.document.createElement("iframe");
+
+ // ugly ugly hack. This is the most lightweight chrome:// file I could find on the tree
+ // This hack should be removed by proper platform support on bug 565388
+ hostFrame.setAttribute("src", "chrome://global/content/mozilla.xhtml");
+ hostFrame.addEventListener("DOMContentLoaded", setHostFrameReady, false);
+
+ hiddenWindow.document.body.appendChild(hostFrame);
+ }
+
+ this.toString = function toString() "[object Frame]";
+}
+
+exports.add = function JP_SDK_Frame_add(frame) {
+ if (!(frame instanceof HiddenFrame))
+ throw new Error("The object to be added must be a HiddenFrame.");
+
+ // This instance was already added.
+ if (cache.filter(function (v) v.frame === frame)[0])
+ return frame;
+
+ function createElement() {
+ hostFrame.removeEventListener("DOMContentLoaded", createElement, false);
+
+ let element = hostDocument.createElementNS(XUL_NS, "iframe");
+
+ element.setAttribute("type", "content");
+ hostDocument.documentElement.appendChild(element);
+
+ /* Public API: hiddenFrame.element */
+ frame.__defineGetter__("element", function () element);
+
+ // Notify consumers that the frame is ready.
+ function onReadyListener(event) {
+ element.removeEventListener("DOMContentLoaded", onReadyListener, false);
+ if (event.target == element.contentDocument) {
+ for (let handler in frame.onReady)
+ errors.catchAndLog(function () handler.call(frame))();
+ }
+ }
+ element.addEventListener("DOMContentLoaded", onReadyListener, false);
+
+ cache.push({
+ frame: frame,
+ element: element,
+ unload: function unload() {
+ hostDocument.documentElement.removeChild(element);
+ }
+ });
+ }
+
+ /* Begin element construction or schedule it for later */
+ if (isHostFrameReady) {
+ createElement();
+ } else {
+ hostFrame.addEventListener("DOMContentLoaded", createElement, false);
+ }
+
+ return frame;
+}
+
+exports.remove = function remove(frame) {
+ if (!(frame instanceof HiddenFrame))
+ throw new Error("The object to be removed must be a HiddenFrame.");
+
+ let entry = cache.filter(function (v) v.frame === frame)[0];
+ if (!entry)
+ return;
+
+ entry.unload();
+ cache.splice(cache.indexOf(entry), 1);
+}
+
+require("./unload").when(function () {
+ for each (let entry in cache.slice())
+ exports.remove(entry.frame);
+
+ if (hostFrame && hostFrame !== hiddenWindow)
+ hiddenWindow.document.body.removeChild(hostFrame);
+});
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/httpd.js b/tools/addon-sdk-1.7/packages/api-utils/lib/httpd.js
new file mode 100644
index 0000000..7f6a363
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/httpd.js
@@ -0,0 +1,5166 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+/*
+* An implementation of an HTTP server both as a loadable script and as an XPCOM
+* component. See the accompanying README file for user documentation on
+* httpd.js.
+*/
+
+
+var {components,Cc,Ci,Cr,Cu} = require("chrome");
+components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const CC = components.Constructor;
+
+const PR_UINT32_MAX = Math.pow(2, 32) - 1;
+
+/** True if debugging output is enabled, false otherwise. */
+var DEBUG = false; // non-const *only* so tweakable in server tests
+
+/** True if debugging output should be timestamped. */
+var DEBUG_TIMESTAMP = false; // non-const so tweakable in server tests
+
+var gGlobalObject = Cc["@mozilla.org/systemprincipal;1"].createInstance();
+
+/**
+* Asserts that the given condition holds. If it doesn't, the given message is
+* dumped, a stack trace is printed, and an exception is thrown to attempt to
+* stop execution (which unfortunately must rely upon the exception not being
+* accidentally swallowed by the code that uses it).
+*/
+function NS_ASSERT(cond, msg)
+{
+ if (DEBUG && !cond)
+ {
+ dumpn("###!!!");
+ dumpn("###!!! ASSERTION" + (msg ? ": " + msg : "!"));
+ dumpn("###!!! Stack follows:");
+
+ var stack = new Error().stack.split(/\n/);
+ dumpn(stack.map(function(val) { return "###!!! " + val; }).join("\n"));
+
+ throw Cr.NS_ERROR_ABORT;
+ }
+}
+
+/** Constructs an HTTP error object. */
+function HttpError(code, description)
+{
+ this.code = code;
+ this.description = description;
+}
+HttpError.prototype =
+{
+ toString: function()
+ {
+ return this.code + " " + this.description;
+ }
+};
+
+/**
+* Errors thrown to trigger specific HTTP server responses.
+*/
+const HTTP_400 = new HttpError(400, "Bad Request");
+const HTTP_401 = new HttpError(401, "Unauthorized");
+const HTTP_402 = new HttpError(402, "Payment Required");
+const HTTP_403 = new HttpError(403, "Forbidden");
+const HTTP_404 = new HttpError(404, "Not Found");
+const HTTP_405 = new HttpError(405, "Method Not Allowed");
+const HTTP_406 = new HttpError(406, "Not Acceptable");
+const HTTP_407 = new HttpError(407, "Proxy Authentication Required");
+const HTTP_408 = new HttpError(408, "Request Timeout");
+const HTTP_409 = new HttpError(409, "Conflict");
+const HTTP_410 = new HttpError(410, "Gone");
+const HTTP_411 = new HttpError(411, "Length Required");
+const HTTP_412 = new HttpError(412, "Precondition Failed");
+const HTTP_413 = new HttpError(413, "Request Entity Too Large");
+const HTTP_414 = new HttpError(414, "Request-URI Too Long");
+const HTTP_415 = new HttpError(415, "Unsupported Media Type");
+const HTTP_417 = new HttpError(417, "Expectation Failed");
+
+const HTTP_500 = new HttpError(500, "Internal Server Error");
+const HTTP_501 = new HttpError(501, "Not Implemented");
+const HTTP_502 = new HttpError(502, "Bad Gateway");
+const HTTP_503 = new HttpError(503, "Service Unavailable");
+const HTTP_504 = new HttpError(504, "Gateway Timeout");
+const HTTP_505 = new HttpError(505, "HTTP Version Not Supported");
+
+/** Creates a hash with fields corresponding to the values in arr. */
+function array2obj(arr)
+{
+ var obj = {};
+ for (var i = 0; i < arr.length; i++)
+ obj[arr[i]] = arr[i];
+ return obj;
+}
+
+/** Returns an array of the integers x through y, inclusive. */
+function range(x, y)
+{
+ var arr = [];
+ for (var i = x; i <= y; i++)
+ arr.push(i);
+ return arr;
+}
+
+/** An object (hash) whose fields are the numbers of all HTTP error codes. */
+const HTTP_ERROR_CODES = array2obj(range(400, 417).concat(range(500, 505)));
+
+
+/**
+* The character used to distinguish hidden files from non-hidden files, a la
+* the leading dot in Apache. Since that mechanism also hides files from
+* easy display in LXR, ls output, etc. however, we choose instead to use a
+* suffix character. If a requested file ends with it, we append another
+* when getting the file on the server. If it doesn't, we just look up that
+* file. Therefore, any file whose name ends with exactly one of the character
+* is "hidden" and available for use by the server.
+*/
+const HIDDEN_CHAR = "^";
+
+/**
+* The file name suffix indicating the file containing overridden headers for
+* a requested file.
+*/
+const HEADERS_SUFFIX = HIDDEN_CHAR + "headers" + HIDDEN_CHAR;
+
+/** Type used to denote SJS scripts for CGI-like functionality. */
+const SJS_TYPE = "sjs";
+
+/** Base for relative timestamps produced by dumpn(). */
+var firstStamp = 0;
+
+/** dump(str) with a trailing "\n" -- only outputs if DEBUG. */
+function dumpn(str)
+{
+ if (DEBUG)
+ {
+ var prefix = "HTTPD-INFO | ";
+ if (DEBUG_TIMESTAMP)
+ {
+ if (firstStamp === 0)
+ firstStamp = Date.now();
+
+ var elapsed = Date.now() - firstStamp; // milliseconds
+ var min = Math.floor(elapsed / 60000);
+ var sec = (elapsed % 60000) / 1000;
+
+ if (sec < 10)
+ prefix += min + ":0" + sec.toFixed(3) + " | ";
+ else
+ prefix += min + ":" + sec.toFixed(3) + " | ";
+ }
+
+ dump(prefix + str + "\n");
+ }
+}
+
+/** Dumps the current JS stack if DEBUG. */
+function dumpStack()
+{
+ // peel off the frames for dumpStack() and Error()
+ var stack = new Error().stack.split(/\n/).slice(2);
+ stack.forEach(dumpn);
+}
+
+
+/** The XPCOM thread manager. */
+var gThreadManager = null;
+
+/** The XPCOM prefs service. */
+var gRootPrefBranch = null;
+function getRootPrefBranch()
+{
+ if (!gRootPrefBranch)
+ {
+ gRootPrefBranch = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ }
+ return gRootPrefBranch;
+}
+
+/**
+* JavaScript constructors for commonly-used classes; precreating these is a
+* speedup over doing the same from base principles. See the docs at
+* http://developer.mozilla.org/en/docs/components.Constructor for details.
+*/
+const ServerSocket = CC("@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "init");
+const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1",
+ "nsIScriptableInputStream",
+ "init");
+const Pipe = CC("@mozilla.org/pipe;1",
+ "nsIPipe",
+ "init");
+const FileInputStream = CC("@mozilla.org/network/file-input-stream;1",
+ "nsIFileInputStream",
+ "init");
+const ConverterInputStream = CC("@mozilla.org/intl/converter-input-stream;1",
+ "nsIConverterInputStream",
+ "init");
+const WritablePropertyBag = CC("@mozilla.org/hash-property-bag;1",
+ "nsIWritablePropertyBag2");
+const SupportsString = CC("@mozilla.org/supports-string;1",
+ "nsISupportsString");
+
+/* These two are non-const only so a test can overwrite them. */
+var BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+var BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream",
+ "setOutputStream");
+
+/**
+* Returns the RFC 822/1123 representation of a date.
+*
+* @param date : Number
+* the date, in milliseconds from midnight (00:00:00), January 1, 1970 GMT
+* @returns string
+* the representation of the given date
+*/
+function toDateString(date)
+{
+ //
+ // rfc1123-date = wkday "," SP date1 SP time SP "GMT"
+ // date1 = 2DIGIT SP month SP 4DIGIT
+ // ; day month year (e.g., 02 Jun 1982)
+ // time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
+ // ; 00:00:00 - 23:59:59
+ // wkday = "Mon" | "Tue" | "Wed"
+ // | "Thu" | "Fri" | "Sat" | "Sun"
+ // month = "Jan" | "Feb" | "Mar" | "Apr"
+ // | "May" | "Jun" | "Jul" | "Aug"
+ // | "Sep" | "Oct" | "Nov" | "Dec"
+ //
+
+ const wkdayStrings = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+ const monthStrings = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
+
+ /**
+* Processes a date and returns the encoded UTC time as a string according to
+* the format specified in RFC 2616.
+*
+* @param date : Date
+* the date to process
+* @returns string
+* a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59"
+*/
+ function toTime(date)
+ {
+ var hrs = date.getUTCHours();
+ var rv = (hrs < 10) ? "0" + hrs : hrs;
+
+ var mins = date.getUTCMinutes();
+ rv += ":";
+ rv += (mins < 10) ? "0" + mins : mins;
+
+ var secs = date.getUTCSeconds();
+ rv += ":";
+ rv += (secs < 10) ? "0" + secs : secs;
+
+ return rv;
+ }
+
+ /**
+* Processes a date and returns the encoded UTC date as a string according to
+* the date1 format specified in RFC 2616.
+*
+* @param date : Date
+* the date to process
+* @returns string
+* a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59"
+*/
+ function toDate1(date)
+ {
+ var day = date.getUTCDate();
+ var month = date.getUTCMonth();
+ var year = date.getUTCFullYear();
+
+ var rv = (day < 10) ? "0" + day : day;
+ rv += " " + monthStrings[month];
+ rv += " " + year;
+
+ return rv;
+ }
+
+ date = new Date(date);
+
+ const fmtString = "%wkday%, %date1% %time% GMT";
+ var rv = fmtString.replace("%wkday%", wkdayStrings[date.getUTCDay()]);
+ rv = rv.replace("%time%", toTime(date));
+ return rv.replace("%date1%", toDate1(date));
+}
+
+/**
+* Prints out a human-readable representation of the object o and its fields,
+* omitting those whose names begin with "_" if showMembers != true (to ignore
+* "private" properties exposed via getters/setters).
+*/
+function printObj(o, showMembers)
+{
+ var s = "******************************\n";
+ s += "o = {\n";
+ for (var i in o)
+ {
+ if (typeof(i) != "string" ||
+ (showMembers || (i.length > 0 && i[0] != "_")))
+ s+= " " + i + ": " + o[i] + ",\n";
+ }
+ s += " };\n";
+ s += "******************************";
+ dumpn(s);
+}
+
+/**
+* Instantiates a new HTTP server.
+*/
+function nsHttpServer()
+{
+ if (!gThreadManager)
+ gThreadManager = Cc["@mozilla.org/thread-manager;1"].getService();
+
+ /** The port on which this server listens. */
+ this._port = undefined;
+
+ /** The socket associated with this. */
+ this._socket = null;
+
+ /** The handler used to process requests to this server. */
+ this._handler = new ServerHandler(this);
+
+ /** Naming information for this server. */
+ this._identity = new ServerIdentity();
+
+ /**
+* Indicates when the server is to be shut down at the end of the request.
+*/
+ this._doQuit = false;
+
+ /**
+* True if the socket in this is closed (and closure notifications have been
+* sent and processed if the socket was ever opened), false otherwise.
+*/
+ this._socketClosed = true;
+
+ /**
+* Used for tracking existing connections and ensuring that all connections
+* are properly cleaned up before server shutdown; increases by 1 for every
+* new incoming connection.
+*/
+ this._connectionGen = 0;
+
+ /**
+* Hash of all open connections, indexed by connection number at time of
+* creation.
+*/
+ this._connections = {};
+}
+nsHttpServer.prototype =
+{
+ classID: components.ID("{54ef6f81-30af-4b1d-ac55-8ba811293e41}"),
+
+ // NSISERVERSOCKETLISTENER
+
+ /**
+* Processes an incoming request coming in on the given socket and contained
+* in the given transport.
+*
+* @param socket : nsIServerSocket
+* the socket through which the request was served
+* @param trans : nsISocketTransport
+* the transport for the request/response
+* @see nsIServerSocketListener.onSocketAccepted
+*/
+ onSocketAccepted: function(socket, trans)
+ {
+ dumpn("*** onSocketAccepted(socket=" + socket + ", trans=" + trans + ")");
+
+ dumpn(">>> new connection on " + trans.host + ":" + trans.port);
+
+ const SEGMENT_SIZE = 8192;
+ const SEGMENT_COUNT = 1024;
+ try
+ {
+ var input = trans.openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ var output = trans.openOutputStream(0, 0, 0);
+ }
+ catch (e)
+ {
+ dumpn("*** error opening transport streams: " + e);
+ trans.close(Cr.NS_BINDING_ABORTED);
+ return;
+ }
+
+ var connectionNumber = ++this._connectionGen;
+
+ try
+ {
+ var conn = new Connection(input, output, this, socket.port, trans.port,
+ connectionNumber);
+ var reader = new RequestReader(conn);
+
+ // XXX add request timeout functionality here!
+
+ // Note: must use main thread here, or we might get a GC that will cause
+ // threadsafety assertions. We really need to fix XPConnect so that
+ // you can actually do things in multi-threaded JS. :-(
+ input.asyncWait(reader, 0, 0, gThreadManager.mainThread);
+ }
+ catch (e)
+ {
+ // Assume this connection can't be salvaged and bail on it completely;
+ // don't attempt to close it so that we can assert that any connection
+ // being closed is in this._connections.
+ dumpn("*** error in initial request-processing stages: " + e);
+ trans.close(Cr.NS_BINDING_ABORTED);
+ return;
+ }
+
+ this._connections[connectionNumber] = conn;
+ dumpn("*** starting connection " + connectionNumber);
+ },
+
+ /**
+* Called when the socket associated with this is closed.
+*
+* @param socket : nsIServerSocket
+* the socket being closed
+* @param status : nsresult
+* the reason the socket stopped listening (NS_BINDING_ABORTED if the server
+* was stopped using nsIHttpServer.stop)
+* @see nsIServerSocketListener.onStopListening
+*/
+ onStopListening: function(socket, status)
+ {
+ dumpn(">>> shutting down server on port " + socket.port);
+ this._socketClosed = true;
+ if (!this._hasOpenConnections())
+ {
+ dumpn("*** no open connections, notifying async from onStopListening");
+
+ // Notify asynchronously so that any pending teardown in stop() has a
+ // chance to run first.
+ var self = this;
+ var stopEvent =
+ {
+ run: function()
+ {
+ dumpn("*** _notifyStopped async callback");
+ self._notifyStopped();
+ }
+ };
+ gThreadManager.currentThread
+ .dispatch(stopEvent, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ },
+
+ // NSIHTTPSERVER
+
+ //
+ // see nsIHttpServer.start
+ //
+ start: function(port)
+ {
+ this._start(port, "localhost")
+ },
+
+ _start: function(port, host)
+ {
+ if (this._socket)
+ throw Cr.NS_ERROR_ALREADY_INITIALIZED;
+
+ this._port = port;
+ this._doQuit = this._socketClosed = false;
+
+ this._host = host;
+
+ // The listen queue needs to be long enough to handle
+ // network.http.max-connections-per-server concurrent connections,
+ // plus a safety margin in case some other process is talking to
+ // the server as well.
+ var prefs = getRootPrefBranch();
+ var maxConnections =
+ prefs.getIntPref("network.http.max-connections-per-server") + 5;
+
+ try
+ {
+ var loopback = true;
+ if (this._host != "127.0.0.1" && this._host != "localhost") {
+ var loopback = false;
+ }
+
+ var socket = new ServerSocket(this._port,
+ loopback, // true = localhost, false = everybody
+ maxConnections);
+ dumpn(">>> listening on port " + socket.port + ", " + maxConnections +
+ " pending connections");
+ socket.asyncListen(this);
+ this._identity._initialize(port, host, true);
+ this._socket = socket;
+ }
+ catch (e)
+ {
+ dumpn("!!! could not start server on port " + port + ": " + e);
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ }
+ },
+
+ //
+ // see nsIHttpServer.stop
+ //
+ stop: function(callback)
+ {
+ if (!callback)
+ throw Cr.NS_ERROR_NULL_POINTER;
+ if (!this._socket)
+ throw Cr.NS_ERROR_UNEXPECTED;
+
+ this._stopCallback = typeof callback === "function"
+ ? callback
+ : function() { callback.onStopped(); };
+
+ dumpn(">>> stopping listening on port " + this._socket.port);
+ this._socket.close();
+ this._socket = null;
+
+ // We can't have this identity any more, and the port on which we're running
+ // this server now could be meaningless the next time around.
+ this._identity._teardown();
+
+ this._doQuit = false;
+
+ // socket-close notification and pending request completion happen async
+ },
+
+ //
+ // see nsIHttpServer.registerFile
+ //
+ registerFile: function(path, file)
+ {
+ if (file && (!file.exists() || file.isDirectory()))
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ this._handler.registerFile(path, file);
+ },
+
+ //
+ // see nsIHttpServer.registerDirectory
+ //
+ registerDirectory: function(path, directory)
+ {
+ // XXX true path validation!
+ if (path.charAt(0) != "/" ||
+ path.charAt(path.length - 1) != "/" ||
+ (directory &&
+ (!directory.exists() || !directory.isDirectory())))
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ // XXX determine behavior of nonexistent /foo/bar when a /foo/bar/ mapping
+ // exists!
+
+ this._handler.registerDirectory(path, directory);
+ },
+
+ //
+ // see nsIHttpServer.registerPathHandler
+ //
+ registerPathHandler: function(path, handler)
+ {
+ this._handler.registerPathHandler(path, handler);
+ },
+
+ //
+ // see nsIHttpServer.registerErrorHandler
+ //
+ registerErrorHandler: function(code, handler)
+ {
+ this._handler.registerErrorHandler(code, handler);
+ },
+
+ //
+ // see nsIHttpServer.setIndexHandler
+ //
+ setIndexHandler: function(handler)
+ {
+ this._handler.setIndexHandler(handler);
+ },
+
+ //
+ // see nsIHttpServer.registerContentType
+ //
+ registerContentType: function(ext, type)
+ {
+ this._handler.registerContentType(ext, type);
+ },
+
+ //
+ // see nsIHttpServer.serverIdentity
+ //
+ get identity()
+ {
+ return this._identity;
+ },
+
+ //
+ // see nsIHttpServer.getState
+ //
+ getState: function(path, k)
+ {
+ return this._handler._getState(path, k);
+ },
+
+ //
+ // see nsIHttpServer.setState
+ //
+ setState: function(path, k, v)
+ {
+ return this._handler._setState(path, k, v);
+ },
+
+ //
+ // see nsIHttpServer.getSharedState
+ //
+ getSharedState: function(k)
+ {
+ return this._handler._getSharedState(k);
+ },
+
+ //
+ // see nsIHttpServer.setSharedState
+ //
+ setSharedState: function(k, v)
+ {
+ return this._handler._setSharedState(k, v);
+ },
+
+ //
+ // see nsIHttpServer.getObjectState
+ //
+ getObjectState: function(k)
+ {
+ return this._handler._getObjectState(k);
+ },
+
+ //
+ // see nsIHttpServer.setObjectState
+ //
+ setObjectState: function(k, v)
+ {
+ return this._handler._setObjectState(k, v);
+ },
+
+
+ // NSISUPPORTS
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIServerSocketListener) || iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // NON-XPCOM PUBLIC API
+
+ /**
+* Returns true iff this server is not running (and is not in the process of
+* serving any requests still to be processed when the server was last
+* stopped after being run).
+*/
+ isStopped: function()
+ {
+ return this._socketClosed && !this._hasOpenConnections();
+ },
+
+ // PRIVATE IMPLEMENTATION
+
+ /** True if this server has any open connections to it, false otherwise. */
+ _hasOpenConnections: function()
+ {
+ //
+ // If we have any open connections, they're tracked as numeric properties on
+ // |this._connections|. The non-standard __count__ property could be used
+ // to check whether there are any properties, but standard-wise, even
+ // looking forward to ES5, there's no less ugly yet still O(1) way to do
+ // this.
+ //
+ for (var n in this._connections)
+ return true;
+ return false;
+ },
+
+ /** Calls the server-stopped callback provided when stop() was called. */
+ _notifyStopped: function()
+ {
+ NS_ASSERT(this._stopCallback !== null, "double-notifying?");
+ NS_ASSERT(!this._hasOpenConnections(), "should be done serving by now");
+
+ //
+ // NB: We have to grab this now, null out the member, *then* call the
+ // callback here, or otherwise the callback could (indirectly) futz with
+ // this._stopCallback by starting and immediately stopping this, at
+ // which point we'd be nulling out a field we no longer have a right to
+ // modify.
+ //
+ var callback = this._stopCallback;
+ this._stopCallback = null;
+ try
+ {
+ callback();
+ }
+ catch (e)
+ {
+ // not throwing because this is specified as being usually (but not
+ // always) asynchronous
+ dump("!!! error running onStopped callback: " + e + "\n");
+ }
+ },
+
+ /**
+* Notifies this server that the given connection has been closed.
+*
+* @param connection : Connection
+* the connection that was closed
+*/
+ _connectionClosed: function(connection)
+ {
+ NS_ASSERT(connection.number in this._connections,
+ "closing a connection " + this + " that we never added to the " +
+ "set of open connections?");
+ NS_ASSERT(this._connections[connection.number] === connection,
+ "connection number mismatch? " +
+ this._connections[connection.number]);
+ delete this._connections[connection.number];
+
+ // Fire a pending server-stopped notification if it's our responsibility.
+ if (!this._hasOpenConnections() && this._socketClosed)
+ this._notifyStopped();
+ },
+
+ /**
+* Requests that the server be shut down when possible.
+*/
+ _requestQuit: function()
+ {
+ dumpn(">>> requesting a quit");
+ dumpStack();
+ this._doQuit = true;
+ }
+};
+
+
+//
+// RFC 2396 section 3.2.2:
+//
+// host = hostname | IPv4address
+// hostname = *( domainlabel "." ) toplabel [ "." ]
+// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
+// toplabel = alpha | alpha *( alphanum | "-" ) alphanum
+// IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit
+//
+
+const HOST_REGEX =
+ new RegExp("^(?:" +
+ // *( domainlabel "." )
+ "(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)*" +
+ // toplabel
+ "[a-z](?:[a-z0-9-]*[a-z0-9])?" +
+ "|" +
+ // IPv4 address
+ "\\d+\\.\\d+\\.\\d+\\.\\d+" +
+ ")$",
+ "i");
+
+
+/**
+* Represents the identity of a server. An identity consists of a set of
+* (scheme, host, port) tuples denoted as locations (allowing a single server to
+* serve multiple sites or to be used behind both HTTP and HTTPS proxies for any
+* host/port). Any incoming request must be to one of these locations, or it
+* will be rejected with an HTTP 400 error. One location, denoted as the
+* primary location, is the location assigned in contexts where a location
+* cannot otherwise be endogenously derived, such as for HTTP/1.0 requests.
+*
+* A single identity may contain at most one location per unique host/port pair;
+* other than that, no restrictions are placed upon what locations may
+* constitute an identity.
+*/
+function ServerIdentity()
+{
+ /** The scheme of the primary location. */
+ this._primaryScheme = "http";
+
+ /** The hostname of the primary location. */
+ this._primaryHost = "127.0.0.1"
+
+ /** The port number of the primary location. */
+ this._primaryPort = -1;
+
+ /**
+* The current port number for the corresponding server, stored so that a new
+* primary location can always be set if the current one is removed.
+*/
+ this._defaultPort = -1;
+
+ /**
+* Maps hosts to maps of ports to schemes, e.g. the following would represent
+* https://example.com:789/ and http://example.org/:
+*
+* {
+* "xexample.com": { 789: "https" },
+* "xexample.org": { 80: "http" }
+* }
+*
+* Note the "x" prefix on hostnames, which prevents collisions with special
+* JS names like "prototype".
+*/
+ this._locations = { "xlocalhost": {} };
+}
+ServerIdentity.prototype =
+{
+ // NSIHTTPSERVERIDENTITY
+
+ //
+ // see nsIHttpServerIdentity.primaryScheme
+ //
+ get primaryScheme()
+ {
+ if (this._primaryPort === -1)
+ throw Cr.NS_ERROR_NOT_INITIALIZED;
+ return this._primaryScheme;
+ },
+
+ //
+ // see nsIHttpServerIdentity.primaryHost
+ //
+ get primaryHost()
+ {
+ if (this._primaryPort === -1)
+ throw Cr.NS_ERROR_NOT_INITIALIZED;
+ return this._primaryHost;
+ },
+
+ //
+ // see nsIHttpServerIdentity.primaryPort
+ //
+ get primaryPort()
+ {
+ if (this._primaryPort === -1)
+ throw Cr.NS_ERROR_NOT_INITIALIZED;
+ return this._primaryPort;
+ },
+
+ //
+ // see nsIHttpServerIdentity.add
+ //
+ add: function(scheme, host, port)
+ {
+ this._validate(scheme, host, port);
+
+ var entry = this._locations["x" + host];
+ if (!entry)
+ this._locations["x" + host] = entry = {};
+
+ entry[port] = scheme;
+ },
+
+ //
+ // see nsIHttpServerIdentity.remove
+ //
+ remove: function(scheme, host, port)
+ {
+ this._validate(scheme, host, port);
+
+ var entry = this._locations["x" + host];
+ if (!entry)
+ return false;
+
+ var present = port in entry;
+ delete entry[port];
+
+ if (this._primaryScheme == scheme &&
+ this._primaryHost == host &&
+ this._primaryPort == port &&
+ this._defaultPort !== -1)
+ {
+ // Always keep at least one identity in existence at any time, unless
+ // we're in the process of shutting down (the last condition above).
+ this._primaryPort = -1;
+ this._initialize(this._defaultPort, host, false);
+ }
+
+ return present;
+ },
+
+ //
+ // see nsIHttpServerIdentity.has
+ //
+ has: function(scheme, host, port)
+ {
+ this._validate(scheme, host, port);
+
+ return "x" + host in this._locations &&
+ scheme === this._locations["x" + host][port];
+ },
+
+ //
+ // see nsIHttpServerIdentity.has
+ //
+ getScheme: function(host, port)
+ {
+ this._validate("http", host, port);
+
+ var entry = this._locations["x" + host];
+ if (!entry)
+ return "";
+
+ return entry[port] || "";
+ },
+
+ //
+ // see nsIHttpServerIdentity.setPrimary
+ //
+ setPrimary: function(scheme, host, port)
+ {
+ this._validate(scheme, host, port);
+
+ this.add(scheme, host, port);
+
+ this._primaryScheme = scheme;
+ this._primaryHost = host;
+ this._primaryPort = port;
+ },
+
+
+ // NSISUPPORTS
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIHttpServerIdentity) || iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // PRIVATE IMPLEMENTATION
+
+ /**
+* Initializes the primary name for the corresponding server, based on the
+* provided port number.
+*/
+ _initialize: function(port, host, addSecondaryDefault)
+ {
+ this._host = host;
+ if (this._primaryPort !== -1)
+ this.add("http", host, port);
+ else
+ this.setPrimary("http", "localhost", port);
+ this._defaultPort = port;
+
+ // Only add this if we're being called at server startup
+ if (addSecondaryDefault && host != "127.0.0.1")
+ this.add("http", "127.0.0.1", port);
+ },
+
+ /**
+* Called at server shutdown time, unsets the primary location only if it was
+* the default-assigned location and removes the default location from the
+* set of locations used.
+*/
+ _teardown: function()
+ {
+ if (this._host != "127.0.0.1") {
+ // Not the default primary location, nothing special to do here
+ this.remove("http", "127.0.0.1", this._defaultPort);
+ }
+
+ // This is a *very* tricky bit of reasoning here; make absolutely sure the
+ // tests for this code pass before you commit changes to it.
+ if (this._primaryScheme == "http" &&
+ this._primaryHost == this._host &&
+ this._primaryPort == this._defaultPort)
+ {
+ // Make sure we don't trigger the readding logic in .remove(), then remove
+ // the default location.
+ var port = this._defaultPort;
+ this._defaultPort = -1;
+ this.remove("http", this._host, port);
+
+ // Ensure a server start triggers the setPrimary() path in ._initialize()
+ this._primaryPort = -1;
+ }
+ else
+ {
+ // No reason not to remove directly as it's not our primary location
+ this.remove("http", this._host, this._defaultPort);
+ }
+ },
+
+ /**
+* Ensures scheme, host, and port are all valid with respect to RFC 2396.
+*
+* @throws NS_ERROR_ILLEGAL_VALUE
+* if any argument doesn't match the corresponding production
+*/
+ _validate: function(scheme, host, port)
+ {
+ if (scheme !== "http" && scheme !== "https")
+ {
+ dumpn("*** server only supports http/https schemes: '" + scheme + "'");
+ dumpStack();
+ throw Cr.NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (!HOST_REGEX.test(host))
+ {
+ dumpn("*** unexpected host: '" + host + "'");
+ throw Cr.NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (port < 0 || port > 65535)
+ {
+ dumpn("*** unexpected port: '" + port + "'");
+ throw Cr.NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+};
+
+
+/**
+* Represents a connection to the server (and possibly in the future the thread
+* on which the connection is processed).
+*
+* @param input : nsIInputStream
+* stream from which incoming data on the connection is read
+* @param output : nsIOutputStream
+* stream to write data out the connection
+* @param server : nsHttpServer
+* the server handling the connection
+* @param port : int
+* the port on which the server is running
+* @param outgoingPort : int
+* the outgoing port used by this connection
+* @param number : uint
+* a serial number used to uniquely identify this connection
+*/
+function Connection(input, output, server, port, outgoingPort, number)
+{
+ dumpn("*** opening new connection " + number + " on port " + outgoingPort);
+
+ /** Stream of incoming data. */
+ this.input = input;
+
+ /** Stream for outgoing data. */
+ this.output = output;
+
+ /** The server associated with this request. */
+ this.server = server;
+
+ /** The port on which the server is running. */
+ this.port = port;
+
+ /** The outgoing poort used by this connection. */
+ this._outgoingPort = outgoingPort;
+
+ /** The serial number of this connection. */
+ this.number = number;
+
+ /**
+* The request for which a response is being generated, null if the
+* incoming request has not been fully received or if it had errors.
+*/
+ this.request = null;
+
+ /** State variables for debugging. */
+ this._closed = this._processed = false;
+}
+Connection.prototype =
+{
+ /** Closes this connection's input/output streams. */
+ close: function()
+ {
+ dumpn("*** closing connection " + this.number +
+ " on port " + this._outgoingPort);
+
+ this.input.close();
+ this.output.close();
+ this._closed = true;
+
+ var server = this.server;
+ server._connectionClosed(this);
+
+ // If an error triggered a server shutdown, act on it now
+ if (server._doQuit)
+ server.stop(function() { /* not like we can do anything better */ });
+ },
+
+ /**
+* Initiates processing of this connection, using the data in the given
+* request.
+*
+* @param request : Request
+* the request which should be processed
+*/
+ process: function(request)
+ {
+ NS_ASSERT(!this._closed && !this._processed);
+
+ this._processed = true;
+
+ this.request = request;
+ this.server._handler.handleResponse(this);
+ },
+
+ /**
+* Initiates processing of this connection, generating a response with the
+* given HTTP error code.
+*
+* @param code : uint
+* an HTTP code, so in the range [0, 1000)
+* @param request : Request
+* incomplete data about the incoming request (since there were errors
+* during its processing
+*/
+ processError: function(code, request)
+ {
+ NS_ASSERT(!this._closed && !this._processed);
+
+ this._processed = true;
+ this.request = request;
+ this.server._handler.handleError(code, this);
+ },
+
+ /** Converts this to a string for debugging purposes. */
+ toString: function()
+ {
+ return "<Connection(" + this.number +
+ (this.request ? ", " + this.request.path : "") +"): " +
+ (this._closed ? "closed" : "open") + ">";
+ }
+};
+
+
+
+/** Returns an array of count bytes from the given input stream. */
+function readBytes(inputStream, count)
+{
+ return new BinaryInputStream(inputStream).readByteArray(count);
+}
+
+
+
+/** Request reader processing states; see RequestReader for details. */
+const READER_IN_REQUEST_LINE = 0;
+const READER_IN_HEADERS = 1;
+const READER_IN_BODY = 2;
+const READER_FINISHED = 3;
+
+
+/**
+* Reads incoming request data asynchronously, does any necessary preprocessing,
+* and forwards it to the request handler. Processing occurs in three states:
+*
+* READER_IN_REQUEST_LINE Reading the request's status line
+* READER_IN_HEADERS Reading headers in the request
+* READER_IN_BODY Reading the body of the request
+* READER_FINISHED Entire request has been read and processed
+*
+* During the first two stages, initial metadata about the request is gathered
+* into a Request object. Once the status line and headers have been processed,
+* we start processing the body of the request into the Request. Finally, when
+* the entire body has been read, we create a Response and hand it off to the
+* ServerHandler to be given to the appropriate request handler.
+*
+* @param connection : Connection
+* the connection for the request being read
+*/
+function RequestReader(connection)
+{
+ /** Connection metadata for this request. */
+ this._connection = connection;
+
+ /**
+* A container providing line-by-line access to the raw bytes that make up the
+* data which has been read from the connection but has not yet been acted
+* upon (by passing it to the request handler or by extracting request
+* metadata from it).
+*/
+ this._data = new LineData();
+
+ /**
+* The amount of data remaining to be read from the body of this request.
+* After all headers in the request have been read this is the value in the
+* Content-Length header, but as the body is read its value decreases to zero.
+*/
+ this._contentLength = 0;
+
+ /** The current state of parsing the incoming request. */
+ this._state = READER_IN_REQUEST_LINE;
+
+ /** Metadata constructed from the incoming request for the request handler. */
+ this._metadata = new Request(connection.port);
+
+ /**
+* Used to preserve state if we run out of line data midway through a
+* multi-line header. _lastHeaderName stores the name of the header, while
+* _lastHeaderValue stores the value we've seen so far for the header.
+*
+* These fields are always either both undefined or both strings.
+*/
+ this._lastHeaderName = this._lastHeaderValue = undefined;
+}
+RequestReader.prototype =
+{
+ // NSIINPUTSTREAMCALLBACK
+
+ /**
+* Called when more data from the incoming request is available. This method
+* then reads the available data from input and deals with that data as
+* necessary, depending upon the syntax of already-downloaded data.
+*
+* @param input : nsIAsyncInputStream
+* the stream of incoming data from the connection
+*/
+ onInputStreamReady: function(input)
+ {
+ dumpn("*** onInputStreamReady(input=" + input + ") on thread " +
+ gThreadManager.currentThread + " (main is " +
+ gThreadManager.mainThread + ")");
+ dumpn("*** this._state == " + this._state);
+
+ // Handle cases where we get more data after a request error has been
+ // discovered but *before* we can close the connection.
+ var data = this._data;
+ if (!data)
+ return;
+
+ try
+ {
+ data.appendBytes(readBytes(input, input.available()));
+ }
+ catch (e)
+ {
+ if (streamClosed(e))
+ {
+ dumpn("*** WARNING: unexpected error when reading from socket; will " +
+ "be treated as if the input stream had been closed");
+ dumpn("*** WARNING: actual error was: " + e);
+ }
+
+ // We've lost a race -- input has been closed, but we're still expecting
+ // to read more data. available() will throw in this case, and since
+ // we're dead in the water now, destroy the connection.
+ dumpn("*** onInputStreamReady called on a closed input, destroying " +
+ "connection");
+ this._connection.close();
+ return;
+ }
+
+ switch (this._state)
+ {
+ default:
+ NS_ASSERT(false, "invalid state: " + this._state);
+ break;
+
+ case READER_IN_REQUEST_LINE:
+ if (!this._processRequestLine())
+ break;
+ /* fall through */
+
+ case READER_IN_HEADERS:
+ if (!this._processHeaders())
+ break;
+ /* fall through */
+
+ case READER_IN_BODY:
+ this._processBody();
+ }
+
+ if (this._state != READER_FINISHED)
+ input.asyncWait(this, 0, 0, gThreadManager.currentThread);
+ },
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: function(aIID)
+ {
+ if (aIID.equals(Ci.nsIInputStreamCallback) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // PRIVATE API
+
+ /**
+* Processes unprocessed, downloaded data as a request line.
+*
+* @returns boolean
+* true iff the request line has been fully processed
+*/
+ _processRequestLine: function()
+ {
+ NS_ASSERT(this._state == READER_IN_REQUEST_LINE);
+
+ // Servers SHOULD ignore any empty line(s) received where a Request-Line
+ // is expected (section 4.1).
+ var data = this._data;
+ var line = {};
+ var readSuccess;
+ while ((readSuccess = data.readLine(line)) && line.value == "")
+ dumpn("*** ignoring beginning blank line...");
+
+ // if we don't have a full line, wait until we do
+ if (!readSuccess)
+ return false;
+
+ // we have the first non-blank line
+ try
+ {
+ this._parseRequestLine(line.value);
+ this._state = READER_IN_HEADERS;
+ return true;
+ }
+ catch (e)
+ {
+ this._handleError(e);
+ return false;
+ }
+ },
+
+ /**
+* Processes stored data, assuming it is either at the beginning or in
+* the middle of processing request headers.
+*
+* @returns boolean
+* true iff header data in the request has been fully processed
+*/
+ _processHeaders: function()
+ {
+ NS_ASSERT(this._state == READER_IN_HEADERS);
+
+ // XXX things to fix here:
+ //
+ // - need to support RFC 2047-encoded non-US-ASCII characters
+
+ try
+ {
+ var done = this._parseHeaders();
+ if (done)
+ {
+ var request = this._metadata;
+
+ // XXX this is wrong for requests with transfer-encodings applied to
+ // them, particularly chunked (which by its nature can have no
+ // meaningful Content-Length header)!
+ this._contentLength = request.hasHeader("Content-Length")
+ ? parseInt(request.getHeader("Content-Length"), 10)
+ : 0;
+ dumpn("_processHeaders, Content-length=" + this._contentLength);
+
+ this._state = READER_IN_BODY;
+ }
+ return done;
+ }
+ catch (e)
+ {
+ this._handleError(e);
+ return false;
+ }
+ },
+
+ /**
+* Processes stored data, assuming it is either at the beginning or in
+* the middle of processing the request body.
+*
+* @returns boolean
+* true iff the request body has been fully processed
+*/
+ _processBody: function()
+ {
+ NS_ASSERT(this._state == READER_IN_BODY);
+
+ // XXX handle chunked transfer-coding request bodies!
+
+ try
+ {
+ if (this._contentLength > 0)
+ {
+ var data = this._data.purge();
+ var count = Math.min(data.length, this._contentLength);
+ dumpn("*** loading data=" + data + " len=" + data.length +
+ " excess=" + (data.length - count));
+
+ var bos = new BinaryOutputStream(this._metadata._bodyOutputStream);
+ bos.writeByteArray(data, count);
+ this._contentLength -= count;
+ }
+
+ dumpn("*** remaining body data len=" + this._contentLength);
+ if (this._contentLength == 0)
+ {
+ this._validateRequest();
+ this._state = READER_FINISHED;
+ this._handleResponse();
+ return true;
+ }
+
+ return false;
+ }
+ catch (e)
+ {
+ this._handleError(e);
+ return false;
+ }
+ },
+
+ /**
+* Does various post-header checks on the data in this request.
+*
+* @throws : HttpError
+* if the request was malformed in some way
+*/
+ _validateRequest: function()
+ {
+ NS_ASSERT(this._state == READER_IN_BODY);
+
+ dumpn("*** _validateRequest");
+
+ var metadata = this._metadata;
+ var headers = metadata._headers;
+
+ // 19.6.1.1 -- servers MUST report 400 to HTTP/1.1 requests w/o Host header
+ var identity = this._connection.server.identity;
+ if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1))
+ {
+ if (!headers.hasHeader("Host"))
+ {
+ dumpn("*** malformed HTTP/1.1 or greater request with no Host header!");
+ throw HTTP_400;
+ }
+
+ // If the Request-URI wasn't absolute, then we need to determine our host.
+ // We have to determine what scheme was used to access us based on the
+ // server identity data at this point, because the request just doesn't
+ // contain enough data on its own to do this, sadly.
+ if (!metadata._host)
+ {
+ var host, port;
+ var hostPort = headers.getHeader("Host");
+ var colon = hostPort.indexOf(":");
+ if (colon < 0)
+ {
+ host = hostPort;
+ port = "";
+ }
+ else
+ {
+ host = hostPort.substring(0, colon);
+ port = hostPort.substring(colon + 1);
+ }
+
+ // NB: We allow an empty port here because, oddly, a colon may be
+ // present even without a port number, e.g. "example.com:"; in this
+ // case the default port applies.
+ if (!HOST_REGEX.test(host) || !/^\d*$/.test(port))
+ {
+ dumpn("*** malformed hostname (" + hostPort + ") in Host " +
+ "header, 400 time");
+ throw HTTP_400;
+ }
+
+ // If we're not given a port, we're stuck, because we don't know what
+ // scheme to use to look up the correct port here, in general. Since
+ // the HTTPS case requires a tunnel/proxy and thus requires that the
+ // requested URI be absolute (and thus contain the necessary
+ // information), let's assume HTTP will prevail and use that.
+ port = +port || 80;
+
+ var scheme = identity.getScheme(host, port);
+ if (!scheme)
+ {
+ dumpn("*** unrecognized hostname (" + hostPort + ") in Host " +
+ "header, 400 time");
+ throw HTTP_400;
+ }
+
+ metadata._scheme = scheme;
+ metadata._host = host;
+ metadata._port = port;
+ }
+ }
+ else
+ {
+ NS_ASSERT(metadata._host === undefined,
+ "HTTP/1.0 doesn't allow absolute paths in the request line!");
+
+ metadata._scheme = identity.primaryScheme;
+ metadata._host = identity.primaryHost;
+ metadata._port = identity.primaryPort;
+ }
+
+ NS_ASSERT(identity.has(metadata._scheme, metadata._host, metadata._port),
+ "must have a location we recognize by now!");
+ },
+
+ /**
+* Handles responses in case of error, either in the server or in the request.
+*
+* @param e
+* the specific error encountered, which is an HttpError in the case where
+* the request is in some way invalid or cannot be fulfilled; if this isn't
+* an HttpError we're going to be paranoid and shut down, because that
+* shouldn't happen, ever
+*/
+ _handleError: function(e)
+ {
+ // Don't fall back into normal processing!
+ this._state = READER_FINISHED;
+
+ var server = this._connection.server;
+ if (e instanceof HttpError)
+ {
+ var code = e.code;
+ }
+ else
+ {
+ dumpn("!!! UNEXPECTED ERROR: " + e +
+ (e.lineNumber ? ", line " + e.lineNumber : ""));
+
+ // no idea what happened -- be paranoid and shut down
+ code = 500;
+ server._requestQuit();
+ }
+
+ // make attempted reuse of data an error
+ this._data = null;
+
+ this._connection.processError(code, this._metadata);
+ },
+
+ /**
+* Now that we've read the request line and headers, we can actually hand off
+* the request to be handled.
+*
+* This method is called once per request, after the request line and all
+* headers and the body, if any, have been received.
+*/
+ _handleResponse: function()
+ {
+ NS_ASSERT(this._state == READER_FINISHED);
+
+ // We don't need the line-based data any more, so make attempted reuse an
+ // error.
+ this._data = null;
+
+ this._connection.process(this._metadata);
+ },
+
+
+ // PARSING
+
+ /**
+* Parses the request line for the HTTP request associated with this.
+*
+* @param line : string
+* the request line
+*/
+ _parseRequestLine: function(line)
+ {
+ NS_ASSERT(this._state == READER_IN_REQUEST_LINE);
+
+ dumpn("*** _parseRequestLine('" + line + "')");
+
+ var metadata = this._metadata;
+
+ // clients and servers SHOULD accept any amount of SP or HT characters
+ // between fields, even though only a single SP is required (section 19.3)
+ var request = line.split(/[ \t]+/);
+ if (!request || request.length != 3)
+ throw HTTP_400;
+
+ metadata._method = request[0];
+
+ // get the HTTP version
+ var ver = request[2];
+ var match = ver.match(/^HTTP\/(\d+\.\d+)$/);
+ if (!match)
+ throw HTTP_400;
+
+ // determine HTTP version
+ try
+ {
+ metadata._httpVersion = new nsHttpVersion(match[1]);
+ if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_0))
+ throw "unsupported HTTP version";
+ }
+ catch (e)
+ {
+ // we support HTTP/1.0 and HTTP/1.1 only
+ throw HTTP_501;
+ }
+
+
+ var fullPath = request[1];
+ var serverIdentity = this._connection.server.identity;
+
+ var scheme, host, port;
+
+ if (fullPath.charAt(0) != "/")
+ {
+ // No absolute paths in the request line in HTTP prior to 1.1
+ if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1))
+ throw HTTP_400;
+
+ try
+ {
+ var uri = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService)
+ .newURI(fullPath, null, null);
+ fullPath = uri.path;
+ scheme = uri.scheme;
+ host = metadata._host = uri.asciiHost;
+ port = uri.port;
+ if (port === -1)
+ {
+ if (scheme === "http")
+ port = 80;
+ else if (scheme === "https")
+ port = 443;
+ else
+ throw HTTP_400;
+ }
+ }
+ catch (e)
+ {
+ // If the host is not a valid host on the server, the response MUST be a
+ // 400 (Bad Request) error message (section 5.2). Alternately, the URI
+ // is malformed.
+ throw HTTP_400;
+ }
+
+ if (!serverIdentity.has(scheme, host, port) || fullPath.charAt(0) != "/")
+ throw HTTP_400;
+ }
+
+ var splitter = fullPath.indexOf("?");
+ if (splitter < 0)
+ {
+ // _queryString already set in ctor
+ metadata._path = fullPath;
+ }
+ else
+ {
+ metadata._path = fullPath.substring(0, splitter);
+ metadata._queryString = fullPath.substring(splitter + 1);
+ }
+
+ metadata._scheme = scheme;
+ metadata._host = host;
+ metadata._port = port;
+ },
+
+ /**
+* Parses all available HTTP headers in this until the header-ending CRLFCRLF,
+* adding them to the store of headers in the request.
+*
+* @throws
+* HTTP_400 if the headers are malformed
+* @returns boolean
+* true if all headers have now been processed, false otherwise
+*/
+ _parseHeaders: function()
+ {
+ NS_ASSERT(this._state == READER_IN_HEADERS);
+
+ dumpn("*** _parseHeaders");
+
+ var data = this._data;
+
+ var headers = this._metadata._headers;
+ var lastName = this._lastHeaderName;
+ var lastVal = this._lastHeaderValue;
+
+ var line = {};
+ while (true)
+ {
+ NS_ASSERT(!((lastVal === undefined) ^ (lastName === undefined)),
+ lastName === undefined ?
+ "lastVal without lastName? lastVal: '" + lastVal + "'" :
+ "lastName without lastVal? lastName: '" + lastName + "'");
+
+ if (!data.readLine(line))
+ {
+ // save any data we have from the header we might still be processing
+ this._lastHeaderName = lastName;
+ this._lastHeaderValue = lastVal;
+ return false;
+ }
+
+ var lineText = line.value;
+ var firstChar = lineText.charAt(0);
+
+ // blank line means end of headers
+ if (lineText == "")
+ {
+ // we're finished with the previous header
+ if (lastName)
+ {
+ try
+ {
+ headers.setHeader(lastName, lastVal, true);
+ }
+ catch (e)
+ {
+ dumpn("*** e == " + e);
+ throw HTTP_400;
+ }
+ }
+ else
+ {
+ // no headers in request -- valid for HTTP/1.0 requests
+ }
+
+ // either way, we're done processing headers
+ this._state = READER_IN_BODY;
+ return true;
+ }
+ else if (firstChar == " " || firstChar == "\t")
+ {
+ // multi-line header if we've already seen a header line
+ if (!lastName)
+ {
+ // we don't have a header to continue!
+ throw HTTP_400;
+ }
+
+ // append this line's text to the value; starts with SP/HT, so no need
+ // for separating whitespace
+ lastVal += lineText;
+ }
+ else
+ {
+ // we have a new header, so set the old one (if one existed)
+ if (lastName)
+ {
+ try
+ {
+ headers.setHeader(lastName, lastVal, true);
+ }
+ catch (e)
+ {
+ dumpn("*** e == " + e);
+ throw HTTP_400;
+ }
+ }
+
+ var colon = lineText.indexOf(":"); // first colon must be splitter
+ if (colon < 1)
+ {
+ // no colon or missing header field-name
+ throw HTTP_400;
+ }
+
+ // set header name, value (to be set in the next loop, usually)
+ lastName = lineText.substring(0, colon);
+ lastVal = lineText.substring(colon + 1);
+ } // empty, continuation, start of header
+ } // while (true)
+ }
+};
+
+
+/** The character codes for CR and LF. */
+const CR = 0x0D, LF = 0x0A;
+
+/**
+* Calculates the number of characters before the first CRLF pair in array, or
+* -1 if the array contains no CRLF pair.
+*
+* @param array : Array
+* an array of numbers in the range [0, 256), each representing a single
+* character; the first CRLF is the lowest index i where
+* |array[i] == "\r".charCodeAt(0)| and |array[i+1] == "\n".charCodeAt(0)|,
+* if such an |i| exists, and -1 otherwise
+* @returns int
+* the index of the first CRLF if any were present, -1 otherwise
+*/
+function findCRLF(array)
+{
+ for (var i = array.indexOf(CR); i >= 0; i = array.indexOf(CR, i + 1))
+ {
+ if (array[i + 1] == LF)
+ return i;
+ }
+ return -1;
+}
+
+
+/**
+* A container which provides line-by-line access to the arrays of bytes with
+* which it is seeded.
+*/
+function LineData()
+{
+ /** An array of queued bytes from which to get line-based characters. */
+ this._data = [];
+}
+LineData.prototype =
+{
+ /**
+* Appends the bytes in the given array to the internal data cache maintained
+* by this.
+*/
+ appendBytes: function(bytes)
+ {
+ Array.prototype.push.apply(this._data, bytes);
+ },
+
+ /**
+* Removes and returns a line of data, delimited by CRLF, from this.
+*
+* @param out
+* an object whose "value" property will be set to the first line of text
+* present in this, sans CRLF, if this contains a full CRLF-delimited line
+* of text; if this doesn't contain enough data, the value of the property
+* is undefined
+* @returns boolean
+* true if a full line of data could be read from the data in this, false
+* otherwise
+*/
+ readLine: function(out)
+ {
+ var data = this._data;
+ var length = findCRLF(data);
+ if (length < 0)
+ return false;
+
+ //
+ // We have the index of the CR, so remove all the characters, including
+ // CRLF, from the array with splice, and convert the removed array into the
+ // corresponding string, from which we then strip the trailing CRLF.
+ //
+ // Getting the line in this matter acknowledges that substring is an O(1)
+ // operation in SpiderMonkey because strings are immutable, whereas two
+ // splices, both from the beginning of the data, are less likely to be as
+ // cheap as a single splice plus two extra character conversions.
+ //
+ var line = String.fromCharCode.apply(null, data.splice(0, length + 2));
+ out.value = line.substring(0, length);
+
+ return true;
+ },
+
+ /**
+* Removes the bytes currently within this and returns them in an array.
+*
+* @returns Array
+* the bytes within this when this method is called
+*/
+ purge: function()
+ {
+ var data = this._data;
+ this._data = [];
+ return data;
+ }
+};
+
+
+
+/**
+* Creates a request-handling function for an nsIHttpRequestHandler object.
+*/
+function createHandlerFunc(handler)
+{
+ return function(metadata, response) { handler.handle(metadata, response); };
+}
+
+
+/**
+* The default handler for directories; writes an HTML response containing a
+* slightly-formatted directory listing.
+*/
+function defaultIndexHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/html", false);
+
+ var path = htmlEscape(decodeURI(metadata.path));
+
+ //
+ // Just do a very basic bit of directory listings -- no need for too much
+ // fanciness, especially since we don't have a style sheet in which we can
+ // stick rules (don't want to pollute the default path-space).
+ //
+
+ var body = '<html>\
+<head>\
+<title>' + path + '</title>\
+</head>\
+<body>\
+<h1>' + path + '</h1>\
+<ol style="list-style-type: none">';
+
+ var directory = metadata.getProperty("directory").QueryInterface(Ci.nsILocalFile);
+ NS_ASSERT(directory && directory.isDirectory());
+
+ var fileList = [];
+ var files = directory.directoryEntries;
+ while (files.hasMoreElements())
+ {
+ var f = files.getNext().QueryInterface(Ci.nsIFile);
+ var name = f.leafName;
+ if (!f.isHidden() &&
+ (name.charAt(name.length - 1) != HIDDEN_CHAR ||
+ name.charAt(name.length - 2) == HIDDEN_CHAR))
+ fileList.push(f);
+ }
+
+ fileList.sort(fileSort);
+
+ for (var i = 0; i < fileList.length; i++)
+ {
+ var file = fileList[i];
+ try
+ {
+ var name = file.leafName;
+ if (name.charAt(name.length - 1) == HIDDEN_CHAR)
+ name = name.substring(0, name.length - 1);
+ var sep = file.isDirectory() ? "/" : "";
+
+ // Note: using " to delimit the attribute here because encodeURIComponent
+ // passes through '.
+ var item = '<li><a href="' + encodeURIComponent(name) + sep + '">' +
+ htmlEscape(name) + sep +
+ '</a></li>';
+
+ body += item;
+ }
+ catch (e) { /* some file system error, ignore the file */ }
+ }
+
+ body += ' </ol>\
+</body>\
+</html>';
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+/**
+* Sorts a and b (nsIFile objects) into an aesthetically pleasing order.
+*/
+function fileSort(a, b)
+{
+ var dira = a.isDirectory(), dirb = b.isDirectory();
+
+ if (dira && !dirb)
+ return -1;
+ if (dirb && !dira)
+ return 1;
+
+ var namea = a.leafName.toLowerCase(), nameb = b.leafName.toLowerCase();
+ return nameb > namea ? -1 : 1;
+}
+
+
+/**
+* Converts an externally-provided path into an internal path for use in
+* determining file mappings.
+*
+* @param path
+* the path to convert
+* @param encoded
+* true if the given path should be passed through decodeURI prior to
+* conversion
+* @throws URIError
+* if path is incorrectly encoded
+*/
+function toInternalPath(path, encoded)
+{
+ if (encoded)
+ path = decodeURI(path);
+
+ var comps = path.split("/");
+ for (var i = 0, sz = comps.length; i < sz; i++)
+ {
+ var comp = comps[i];
+ if (comp.charAt(comp.length - 1) == HIDDEN_CHAR)
+ comps[i] = comp + HIDDEN_CHAR;
+ }
+ return comps.join("/");
+}
+
+
+/**
+* Adds custom-specified headers for the given file to the given response, if
+* any such headers are specified.
+*
+* @param file
+* the file on the disk which is to be written
+* @param metadata
+* metadata about the incoming request
+* @param response
+* the Response to which any specified headers/data should be written
+* @throws HTTP_500
+* if an error occurred while processing custom-specified headers
+*/
+function maybeAddHeaders(file, metadata, response)
+{
+ var name = file.leafName;
+ if (name.charAt(name.length - 1) == HIDDEN_CHAR)
+ name = name.substring(0, name.length - 1);
+
+ var headerFile = file.parent;
+ headerFile.append(name + HEADERS_SUFFIX);
+
+ if (!headerFile.exists())
+ return;
+
+ const PR_RDONLY = 0x01;
+ var fis = new FileInputStream(headerFile, PR_RDONLY, parseInt("444", 8),
+ Ci.nsIFileInputStream.CLOSE_ON_EOF);
+
+ try
+ {
+ var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0);
+ lis.QueryInterface(Ci.nsIUnicharLineInputStream);
+
+ var line = {value: ""};
+ var more = lis.readLine(line);
+
+ if (!more && line.value == "")
+ return;
+
+
+ // request line
+
+ var status = line.value;
+ if (status.indexOf("HTTP ") == 0)
+ {
+ status = status.substring(5);
+ var space = status.indexOf(" ");
+ var code, description;
+ if (space < 0)
+ {
+ code = status;
+ description = "";
+ }
+ else
+ {
+ code = status.substring(0, space);
+ description = status.substring(space + 1, status.length);
+ }
+
+ response.setStatusLine(metadata.httpVersion, parseInt(code, 10), description);
+
+ line.value = "";
+ more = lis.readLine(line);
+ }
+
+ // headers
+ while (more || line.value != "")
+ {
+ var header = line.value;
+ var colon = header.indexOf(":");
+
+ response.setHeader(header.substring(0, colon),
+ header.substring(colon + 1, header.length),
+ false); // allow overriding server-set headers
+
+ line.value = "";
+ more = lis.readLine(line);
+ }
+ }
+ catch (e)
+ {
+ dumpn("WARNING: error in headers for " + metadata.path + ": " + e);
+ throw HTTP_500;
+ }
+ finally
+ {
+ fis.close();
+ }
+}
+
+
+/**
+* An object which handles requests for a server, executing default and
+* overridden behaviors as instructed by the code which uses and manipulates it.
+* Default behavior includes the paths / and /trace (diagnostics), with some
+* support for HTTP error pages for various codes and fallback to HTTP 500 if
+* those codes fail for any reason.
+*
+* @param server : nsHttpServer
+* the server in which this handler is being used
+*/
+function ServerHandler(server)
+{
+ // FIELDS
+
+ /**
+* The nsHttpServer instance associated with this handler.
+*/
+ this._server = server;
+
+ /**
+* A FileMap object containing the set of path->nsILocalFile mappings for
+* all directory mappings set in the server (e.g., "/" for /var/www/html/,
+* "/foo/bar/" for /local/path/, and "/foo/bar/baz/" for /local/path2).
+*
+* Note carefully: the leading and trailing "/" in each path (not file) are
+* removed before insertion to simplify the code which uses this. You have
+* been warned!
+*/
+ this._pathDirectoryMap = new FileMap();
+
+ /**
+* Custom request handlers for the server in which this resides. Path-handler
+* pairs are stored as property-value pairs in this property.
+*
+* @see ServerHandler.prototype._defaultPaths
+*/
+ this._overridePaths = {};
+
+ /**
+* Custom request handlers for the error handlers in the server in which this
+* resides. Path-handler pairs are stored as property-value pairs in this
+* property.
+*
+* @see ServerHandler.prototype._defaultErrors
+*/
+ this._overrideErrors = {};
+
+ /**
+* Maps file extensions to their MIME types in the server, overriding any
+* mapping that might or might not exist in the MIME service.
+*/
+ this._mimeMappings = {};
+
+ /**
+* The default handler for requests for directories, used to serve directories
+* when no index file is present.
+*/
+ this._indexHandler = defaultIndexHandler;
+
+ /** Per-path state storage for the server. */
+ this._state = {};
+
+ /** Entire-server state storage. */
+ this._sharedState = {};
+
+ /** Entire-server state storage for nsISupports values. */
+ this._objectState = {};
+}
+ServerHandler.prototype =
+{
+ // PUBLIC API
+
+ /**
+* Handles a request to this server, responding to the request appropriately
+* and initiating server shutdown if necessary.
+*
+* This method never throws an exception.
+*
+* @param connection : Connection
+* the connection for this request
+*/
+ handleResponse: function(connection)
+ {
+ var request = connection.request;
+ var response = new Response(connection);
+
+ var path = request.path;
+ dumpn("*** path == " + path);
+
+ try
+ {
+ try
+ {
+ if (path in this._overridePaths)
+ {
+ // explicit paths first, then files based on existing directory mappings,
+ // then (if the file doesn't exist) built-in server default paths
+ dumpn("calling override for " + path);
+ this._overridePaths[path](request, response);
+ }
+ else
+ {
+ this._handleDefault(request, response);
+ }
+ }
+ catch (e)
+ {
+ if (response.partiallySent())
+ {
+ response.abort(e);
+ return;
+ }
+
+ if (!(e instanceof HttpError))
+ {
+ dumpn("*** unexpected error: e == " + e);
+ throw HTTP_500;
+ }
+ if (e.code !== 404)
+ throw e;
+
+ dumpn("*** default: " + (path in this._defaultPaths));
+
+ response = new Response(connection);
+ if (path in this._defaultPaths)
+ this._defaultPaths[path](request, response);
+ else
+ throw HTTP_404;
+ }
+ }
+ catch (e)
+ {
+ if (response.partiallySent())
+ {
+ response.abort(e);
+ return;
+ }
+
+ var errorCode = "internal";
+
+ try
+ {
+ if (!(e instanceof HttpError))
+ throw e;
+
+ errorCode = e.code;
+ dumpn("*** errorCode == " + errorCode);
+
+ response = new Response(connection);
+ if (e.customErrorHandling)
+ e.customErrorHandling(response);
+ this._handleError(errorCode, request, response);
+ return;
+ }
+ catch (e2)
+ {
+ dumpn("*** error handling " + errorCode + " error: " +
+ "e2 == " + e2 + ", shutting down server");
+
+ connection.server._requestQuit();
+ response.abort(e2);
+ return;
+ }
+ }
+
+ response.complete();
+ },
+
+ //
+ // see nsIHttpServer.registerFile
+ //
+ registerFile: function(path, file)
+ {
+ if (!file)
+ {
+ dumpn("*** unregistering '" + path + "' mapping");
+ delete this._overridePaths[path];
+ return;
+ }
+
+ dumpn("*** registering '" + path + "' as mapping to " + file.path);
+ file = file.clone();
+
+ var self = this;
+ this._overridePaths[path] =
+ function(request, response)
+ {
+ if (!file.exists())
+ throw HTTP_404;
+
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ self._writeFileResponse(request, file, response, 0, file.fileSize);
+ };
+ },
+
+ //
+ // see nsIHttpServer.registerPathHandler
+ //
+ registerPathHandler: function(path, handler)
+ {
+ // XXX true path validation!
+ if (path.charAt(0) != "/")
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ this._handlerToField(handler, this._overridePaths, path);
+ },
+
+ //
+ // see nsIHttpServer.registerDirectory
+ //
+ registerDirectory: function(path, directory)
+ {
+ // strip off leading and trailing '/' so that we can use lastIndexOf when
+ // determining exactly how a path maps onto a mapped directory --
+ // conditional is required here to deal with "/".substring(1, 0) being
+ // converted to "/".substring(0, 1) per the JS specification
+ var key = path.length == 1 ? "" : path.substring(1, path.length - 1);
+
+ // the path-to-directory mapping code requires that the first character not
+ // be "/", or it will go into an infinite loop
+ if (key.charAt(0) == "/")
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ key = toInternalPath(key, false);
+
+ if (directory)
+ {
+ dumpn("*** mapping '" + path + "' to the location " + directory.path);
+ this._pathDirectoryMap.put(key, directory);
+ }
+ else
+ {
+ dumpn("*** removing mapping for '" + path + "'");
+ this._pathDirectoryMap.put(key, null);
+ }
+ },
+
+ //
+ // see nsIHttpServer.registerErrorHandler
+ //
+ registerErrorHandler: function(err, handler)
+ {
+ if (!(err in HTTP_ERROR_CODES))
+ dumpn("*** WARNING: registering non-HTTP/1.1 error code " +
+ "(" + err + ") handler -- was this intentional?");
+
+ this._handlerToField(handler, this._overrideErrors, err);
+ },
+
+ //
+ // see nsIHttpServer.setIndexHandler
+ //
+ setIndexHandler: function(handler)
+ {
+ if (!handler)
+ handler = defaultIndexHandler;
+ else if (typeof(handler) != "function")
+ handler = createHandlerFunc(handler);
+
+ this._indexHandler = handler;
+ },
+
+ //
+ // see nsIHttpServer.registerContentType
+ //
+ registerContentType: function(ext, type)
+ {
+ if (!type)
+ delete this._mimeMappings[ext];
+ else
+ this._mimeMappings[ext] = headerUtils.normalizeFieldValue(type);
+ },
+
+ // PRIVATE API
+
+ /**
+* Sets or remove (if handler is null) a handler in an object with a key.
+*
+* @param handler
+* a handler, either function or an nsIHttpRequestHandler
+* @param dict
+* The object to attach the handler to.
+* @param key
+* The field name of the handler.
+*/
+ _handlerToField: function(handler, dict, key)
+ {
+ // for convenience, handler can be a function if this is run from xpcshell
+ if (typeof(handler) == "function")
+ dict[key] = handler;
+ else if (handler)
+ dict[key] = createHandlerFunc(handler);
+ else
+ delete dict[key];
+ },
+
+ /**
+* Handles a request which maps to a file in the local filesystem (if a base
+* path has already been set; otherwise the 404 error is thrown).
+*
+* @param metadata : Request
+* metadata for the incoming request
+* @param response : Response
+* an uninitialized Response to the given request, to be initialized by a
+* request handler
+* @throws HTTP_###
+* if an HTTP error occurred (usually HTTP_404); note that in this case the
+* calling code must handle post-processing of the response
+*/
+ _handleDefault: function(metadata, response)
+ {
+ dumpn("*** _handleDefault()");
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+
+ var path = metadata.path;
+ NS_ASSERT(path.charAt(0) == "/", "invalid path: <" + path + ">");
+
+ // determine the actual on-disk file; this requires finding the deepest
+ // path-to-directory mapping in the requested URL
+ var file = this._getFileForPath(path);
+
+ // the "file" might be a directory, in which case we either serve the
+ // contained index.html or make the index handler write the response
+ if (file.exists() && file.isDirectory())
+ {
+ file.append("index.html"); // make configurable?
+ if (!file.exists() || file.isDirectory())
+ {
+ metadata._ensurePropertyBag();
+ metadata._bag.setPropertyAsInterface("directory", file.parent);
+ this._indexHandler(metadata, response);
+ return;
+ }
+ }
+
+ // alternately, the file might not exist
+ if (!file.exists())
+ throw HTTP_404;
+
+ var start, end;
+ if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1) &&
+ metadata.hasHeader("Range") &&
+ this._getTypeFromFile(file) !== SJS_TYPE)
+ {
+ var rangeMatch = metadata.getHeader("Range").match(/^bytes=(\d+)?-(\d+)?$/);
+ if (!rangeMatch)
+ throw HTTP_400;
+
+ if (rangeMatch[1] !== undefined)
+ start = parseInt(rangeMatch[1], 10);
+
+ if (rangeMatch[2] !== undefined)
+ end = parseInt(rangeMatch[2], 10);
+
+ if (start === undefined && end === undefined)
+ throw HTTP_400;
+
+ // No start given, so the end is really the count of bytes from the
+ // end of the file.
+ if (start === undefined)
+ {
+ start = Math.max(0, file.fileSize - end);
+ end = file.fileSize - 1;
+ }
+
+ // start and end are inclusive
+ if (end === undefined || end >= file.fileSize)
+ end = file.fileSize - 1;
+
+ if (start !== undefined && start >= file.fileSize) {
+ var HTTP_416 = new HttpError(416, "Requested Range Not Satisfiable");
+ HTTP_416.customErrorHandling = function(errorResponse)
+ {
+ maybeAddHeaders(file, metadata, errorResponse);
+ };
+ throw HTTP_416;
+ }
+
+ if (end < start)
+ {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ start = 0;
+ end = file.fileSize - 1;
+ }
+ else
+ {
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ var contentRange = "bytes " + start + "-" + end + "/" + file.fileSize;
+ response.setHeader("Content-Range", contentRange);
+ }
+ }
+ else
+ {
+ start = 0;
+ end = file.fileSize - 1;
+ }
+
+ // finally...
+ dumpn("*** handling '" + path + "' as mapping to " + file.path + " from " +
+ start + " to " + end + " inclusive");
+ this._writeFileResponse(metadata, file, response, start, end - start + 1);
+ },
+
+ /**
+* Writes an HTTP response for the given file, including setting headers for
+* file metadata.
+*
+* @param metadata : Request
+* the Request for which a response is being generated
+* @param file : nsILocalFile
+* the file which is to be sent in the response
+* @param response : Response
+* the response to which the file should be written
+* @param offset: uint
+* the byte offset to skip to when writing
+* @param count: uint
+* the number of bytes to write
+*/
+ _writeFileResponse: function(metadata, file, response, offset, count)
+ {
+ const PR_RDONLY = 0x01;
+
+ var type = this._getTypeFromFile(file);
+ if (type === SJS_TYPE)
+ {
+ var fis = new FileInputStream(file, PR_RDONLY, parseInt("444", 8),
+ Ci.nsIFileInputStream.CLOSE_ON_EOF);
+
+ try
+ {
+ var sis = new ScriptableInputStream(fis);
+ var s = Cu.Sandbox(gGlobalObject);
+ s.importFunction(dump, "dump");
+
+ // Define a basic key-value state-preservation API across requests, with
+ // keys initially corresponding to the empty string.
+ var self = this;
+ var path = metadata.path;
+ s.importFunction(function getState(k)
+ {
+ return self._getState(path, k);
+ });
+ s.importFunction(function setState(k, v)
+ {
+ self._setState(path, k, v);
+ });
+ s.importFunction(function getSharedState(k)
+ {
+ return self._getSharedState(k);
+ });
+ s.importFunction(function setSharedState(k, v)
+ {
+ self._setSharedState(k, v);
+ });
+ s.importFunction(function getObjectState(k, callback)
+ {
+ callback(self._getObjectState(k));
+ });
+ s.importFunction(function setObjectState(k, v)
+ {
+ self._setObjectState(k, v);
+ });
+ s.importFunction(function registerPathHandler(p, h)
+ {
+ self.registerPathHandler(p, h);
+ });
+
+ // Make it possible for sjs files to access their location
+ this._setState(path, "__LOCATION__", file.path);
+
+ try
+ {
+ // Alas, the line number in errors dumped to console when calling the
+ // request handler is simply an offset from where we load the SJS file.
+ // Work around this in a reasonably non-fragile way by dynamically
+ // getting the line number where we evaluate the SJS file. Don't
+ // separate these two lines!
+ var line = new Error().lineNumber;
+ Cu.evalInSandbox(sis.read(file.fileSize), s);
+ }
+ catch (e)
+ {
+ dumpn("*** syntax error in SJS at " + file.path + ": " + e);
+ throw HTTP_500;
+ }
+
+ try
+ {
+ s.handleRequest(metadata, response);
+ }
+ catch (e)
+ {
+ dump("*** error running SJS at " + file.path + ": " +
+ e + " on line " +
+ (e instanceof Error
+ ? e.lineNumber + " in httpd.js"
+ : (e.lineNumber - line)) + "\n");
+ throw HTTP_500;
+ }
+ }
+ finally
+ {
+ fis.close();
+ }
+ }
+ else
+ {
+ try
+ {
+ response.setHeader("Last-Modified",
+ toDateString(file.lastModifiedTime),
+ false);
+ }
+ catch (e) { /* lastModifiedTime threw, ignore */ }
+
+ response.setHeader("Content-Type", type, false);
+ maybeAddHeaders(file, metadata, response);
+ response.setHeader("Content-Length", "" + count, false);
+
+ var fis = new FileInputStream(file, PR_RDONLY, parseInt("444", 8),
+ Ci.nsIFileInputStream.CLOSE_ON_EOF);
+
+ offset = offset || 0;
+ count = count || file.fileSize;
+ NS_ASSERT(offset === 0 || offset < file.fileSize, "bad offset");
+ NS_ASSERT(count >= 0, "bad count");
+ NS_ASSERT(offset + count <= file.fileSize, "bad total data size");
+
+ try
+ {
+ if (offset !== 0)
+ {
+ // Seek (or read, if seeking isn't supported) to the correct offset so
+ // the data sent to the client matches the requested range.
+ if (fis instanceof Ci.nsISeekableStream)
+ fis.seek(Ci.nsISeekableStream.NS_SEEK_SET, offset);
+ else
+ new ScriptableInputStream(fis).read(offset);
+ }
+ }
+ catch (e)
+ {
+ fis.close();
+ throw e;
+ }
+
+ function writeMore()
+ {
+ gThreadManager.currentThread
+ .dispatch(writeData, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+
+ var input = new BinaryInputStream(fis);
+ var output = new BinaryOutputStream(response.bodyOutputStream);
+ var writeData =
+ {
+ run: function()
+ {
+ var chunkSize = Math.min(65536, count);
+ count -= chunkSize;
+ NS_ASSERT(count >= 0, "underflow");
+
+ try
+ {
+ var data = input.readByteArray(chunkSize);
+ NS_ASSERT(data.length === chunkSize,
+ "incorrect data returned? got " + data.length +
+ ", expected " + chunkSize);
+ output.writeByteArray(data, data.length);
+ if (count === 0)
+ {
+ fis.close();
+ response.finish();
+ }
+ else
+ {
+ writeMore();
+ }
+ }
+ catch (e)
+ {
+ try
+ {
+ fis.close();
+ }
+ finally
+ {
+ response.finish();
+ }
+ throw e;
+ }
+ }
+ };
+
+ writeMore();
+
+ // Now that we know copying will start, flag the response as async.
+ response.processAsync();
+ }
+ },
+
+ /**
+* Get the value corresponding to a given key for the given path for SJS state
+* preservation across requests.
+*
+* @param path : string
+* the path from which the given state is to be retrieved
+* @param k : string
+* the key whose corresponding value is to be returned
+* @returns string
+* the corresponding value, which is initially the empty string
+*/
+ _getState: function(path, k)
+ {
+ var state = this._state;
+ if (path in state && k in state[path])
+ return state[path][k];
+ return "";
+ },
+
+ /**
+* Set the value corresponding to a given key for the given path for SJS state
+* preservation across requests.
+*
+* @param path : string
+* the path from which the given state is to be retrieved
+* @param k : string
+* the key whose corresponding value is to be set
+* @param v : string
+* the value to be set
+*/
+ _setState: function(path, k, v)
+ {
+ if (typeof v !== "string")
+ throw new Error("non-string value passed");
+ var state = this._state;
+ if (!(path in state))
+ state[path] = {};
+ state[path][k] = v;
+ },
+
+ /**
+* Get the value corresponding to a given key for SJS state preservation
+* across requests.
+*
+* @param k : string
+* the key whose corresponding value is to be returned
+* @returns string
+* the corresponding value, which is initially the empty string
+*/
+ _getSharedState: function(k)
+ {
+ var state = this._sharedState;
+ if (k in state)
+ return state[k];
+ return "";
+ },
+
+ /**
+* Set the value corresponding to a given key for SJS state preservation
+* across requests.
+*
+* @param k : string
+* the key whose corresponding value is to be set
+* @param v : string
+* the value to be set
+*/
+ _setSharedState: function(k, v)
+ {
+ if (typeof v !== "string")
+ throw new Error("non-string value passed");
+ this._sharedState[k] = v;
+ },
+
+ /**
+* Returns the object associated with the given key in the server for SJS
+* state preservation across requests.
+*
+* @param k : string
+* the key whose corresponding object is to be returned
+* @returns nsISupports
+* the corresponding object, or null if none was present
+*/
+ _getObjectState: function(k)
+ {
+ if (typeof k !== "string")
+ throw new Error("non-string key passed");
+ return this._objectState[k] || null;
+ },
+
+ /**
+* Sets the object associated with the given key in the server for SJS
+* state preservation across requests.
+*
+* @param k : string
+* the key whose corresponding object is to be set
+* @param v : nsISupports
+* the object to be associated with the given key; may be null
+*/
+ _setObjectState: function(k, v)
+ {
+ if (typeof k !== "string")
+ throw new Error("non-string key passed");
+ if (typeof v !== "object")
+ throw new Error("non-object value passed");
+ if (v && !("QueryInterface" in v))
+ {
+ throw new Error("must pass an nsISupports; use wrappedJSObject to ease " +
+ "pain when using the server from JS");
+ }
+
+ this._objectState[k] = v;
+ },
+
+ /**
+* Gets a content-type for the given file, first by checking for any custom
+* MIME-types registered with this handler for the file's extension, second by
+* asking the global MIME service for a content-type, and finally by failing
+* over to application/octet-stream.
+*
+* @param file : nsIFile
+* the nsIFile for which to get a file type
+* @returns string
+* the best content-type which can be determined for the file
+*/
+ _getTypeFromFile: function(file)
+ {
+ try
+ {
+ var name = file.leafName;
+ var dot = name.lastIndexOf(".");
+ if (dot > 0)
+ {
+ var ext = name.slice(dot + 1);
+ if (ext in this._mimeMappings)
+ return this._mimeMappings[ext];
+ }
+ return Cc["@mozilla.org/uriloader/external-helper-app-service;1"]
+ .getService(Ci.nsIMIMEService)
+ .getTypeFromFile(file);
+ }
+ catch (e)
+ {
+ return "application/octet-stream";
+ }
+ },
+
+ /**
+* Returns the nsILocalFile which corresponds to the path, as determined using
+* all registered path->directory mappings and any paths which are explicitly
+* overridden.
+*
+* @param path : string
+* the server path for which a file should be retrieved, e.g. "/foo/bar"
+* @throws HttpError
+* when the correct action is the corresponding HTTP error (i.e., because no
+* mapping was found for a directory in path, the referenced file doesn't
+* exist, etc.)
+* @returns nsILocalFile
+* the file to be sent as the response to a request for the path
+*/
+ _getFileForPath: function(path)
+ {
+ // decode and add underscores as necessary
+ try
+ {
+ path = toInternalPath(path, true);
+ }
+ catch (e)
+ {
+ throw HTTP_400; // malformed path
+ }
+
+ // next, get the directory which contains this path
+ var pathMap = this._pathDirectoryMap;
+
+ // An example progression of tmp for a path "/foo/bar/baz/" might be:
+ // "foo/bar/baz/", "foo/bar/baz", "foo/bar", "foo", ""
+ var tmp = path.substring(1);
+ while (true)
+ {
+ // do we have a match for current head of the path?
+ var file = pathMap.get(tmp);
+ if (file)
+ {
+ // XXX hack; basically disable showing mapping for /foo/bar/ when the
+ // requested path was /foo/bar, because relative links on the page
+ // will all be incorrect -- we really need the ability to easily
+ // redirect here instead
+ if (tmp == path.substring(1) &&
+ tmp.length != 0 &&
+ tmp.charAt(tmp.length - 1) != "/")
+ file = null;
+ else
+ break;
+ }
+
+ // if we've finished trying all prefixes, exit
+ if (tmp == "")
+ break;
+
+ tmp = tmp.substring(0, tmp.lastIndexOf("/"));
+ }
+
+ // no mapping applies, so 404
+ if (!file)
+ throw HTTP_404;
+
+
+ // last, get the file for the path within the determined directory
+ var parentFolder = file.parent;
+ var dirIsRoot = (parentFolder == null);
+
+ // Strategy here is to append components individually, making sure we
+ // never move above the given directory; this allows paths such as
+ // "<file>/foo/../bar" but prevents paths such as "<file>/../base-sibling";
+ // this component-wise approach also means the code works even on platforms
+ // which don't use "/" as the directory separator, such as Windows
+ var leafPath = path.substring(tmp.length + 1);
+ var comps = leafPath.split("/");
+ for (var i = 0, sz = comps.length; i < sz; i++)
+ {
+ var comp = comps[i];
+
+ if (comp == "..")
+ file = file.parent;
+ else if (comp == "." || comp == "")
+ continue;
+ else
+ file.append(comp);
+
+ if (!dirIsRoot && file.equals(parentFolder))
+ throw HTTP_403;
+ }
+
+ return file;
+ },
+
+ /**
+* Writes the error page for the given HTTP error code over the given
+* connection.
+*
+* @param errorCode : uint
+* the HTTP error code to be used
+* @param connection : Connection
+* the connection on which the error occurred
+*/
+ handleError: function(errorCode, connection)
+ {
+ var response = new Response(connection);
+
+ dumpn("*** error in request: " + errorCode);
+
+ this._handleError(errorCode, new Request(connection.port), response);
+ },
+
+ /**
+* Handles a request which generates the given error code, using the
+* user-defined error handler if one has been set, gracefully falling back to
+* the x00 status code if the code has no handler, and failing to status code
+* 500 if all else fails.
+*
+* @param errorCode : uint
+* the HTTP error which is to be returned
+* @param metadata : Request
+* metadata for the request, which will often be incomplete since this is an
+* error
+* @param response : Response
+* an uninitialized Response should be initialized when this method
+* completes with information which represents the desired error code in the
+* ideal case or a fallback code in abnormal circumstances (i.e., 500 is a
+* fallback for 505, per HTTP specs)
+*/
+ _handleError: function(errorCode, metadata, response)
+ {
+ if (!metadata)
+ throw Cr.NS_ERROR_NULL_POINTER;
+
+ var errorX00 = errorCode - (errorCode % 100);
+
+ try
+ {
+ if (!(errorCode in HTTP_ERROR_CODES))
+ dumpn("*** WARNING: requested invalid error: " + errorCode);
+
+ // RFC 2616 says that we should try to handle an error by its class if we
+ // can't otherwise handle it -- if that fails, we revert to handling it as
+ // a 500 internal server error, and if that fails we throw and shut down
+ // the server
+
+ // actually handle the error
+ try
+ {
+ if (errorCode in this._overrideErrors)
+ this._overrideErrors[errorCode](metadata, response);
+ else
+ this._defaultErrors[errorCode](metadata, response);
+ }
+ catch (e)
+ {
+ if (response.partiallySent())
+ {
+ response.abort(e);
+ return;
+ }
+
+ // don't retry the handler that threw
+ if (errorX00 == errorCode)
+ throw HTTP_500;
+
+ dumpn("*** error in handling for error code " + errorCode + ", " +
+ "falling back to " + errorX00 + "...");
+ response = new Response(response._connection);
+ if (errorX00 in this._overrideErrors)
+ this._overrideErrors[errorX00](metadata, response);
+ else if (errorX00 in this._defaultErrors)
+ this._defaultErrors[errorX00](metadata, response);
+ else
+ throw HTTP_500;
+ }
+ }
+ catch (e)
+ {
+ if (response.partiallySent())
+ {
+ response.abort();
+ return;
+ }
+
+ // we've tried everything possible for a meaningful error -- now try 500
+ dumpn("*** error in handling for error code " + errorX00 + ", falling " +
+ "back to 500...");
+
+ try
+ {
+ response = new Response(response._connection);
+ if (500 in this._overrideErrors)
+ this._overrideErrors[500](metadata, response);
+ else
+ this._defaultErrors[500](metadata, response);
+ }
+ catch (e2)
+ {
+ dumpn("*** multiple errors in default error handlers!");
+ dumpn("*** e == " + e + ", e2 == " + e2);
+ response.abort(e2);
+ return;
+ }
+ }
+
+ response.complete();
+ },
+
+ // FIELDS
+
+ /**
+* This object contains the default handlers for the various HTTP error codes.
+*/
+ _defaultErrors:
+ {
+ 400: function(metadata, response)
+ {
+ // none of the data in metadata is reliable, so hard-code everything here
+ response.setStatusLine("1.1", 400, "Bad Request");
+ response.setHeader("Content-Type", "text/plain", false);
+
+ var body = "Bad request\n";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 403: function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion, 403, "Forbidden");
+ response.setHeader("Content-Type", "text/html", false);
+
+ var body = "<html>\
+<head><title>403 Forbidden</title></head>\
+<body>\
+<h1>403 Forbidden</h1>\
+</body>\
+</html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 404: function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ response.setHeader("Content-Type", "text/html", false);
+
+ var body = "<html>\
+<head><title>404 Not Found</title></head>\
+<body>\
+<h1>404 Not Found</h1>\
+<p>\
+<span style='font-family: monospace;'>" +
+ htmlEscape(metadata.path) +
+ "</span> was not found.\
+</p>\
+</body>\
+</html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 416: function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion,
+ 416,
+ "Requested Range Not Satisfiable");
+ response.setHeader("Content-Type", "text/html", false);
+
+ var body = "<html>\
+<head>\
+<title>416 Requested Range Not Satisfiable</title></head>\
+<body>\
+<h1>416 Requested Range Not Satisfiable</h1>\
+<p>The byte range was not valid for the\
+requested resource.\
+</p>\
+</body>\
+</html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 500: function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion,
+ 500,
+ "Internal Server Error");
+ response.setHeader("Content-Type", "text/html", false);
+
+ var body = "<html>\
+<head><title>500 Internal Server Error</title></head>\
+<body>\
+<h1>500 Internal Server Error</h1>\
+<p>Something's broken in this server and\
+needs to be fixed.</p>\
+</body>\
+</html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 501: function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion, 501, "Not Implemented");
+ response.setHeader("Content-Type", "text/html", false);
+
+ var body = "<html>\
+<head><title>501 Not Implemented</title></head>\
+<body>\
+<h1>501 Not Implemented</h1>\
+<p>This server is not (yet) Apache.</p>\
+</body>\
+</html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 505: function(metadata, response)
+ {
+ response.setStatusLine("1.1", 505, "HTTP Version Not Supported");
+ response.setHeader("Content-Type", "text/html", false);
+
+ var body = "<html>\
+<head><title>505 HTTP Version Not Supported</title></head>\
+<body>\
+<h1>505 HTTP Version Not Supported</h1>\
+<p>This server only supports HTTP/1.0 and HTTP/1.1\
+connections.</p>\
+</body>\
+</html>";
+ response.bodyOutputStream.write(body, body.length);
+ }
+ },
+
+ /**
+* Contains handlers for the default set of URIs contained in this server.
+*/
+ _defaultPaths:
+ {
+ "/": function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+
+ var body = "<html>\
+<head><title>httpd.js</title></head>\
+<body>\
+<h1>httpd.js</h1>\
+<p>If you're seeing this page, httpd.js is up and\
+serving requests! Now set a base path and serve some\
+files!</p>\
+</body>\
+</html>";
+
+ response.bodyOutputStream.write(body, body.length);
+ },
+
+ "/trace": function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+
+ var body = "Request-URI: " +
+ metadata.scheme + "://" + metadata.host + ":" + metadata.port +
+ metadata.path + "\n\n";
+ body += "Request (semantically equivalent, slightly reformatted):\n\n";
+ body += metadata.method + " " + metadata.path;
+
+ if (metadata.queryString)
+ body += "?" + metadata.queryString;
+
+ body += " HTTP/" + metadata.httpVersion + "\r\n";
+
+ var headEnum = metadata.headers;
+ while (headEnum.hasMoreElements())
+ {
+ var fieldName = headEnum.getNext()
+ .QueryInterface(Ci.nsISupportsString)
+ .data;
+ body += fieldName + ": " + metadata.getHeader(fieldName) + "\r\n";
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+ }
+ }
+};
+
+
+/**
+* Maps absolute paths to files on the local file system (as nsILocalFiles).
+*/
+function FileMap()
+{
+ /** Hash which will map paths to nsILocalFiles. */
+ this._map = {};
+}
+FileMap.prototype =
+{
+ // PUBLIC API
+
+ /**
+* Maps key to a clone of the nsILocalFile value if value is non-null;
+* otherwise, removes any extant mapping for key.
+*
+* @param key : string
+* string to which a clone of value is mapped
+* @param value : nsILocalFile
+* the file to map to key, or null to remove a mapping
+*/
+ put: function(key, value)
+ {
+ if (value)
+ this._map[key] = value.clone();
+ else
+ delete this._map[key];
+ },
+
+ /**
+* Returns a clone of the nsILocalFile mapped to key, or null if no such
+* mapping exists.
+*
+* @param key : string
+* key to which the returned file maps
+* @returns nsILocalFile
+* a clone of the mapped file, or null if no mapping exists
+*/
+ get: function(key)
+ {
+ var val = this._map[key];
+ return val ? val.clone() : null;
+ }
+};
+
+
+// Response CONSTANTS
+
+// token = *<any CHAR except CTLs or separators>
+// CHAR = <any US-ASCII character (0-127)>
+// CTL = <any US-ASCII control character (0-31) and DEL (127)>
+// separators = "(" | ")" | "<" | ">" | "@"
+// | "," | ";" | ":" | "\" | <">
+// | "/" | "[" | "]" | "?" | "="
+// | "{" | "}" | SP | HT
+const IS_TOKEN_ARRAY =
+ [0, 0, 0, 0, 0, 0, 0, 0, // 0
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24
+
+ 0, 1, 0, 1, 1, 1, 1, 1, // 32
+ 0, 0, 1, 1, 0, 1, 1, 0, // 40
+ 1, 1, 1, 1, 1, 1, 1, 1, // 48
+ 1, 1, 0, 0, 0, 0, 0, 0, // 56
+
+ 0, 1, 1, 1, 1, 1, 1, 1, // 64
+ 1, 1, 1, 1, 1, 1, 1, 1, // 72
+ 1, 1, 1, 1, 1, 1, 1, 1, // 80
+ 1, 1, 1, 0, 0, 0, 1, 1, // 88
+
+ 1, 1, 1, 1, 1, 1, 1, 1, // 96
+ 1, 1, 1, 1, 1, 1, 1, 1, // 104
+ 1, 1, 1, 1, 1, 1, 1, 1, // 112
+ 1, 1, 1, 0, 1, 0, 1]; // 120
+
+
+/**
+* Determines whether the given character code is a CTL.
+*
+* @param code : uint
+* the character code
+* @returns boolean
+* true if code is a CTL, false otherwise
+*/
+function isCTL(code)
+{
+ return (code >= 0 && code <= 31) || (code == 127);
+}
+
+/**
+* Represents a response to an HTTP request, encapsulating all details of that
+* response. This includes all headers, the HTTP version, status code and
+* explanation, and the entity itself.
+*
+* @param connection : Connection
+* the connection over which this response is to be written
+*/
+function Response(connection)
+{
+ /** The connection over which this response will be written. */
+ this._connection = connection;
+
+ /**
+* The HTTP version of this response; defaults to 1.1 if not set by the
+* handler.
+*/
+ this._httpVersion = nsHttpVersion.HTTP_1_1;
+
+ /**
+* The HTTP code of this response; defaults to 200.
+*/
+ this._httpCode = 200;
+
+ /**
+* The description of the HTTP code in this response; defaults to "OK".
+*/
+ this._httpDescription = "OK";
+
+ /**
+* An nsIHttpHeaders object in which the headers in this response should be
+* stored. This property is null after the status line and headers have been
+* written to the network, and it may be modified up until it is cleared,
+* except if this._finished is set first (in which case headers are written
+* asynchronously in response to a finish() call not preceded by
+* flushHeaders()).
+*/
+ this._headers = new nsHttpHeaders();
+
+ /**
+* Set to true when this response is ended (completely constructed if possible
+* and the connection closed); further actions on this will then fail.
+*/
+ this._ended = false;
+
+ /**
+* A stream used to hold data written to the body of this response.
+*/
+ this._bodyOutputStream = null;
+
+ /**
+* A stream containing all data that has been written to the body of this
+* response so far. (Async handlers make the data contained in this
+* unreliable as a way of determining content length in general, but auxiliary
+* saved information can sometimes be used to guarantee reliability.)
+*/
+ this._bodyInputStream = null;
+
+ /**
+* A stream copier which copies data to the network. It is initially null
+* until replaced with a copier for response headers; when headers have been
+* fully sent it is replaced with a copier for the response body, remaining
+* so for the duration of response processing.
+*/
+ this._asyncCopier = null;
+
+ /**
+* True if this response has been designated as being processed
+* asynchronously rather than for the duration of a single call to
+* nsIHttpRequestHandler.handle.
+*/
+ this._processAsync = false;
+
+ /**
+* True iff finish() has been called on this, signaling that no more changes
+* to this may be made.
+*/
+ this._finished = false;
+
+ /**
+* True iff powerSeized() has been called on this, signaling that this
+* response is to be handled manually by the response handler (which may then
+* send arbitrary data in response, even non-HTTP responses).
+*/
+ this._powerSeized = false;
+}
+Response.prototype =
+{
+ // PUBLIC CONSTRUCTION API
+
+ //
+ // see nsIHttpResponse.bodyOutputStream
+ //
+ get bodyOutputStream()
+ {
+ if (this._finished)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ if (!this._bodyOutputStream)
+ {
+ var pipe = new Pipe(true, false, Response.SEGMENT_SIZE, PR_UINT32_MAX,
+ null);
+ this._bodyOutputStream = pipe.outputStream;
+ this._bodyInputStream = pipe.inputStream;
+ if (this._processAsync || this._powerSeized)
+ this._startAsyncProcessor();
+ }
+
+ return this._bodyOutputStream;
+ },
+
+ //
+ // see nsIHttpResponse.write
+ //
+ write: function(data)
+ {
+ if (this._finished)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ var dataAsString = String(data);
+ this.bodyOutputStream.write(dataAsString, dataAsString.length);
+ },
+
+ //
+ // see nsIHttpResponse.setStatusLine
+ //
+ setStatusLine: function(httpVersion, code, description)
+ {
+ if (!this._headers || this._finished || this._powerSeized)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ this._ensureAlive();
+
+ if (!(code >= 0 && code < 1000))
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ try
+ {
+ var httpVer;
+ // avoid version construction for the most common cases
+ if (!httpVersion || httpVersion == "1.1")
+ httpVer = nsHttpVersion.HTTP_1_1;
+ else if (httpVersion == "1.0")
+ httpVer = nsHttpVersion.HTTP_1_0;
+ else
+ httpVer = new nsHttpVersion(httpVersion);
+ }
+ catch (e)
+ {
+ throw Cr.NS_ERROR_INVALID_ARG;
+ }
+
+ // Reason-Phrase = *<TEXT, excluding CR, LF>
+ // TEXT = <any OCTET except CTLs, but including LWS>
+ //
+ // XXX this ends up disallowing octets which aren't Unicode, I think -- not
+ // much to do if description is IDL'd as string
+ if (!description)
+ description = "";
+ for (var i = 0; i < description.length; i++)
+ if (isCTL(description.charCodeAt(i)) && description.charAt(i) != "\t")
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ // set the values only after validation to preserve atomicity
+ this._httpDescription = description;
+ this._httpCode = code;
+ this._httpVersion = httpVer;
+ },
+
+ //
+ // see nsIHttpResponse.setHeader
+ //
+ setHeader: function(name, value, merge)
+ {
+ if (!this._headers || this._finished || this._powerSeized)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ this._ensureAlive();
+
+ this._headers.setHeader(name, value, merge);
+ },
+
+ //
+ // see nsIHttpResponse.processAsync
+ //
+ processAsync: function()
+ {
+ if (this._finished)
+ throw Cr.NS_ERROR_UNEXPECTED;
+ if (this._powerSeized)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ if (this._processAsync)
+ return;
+ this._ensureAlive();
+
+ dumpn("*** processing connection " + this._connection.number + " async");
+ this._processAsync = true;
+
+ /*
+* Either the bodyOutputStream getter or this method is responsible for
+* starting the asynchronous processor and catching writes of data to the
+* response body of async responses as they happen, for the purpose of
+* forwarding those writes to the actual connection's output stream.
+* If bodyOutputStream is accessed first, calling this method will create
+* the processor (when it first is clear that body data is to be written
+* immediately, not buffered). If this method is called first, accessing
+* bodyOutputStream will create the processor. If only this method is
+* called, we'll write nothing, neither headers nor the nonexistent body,
+* until finish() is called. Since that delay is easily avoided by simply
+* getting bodyOutputStream or calling write(""), we don't worry about it.
+*/
+ if (this._bodyOutputStream && !this._asyncCopier)
+ this._startAsyncProcessor();
+ },
+
+ //
+ // see nsIHttpResponse.seizePower
+ //
+ seizePower: function()
+ {
+ if (this._processAsync)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ if (this._finished)
+ throw Cr.NS_ERROR_UNEXPECTED;
+ if (this._powerSeized)
+ return;
+ this._ensureAlive();
+
+ dumpn("*** forcefully seizing power over connection " +
+ this._connection.number + "...");
+
+ // Purge any already-written data without sending it. We could as easily
+ // swap out the streams entirely, but that makes it possible to acquire and
+ // unknowingly use a stale reference, so we require there only be one of
+ // each stream ever for any response to avoid this complication.
+ if (this._asyncCopier)
+ this._asyncCopier.cancel(Cr.NS_BINDING_ABORTED);
+ this._asyncCopier = null;
+ if (this._bodyOutputStream)
+ {
+ var input = new BinaryInputStream(this._bodyInputStream);
+ var avail;
+ while ((avail = input.available()) > 0)
+ input.readByteArray(avail);
+ }
+
+ this._powerSeized = true;
+ if (this._bodyOutputStream)
+ this._startAsyncProcessor();
+ },
+
+ //
+ // see nsIHttpResponse.finish
+ //
+ finish: function()
+ {
+ if (!this._processAsync && !this._powerSeized)
+ throw Cr.NS_ERROR_UNEXPECTED;
+ if (this._finished)
+ return;
+
+ dumpn("*** finishing connection " + this._connection.number);
+ this._startAsyncProcessor(); // in case bodyOutputStream was never accessed
+ if (this._bodyOutputStream)
+ this._bodyOutputStream.close();
+ this._finished = true;
+ },
+
+
+ // NSISUPPORTS
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIHttpResponse) || iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // POST-CONSTRUCTION API (not exposed externally)
+
+ /**
+* The HTTP version number of this, as a string (e.g. "1.1").
+*/
+ get httpVersion()
+ {
+ this._ensureAlive();
+ return this._httpVersion.toString();
+ },
+
+ /**
+* The HTTP status code of this response, as a string of three characters per
+* RFC 2616.
+*/
+ get httpCode()
+ {
+ this._ensureAlive();
+
+ var codeString = (this._httpCode < 10 ? "0" : "") +
+ (this._httpCode < 100 ? "0" : "") +
+ this._httpCode;
+ return codeString;
+ },
+
+ /**
+* The description of the HTTP status code of this response, or "" if none is
+* set.
+*/
+ get httpDescription()
+ {
+ this._ensureAlive();
+
+ return this._httpDescription;
+ },
+
+ /**
+* The headers in this response, as an nsHttpHeaders object.
+*/
+ get headers()
+ {
+ this._ensureAlive();
+
+ return this._headers;
+ },
+
+ //
+ // see nsHttpHeaders.getHeader
+ //
+ getHeader: function(name)
+ {
+ this._ensureAlive();
+
+ return this._headers.getHeader(name);
+ },
+
+ /**
+* Determines whether this response may be abandoned in favor of a newly
+* constructed response. A response may be abandoned only if it is not being
+* sent asynchronously and if raw control over it has not been taken from the
+* server.
+*
+* @returns boolean
+* true iff no data has been written to the network
+*/
+ partiallySent: function()
+ {
+ dumpn("*** partiallySent()");
+ return this._processAsync || this._powerSeized;
+ },
+
+ /**
+* If necessary, kicks off the remaining request processing needed to be done
+* after a request handler performs its initial work upon this response.
+*/
+ complete: function()
+ {
+ dumpn("*** complete()");
+ if (this._processAsync || this._powerSeized)
+ {
+ NS_ASSERT(this._processAsync ^ this._powerSeized,
+ "can't both send async and relinquish power");
+ return;
+ }
+
+ NS_ASSERT(!this.partiallySent(), "completing a partially-sent response?");
+
+ this._startAsyncProcessor();
+
+ // Now make sure we finish processing this request!
+ if (this._bodyOutputStream)
+ this._bodyOutputStream.close();
+ },
+
+ /**
+* Abruptly ends processing of this response, usually due to an error in an
+* incoming request but potentially due to a bad error handler. Since we
+* cannot handle the error in the usual way (giving an HTTP error page in
+* response) because data may already have been sent (or because the response
+* might be expected to have been generated asynchronously or completely from
+* scratch by the handler), we stop processing this response and abruptly
+* close the connection.
+*
+* @param e : Error
+* the exception which precipitated this abort, or null if no such exception
+* was generated
+*/
+ abort: function(e)
+ {
+ dumpn("*** abort(<" + e + ">)");
+
+ // This response will be ended by the processor if one was created.
+ var copier = this._asyncCopier;
+ if (copier)
+ {
+ // We dispatch asynchronously here so that any pending writes of data to
+ // the connection will be deterministically written. This makes it easier
+ // to specify exact behavior, and it makes observable behavior more
+ // predictable for clients. Note that the correctness of this depends on
+ // callbacks in response to _waitToReadData in WriteThroughCopier
+ // happening asynchronously with respect to the actual writing of data to
+ // bodyOutputStream, as they currently do; if they happened synchronously,
+ // an event which ran before this one could write more data to the
+ // response body before we get around to canceling the copier. We have
+ // tests for this in test_seizepower.js, however, and I can't think of a
+ // way to handle both cases without removing bodyOutputStream access and
+ // moving its effective write(data, length) method onto Response, which
+ // would be slower and require more code than this anyway.
+ gThreadManager.currentThread.dispatch({
+ run: function()
+ {
+ dumpn("*** canceling copy asynchronously...");
+ copier.cancel(Cr.NS_ERROR_UNEXPECTED);
+ }
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ else
+ {
+ this.end();
+ }
+ },
+
+ /**
+* Closes this response's network connection, marks the response as finished,
+* and notifies the server handler that the request is done being processed.
+*/
+ end: function()
+ {
+ NS_ASSERT(!this._ended, "ending this response twice?!?!");
+
+ this._connection.close();
+ if (this._bodyOutputStream)
+ this._bodyOutputStream.close();
+
+ this._finished = true;
+ this._ended = true;
+ },
+
+ // PRIVATE IMPLEMENTATION
+
+ /**
+* Sends the status line and headers of this response if they haven't been
+* sent and initiates the process of copying data written to this response's
+* body to the network.
+*/
+ _startAsyncProcessor: function()
+ {
+ dumpn("*** _startAsyncProcessor()");
+
+ // Handle cases where we're being called a second time. The former case
+ // happens when this is triggered both by complete() and by processAsync(),
+ // while the latter happens when processAsync() in conjunction with sent
+ // data causes abort() to be called.
+ if (this._asyncCopier || this._ended)
+ {
+ dumpn("*** ignoring second call to _startAsyncProcessor");
+ return;
+ }
+
+ // Send headers if they haven't been sent already and should be sent, then
+ // asynchronously continue to send the body.
+ if (this._headers && !this._powerSeized)
+ {
+ this._sendHeaders();
+ return;
+ }
+
+ this._headers = null;
+ this._sendBody();
+ },
+
+ /**
+* Signals that all modifications to the response status line and headers are
+* complete and then sends that data over the network to the client. Once
+* this method completes, a different response to the request that resulted
+* in this response cannot be sent -- the only possible action in case of
+* error is to abort the response and close the connection.
+*/
+ _sendHeaders: function()
+ {
+ dumpn("*** _sendHeaders()");
+
+ NS_ASSERT(this._headers);
+ NS_ASSERT(!this._powerSeized);
+
+ // request-line
+ var statusLine = "HTTP/" + this.httpVersion + " " +
+ this.httpCode + " " +
+ this.httpDescription + "\r\n";
+
+ // header post-processing
+
+ var headers = this._headers;
+ headers.setHeader("Connection", "close", false);
+ headers.setHeader("Server", "httpd.js", false);
+ if (!headers.hasHeader("Date"))
+ headers.setHeader("Date", toDateString(Date.now()), false);
+
+ // Any response not being processed asynchronously must have an associated
+ // Content-Length header for reasons of backwards compatibility with the
+ // initial server, which fully buffered every response before sending it.
+ // Beyond that, however, it's good to do this anyway because otherwise it's
+ // impossible to test behaviors that depend on the presence or absence of a
+ // Content-Length header.
+ if (!this._processAsync)
+ {
+ dumpn("*** non-async response, set Content-Length");
+
+ var bodyStream = this._bodyInputStream;
+ var avail = bodyStream ? bodyStream.available() : 0;
+
+ // XXX assumes stream will always report the full amount of data available
+ headers.setHeader("Content-Length", "" + avail, false);
+ }
+
+
+ // construct and send response
+ dumpn("*** header post-processing completed, sending response head...");
+
+ // request-line
+ var preambleData = [statusLine];
+
+ // headers
+ var headEnum = headers.enumerator;
+ while (headEnum.hasMoreElements())
+ {
+ var fieldName = headEnum.getNext()
+ .QueryInterface(Ci.nsISupportsString)
+ .data;
+ var values = headers.getHeaderValues(fieldName);
+ for (var i = 0, sz = values.length; i < sz; i++)
+ preambleData.push(fieldName + ": " + values[i] + "\r\n");
+ }
+
+ // end request-line/headers
+ preambleData.push("\r\n");
+
+ var preamble = preambleData.join("");
+
+ var responseHeadPipe = new Pipe(true, false, 0, PR_UINT32_MAX, null);
+ responseHeadPipe.outputStream.write(preamble, preamble.length);
+
+ var response = this;
+ var copyObserver =
+ {
+ onStartRequest: function(request, cx)
+ {
+ dumpn("*** preamble copying started");
+ },
+
+ onStopRequest: function(request, cx, statusCode)
+ {
+ dumpn("*** preamble copying complete " +
+ "[status=0x" + statusCode.toString(16) + "]");
+
+ if (!components.isSuccessCode(statusCode))
+ {
+ dumpn("!!! header copying problems: non-success statusCode, " +
+ "ending response");
+
+ response.end();
+ }
+ else
+ {
+ response._sendBody();
+ }
+ },
+
+ QueryInterface: function(aIID)
+ {
+ if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ };
+
+ var headerCopier = this._asyncCopier =
+ new WriteThroughCopier(responseHeadPipe.inputStream,
+ this._connection.output,
+ copyObserver, null);
+
+ responseHeadPipe.outputStream.close();
+
+ // Forbid setting any more headers or modifying the request line.
+ this._headers = null;
+ },
+
+ /**
+* Asynchronously writes the body of the response (or the entire response, if
+* seizePower() has been called) to the network.
+*/
+ _sendBody: function()
+ {
+ dumpn("*** _sendBody");
+
+ NS_ASSERT(!this._headers, "still have headers around but sending body?");
+
+ // If no body data was written, we're done
+ if (!this._bodyInputStream)
+ {
+ dumpn("*** empty body, response finished");
+ this.end();
+ return;
+ }
+
+ var response = this;
+ var copyObserver =
+ {
+ onStartRequest: function(request, context)
+ {
+ dumpn("*** onStartRequest");
+ },
+
+ onStopRequest: function(request, cx, statusCode)
+ {
+ dumpn("*** onStopRequest [status=0x" + statusCode.toString(16) + "]");
+
+ if (statusCode === Cr.NS_BINDING_ABORTED)
+ {
+ dumpn("*** terminating copy observer without ending the response");
+ }
+ else
+ {
+ if (!components.isSuccessCode(statusCode))
+ dumpn("*** WARNING: non-success statusCode in onStopRequest");
+
+ response.end();
+ }
+ },
+
+ QueryInterface: function(aIID)
+ {
+ if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ };
+
+ dumpn("*** starting async copier of body data...");
+ this._asyncCopier =
+ new WriteThroughCopier(this._bodyInputStream, this._connection.output,
+ copyObserver, null);
+ },
+
+ /** Ensures that this hasn't been ended. */
+ _ensureAlive: function()
+ {
+ NS_ASSERT(!this._ended, "not handling response lifetime correctly");
+ }
+};
+
+/**
+* Size of the segments in the buffer used in storing response data and writing
+* it to the socket.
+*/
+Response.SEGMENT_SIZE = 8192;
+
+/** Serves double duty in WriteThroughCopier implementation. */
+function notImplemented()
+{
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/** Returns true iff the given exception represents stream closure. */
+function streamClosed(e)
+{
+ return e === Cr.NS_BASE_STREAM_CLOSED ||
+ (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_CLOSED);
+}
+
+/** Returns true iff the given exception represents a blocked stream. */
+function wouldBlock(e)
+{
+ return e === Cr.NS_BASE_STREAM_WOULD_BLOCK ||
+ (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_WOULD_BLOCK);
+}
+
+/**
+* Copies data from source to sink as it becomes available, when that data can
+* be written to sink without blocking.
+*
+* @param source : nsIAsyncInputStream
+* the stream from which data is to be read
+* @param sink : nsIAsyncOutputStream
+* the stream to which data is to be copied
+* @param observer : nsIRequestObserver
+* an observer which will be notified when the copy starts and finishes
+* @param context : nsISupports
+* context passed to observer when notified of start/stop
+* @throws NS_ERROR_NULL_POINTER
+* if source, sink, or observer are null
+*/
+function WriteThroughCopier(source, sink, observer, context)
+{
+ if (!source || !sink || !observer)
+ throw Cr.NS_ERROR_NULL_POINTER;
+
+ /** Stream from which data is being read. */
+ this._source = source;
+
+ /** Stream to which data is being written. */
+ this._sink = sink;
+
+ /** Observer watching this copy. */
+ this._observer = observer;
+
+ /** Context for the observer watching this. */
+ this._context = context;
+
+ /**
+* True iff this is currently being canceled (cancel has been called, the
+* callback may not yet have been made).
+*/
+ this._canceled = false;
+
+ /**
+* False until all data has been read from input and written to output, at
+* which point this copy is completed and cancel() is asynchronously called.
+*/
+ this._completed = false;
+
+ /** Required by nsIRequest, meaningless. */
+ this.loadFlags = 0;
+ /** Required by nsIRequest, meaningless. */
+ this.loadGroup = null;
+ /** Required by nsIRequest, meaningless. */
+ this.name = "response-body-copy";
+
+ /** Status of this request. */
+ this.status = Cr.NS_OK;
+
+ /** Arrays of byte strings waiting to be written to output. */
+ this._pendingData = [];
+
+ // start copying
+ try
+ {
+ observer.onStartRequest(this, context);
+ this._waitToReadData();
+ this._waitForSinkClosure();
+ }
+ catch (e)
+ {
+ dumpn("!!! error starting copy: " + e +
+ ("lineNumber" in e ? ", line " + e.lineNumber : ""));
+ dumpn(e.stack);
+ this.cancel(Cr.NS_ERROR_UNEXPECTED);
+ }
+}
+WriteThroughCopier.prototype =
+{
+ /* nsISupports implementation */
+
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIInputStreamCallback) ||
+ iid.equals(Ci.nsIOutputStreamCallback) ||
+ iid.equals(Ci.nsIRequest) ||
+ iid.equals(Ci.nsISupports))
+ {
+ return this;
+ }
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // NSIINPUTSTREAMCALLBACK
+
+ /**
+* Receives a more-data-in-input notification and writes the corresponding
+* data to the output.
+*
+* @param input : nsIAsyncInputStream
+* the input stream on whose data we have been waiting
+*/
+ onInputStreamReady: function(input)
+ {
+ if (this._source === null)
+ return;
+
+ dumpn("*** onInputStreamReady");
+
+ //
+ // Ordinarily we'll read a non-zero amount of data from input, queue it up
+ // to be written and then wait for further callbacks. The complications in
+ // this method are the cases where we deviate from that behavior when errors
+ // occur or when copying is drawing to a finish.
+ //
+ // The edge cases when reading data are:
+ //
+ // Zero data is read
+ // If zero data was read, we're at the end of available data, so we can
+ // should stop reading and move on to writing out what we have (or, if
+ // we've already done that, onto notifying of completion).
+ // A stream-closed exception is thrown
+ // This is effectively a less kind version of zero data being read; the
+ // only difference is that we notify of completion with that result
+ // rather than with NS_OK.
+ // Some other exception is thrown
+ // This is the least kind result. We don't know what happened, so we
+ // act as though the stream closed except that we notify of completion
+ // with the result NS_ERROR_UNEXPECTED.
+ //
+
+ var bytesWanted = 0, bytesConsumed = -1;
+ try
+ {
+ input = new BinaryInputStream(input);
+
+ bytesWanted = Math.min(input.available(), Response.SEGMENT_SIZE);
+ dumpn("*** input wanted: " + bytesWanted);
+
+ if (bytesWanted > 0)
+ {
+ var data = input.readByteArray(bytesWanted);
+ bytesConsumed = data.length;
+ this._pendingData.push(String.fromCharCode.apply(String, data));
+ }
+
+ dumpn("*** " + bytesConsumed + " bytes read");
+
+ // Handle the zero-data edge case in the same place as all other edge
+ // cases are handled.
+ if (bytesWanted === 0)
+ throw Cr.NS_BASE_STREAM_CLOSED;
+ }
+ catch (e)
+ {
+ if (streamClosed(e))
+ {
+ dumpn("*** input stream closed");
+ e = bytesWanted === 0 ? Cr.NS_OK : Cr.NS_ERROR_UNEXPECTED;
+ }
+ else
+ {
+ dumpn("!!! unexpected error reading from input, canceling: " + e);
+ e = Cr.NS_ERROR_UNEXPECTED;
+ }
+
+ this._doneReadingSource(e);
+ return;
+ }
+
+ var pendingData = this._pendingData;
+
+ NS_ASSERT(bytesConsumed > 0);
+ NS_ASSERT(pendingData.length > 0, "no pending data somehow?");
+ NS_ASSERT(pendingData[pendingData.length - 1].length > 0,
+ "buffered zero bytes of data?");
+
+ NS_ASSERT(this._source !== null);
+
+ // Reading has gone great, and we've gotten data to write now. What if we
+ // don't have a place to write that data, because output went away just
+ // before this read? Drop everything on the floor, including new data, and
+ // cancel at this point.
+ if (this._sink === null)
+ {
+ pendingData.length = 0;
+ this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Okay, we've read the data, and we know we have a place to write it. We
+ // need to queue up the data to be written, but *only* if none is queued
+ // already -- if data's already queued, the code that actually writes the
+ // data will make sure to wait on unconsumed pending data.
+ try
+ {
+ if (pendingData.length === 1)
+ this._waitToWriteData();
+ }
+ catch (e)
+ {
+ dumpn("!!! error waiting to write data just read, swallowing and " +
+ "writing only what we already have: " + e);
+ this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Whee! We successfully read some data, and it's successfully queued up to
+ // be written. All that remains now is to wait for more data to read.
+ try
+ {
+ this._waitToReadData();
+ }
+ catch (e)
+ {
+ dumpn("!!! error waiting to read more data: " + e);
+ this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED);
+ }
+ },
+
+
+ // NSIOUTPUTSTREAMCALLBACK
+
+ /**
+* Callback when data may be written to the output stream without blocking, or
+* when the output stream has been closed.
+*
+* @param output : nsIAsyncOutputStream
+* the output stream on whose writability we've been waiting, also known as
+* this._sink
+*/
+ onOutputStreamReady: function(output)
+ {
+ if (this._sink === null)
+ return;
+
+ dumpn("*** onOutputStreamReady");
+
+ var pendingData = this._pendingData;
+ if (pendingData.length === 0)
+ {
+ // There's no pending data to write. The only way this can happen is if
+ // we're waiting on the output stream's closure, so we can respond to a
+ // copying failure as quickly as possible (rather than waiting for data to
+ // be available to read and then fail to be copied). Therefore, we must
+ // be done now -- don't bother to attempt to write anything and wrap
+ // things up.
+ dumpn("!!! output stream closed prematurely, ending copy");
+
+ this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+
+ NS_ASSERT(pendingData[0].length > 0, "queued up an empty quantum?");
+
+ //
+ // Write out the first pending quantum of data. The possible errors here
+ // are:
+ //
+ // The write might fail because we can't write that much data
+ // Okay, we've written what we can now, so re-queue what's left and
+ // finish writing it out later.
+ // The write failed because the stream was closed
+ // Discard pending data that we can no longer write, stop reading, and
+ // signal that copying finished.
+ // Some other error occurred.
+ // Same as if the stream were closed, but notify with the status
+ // NS_ERROR_UNEXPECTED so the observer knows something was wonky.
+ //
+
+ try
+ {
+ var quantum = pendingData[0];
+
+ // XXX |quantum| isn't guaranteed to be ASCII, so we're relying on
+ // undefined behavior! We're only using this because writeByteArray
+ // is unusably broken for asynchronous output streams; see bug 532834
+ // for details.
+ var bytesWritten = output.write(quantum, quantum.length);
+ if (bytesWritten === quantum.length)
+ pendingData.shift();
+ else
+ pendingData[0] = quantum.substring(bytesWritten);
+
+ dumpn("*** wrote " + bytesWritten + " bytes of data");
+ }
+ catch (e)
+ {
+ if (wouldBlock(e))
+ {
+ NS_ASSERT(pendingData.length > 0,
+ "stream-blocking exception with no data to write?");
+ NS_ASSERT(pendingData[0].length > 0,
+ "stream-blocking exception with empty quantum?");
+ this._waitToWriteData();
+ return;
+ }
+
+ if (streamClosed(e))
+ dumpn("!!! output stream prematurely closed, signaling error...");
+ else
+ dumpn("!!! unknown error: " + e + ", quantum=" + quantum);
+
+ this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // The day is ours! Quantum written, now let's see if we have more data
+ // still to write.
+ try
+ {
+ if (pendingData.length > 0)
+ {
+ this._waitToWriteData();
+ return;
+ }
+ }
+ catch (e)
+ {
+ dumpn("!!! unexpected error waiting to write pending data: " + e);
+ this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Okay, we have no more pending data to write -- but might we get more in
+ // the future?
+ if (this._source !== null)
+ {
+ /*
+* If we might, then wait for the output stream to be closed. (We wait
+* only for closure because we have no data to write -- and if we waited
+* for a specific amount of data, we would get repeatedly notified for no
+* reason if over time the output stream permitted more and more data to
+* be written to it without blocking.)
+*/
+ this._waitForSinkClosure();
+ }
+ else
+ {
+ /*
+* On the other hand, if we can't have more data because the input
+* stream's gone away, then it's time to notify of copy completion.
+* Victory!
+*/
+ this._sink = null;
+ this._cancelOrDispatchCancelCallback(Cr.NS_OK);
+ }
+ },
+
+
+ // NSIREQUEST
+
+ /** Returns true if the cancel observer hasn't been notified yet. */
+ isPending: function()
+ {
+ return !this._completed;
+ },
+
+ /** Not implemented, don't use! */
+ suspend: notImplemented,
+ /** Not implemented, don't use! */
+ resume: notImplemented,
+
+ /**
+* Cancels data reading from input, asynchronously writes out any pending
+* data, and causes the observer to be notified with the given error code when
+* all writing has finished.
+*
+* @param status : nsresult
+* the status to pass to the observer when data copying has been canceled
+*/
+ cancel: function(status)
+ {
+ dumpn("*** cancel(" + status.toString(16) + ")");
+
+ if (this._canceled)
+ {
+ dumpn("*** suppressing a late cancel");
+ return;
+ }
+
+ this._canceled = true;
+ this.status = status;
+
+ // We could be in the middle of absolutely anything at this point. Both
+ // input and output might still be around, we might have pending data to
+ // write, and in general we know nothing about the state of the world. We
+ // therefore must assume everything's in progress and take everything to its
+ // final steady state (or so far as it can go before we need to finish
+ // writing out remaining data).
+
+ this._doneReadingSource(status);
+ },
+
+
+ // PRIVATE IMPLEMENTATION
+
+ /**
+* Stop reading input if we haven't already done so, passing e as the status
+* when closing the stream, and kick off a copy-completion notice if no more
+* data remains to be written.
+*
+* @param e : nsresult
+* the status to be used when closing the input stream
+*/
+ _doneReadingSource: function(e)
+ {
+ dumpn("*** _doneReadingSource(0x" + e.toString(16) + ")");
+
+ this._finishSource(e);
+ if (this._pendingData.length === 0)
+ this._sink = null;
+ else
+ NS_ASSERT(this._sink !== null, "null output?");
+
+ // If we've written out all data read up to this point, then it's time to
+ // signal completion.
+ if (this._sink === null)
+ {
+ NS_ASSERT(this._pendingData.length === 0, "pending data still?");
+ this._cancelOrDispatchCancelCallback(e);
+ }
+ },
+
+ /**
+* Stop writing output if we haven't already done so, discard any data that
+* remained to be sent, close off input if it wasn't already closed, and kick
+* off a copy-completion notice.
+*
+* @param e : nsresult
+* the status to be used when closing input if it wasn't already closed
+*/
+ _doneWritingToSink: function(e)
+ {
+ dumpn("*** _doneWritingToSink(0x" + e.toString(16) + ")");
+
+ this._pendingData.length = 0;
+ this._sink = null;
+ this._doneReadingSource(e);
+ },
+
+ /**
+* Completes processing of this copy: either by canceling the copy if it
+* hasn't already been canceled using the provided status, or by dispatching
+* the cancel callback event (with the originally provided status, of course)
+* if it already has been canceled.
+*
+* @param status : nsresult
+* the status code to use to cancel this, if this hasn't already been
+* canceled
+*/
+ _cancelOrDispatchCancelCallback: function(status)
+ {
+ dumpn("*** _cancelOrDispatchCancelCallback(" + status + ")");
+
+ NS_ASSERT(this._source === null, "should have finished input");
+ NS_ASSERT(this._sink === null, "should have finished output");
+ NS_ASSERT(this._pendingData.length === 0, "should have no pending data");
+
+ if (!this._canceled)
+ {
+ this.cancel(status);
+ return;
+ }
+
+ var self = this;
+ var event =
+ {
+ run: function()
+ {
+ dumpn("*** onStopRequest async callback");
+
+ self._completed = true;
+ try
+ {
+ self._observer.onStopRequest(self, self._context, self.status);
+ }
+ catch (e)
+ {
+ NS_ASSERT(false,
+ "how are we throwing an exception here? we control " +
+ "all the callers! " + e);
+ }
+ }
+ };
+
+ gThreadManager.currentThread.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL);
+ },
+
+ /**
+* Kicks off another wait for more data to be available from the input stream.
+*/
+ _waitToReadData: function()
+ {
+ dumpn("*** _waitToReadData");
+ this._source.asyncWait(this, 0, Response.SEGMENT_SIZE,
+ gThreadManager.mainThread);
+ },
+
+ /**
+* Kicks off another wait until data can be written to the output stream.
+*/
+ _waitToWriteData: function()
+ {
+ dumpn("*** _waitToWriteData");
+
+ var pendingData = this._pendingData;
+ NS_ASSERT(pendingData.length > 0, "no pending data to write?");
+ NS_ASSERT(pendingData[0].length > 0, "buffered an empty write?");
+
+ this._sink.asyncWait(this, 0, pendingData[0].length,
+ gThreadManager.mainThread);
+ },
+
+ /**
+* Kicks off a wait for the sink to which data is being copied to be closed.
+* We wait for stream closure when we don't have any data to be copied, rather
+* than waiting to write a specific amount of data. We can't wait to write
+* data because the sink might be infinitely writable, and if no data appears
+* in the source for a long time we might have to spin quite a bit waiting to
+* write, waiting to write again, &c. Waiting on stream closure instead means
+* we'll get just one notification if the sink dies. Note that when data
+* starts arriving from the sink we'll resume waiting for data to be written,
+* dropping this closure-only callback entirely.
+*/
+ _waitForSinkClosure: function()
+ {
+ dumpn("*** _waitForSinkClosure");
+
+ this._sink.asyncWait(this, Ci.nsIAsyncOutputStream.WAIT_CLOSURE_ONLY, 0,
+ gThreadManager.mainThread);
+ },
+
+ /**
+* Closes input with the given status, if it hasn't already been closed;
+* otherwise a no-op.
+*
+* @param status : nsresult
+* status code use to close the source stream if necessary
+*/
+ _finishSource: function(status)
+ {
+ dumpn("*** _finishSource(" + status.toString(16) + ")");
+
+ if (this._source !== null)
+ {
+ this._source.closeWithStatus(status);
+ this._source = null;
+ }
+ }
+};
+
+
+/**
+* A container for utility functions used with HTTP headers.
+*/
+const headerUtils =
+{
+ /**
+* Normalizes fieldName (by converting it to lowercase) and ensures it is a
+* valid header field name (although not necessarily one specified in RFC
+* 2616).
+*
+* @throws NS_ERROR_INVALID_ARG
+* if fieldName does not match the field-name production in RFC 2616
+* @returns string
+* fieldName converted to lowercase if it is a valid header, for characters
+* where case conversion is possible
+*/
+ normalizeFieldName: function(fieldName)
+ {
+ if (fieldName == "")
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ for (var i = 0, sz = fieldName.length; i < sz; i++)
+ {
+ if (!IS_TOKEN_ARRAY[fieldName.charCodeAt(i)])
+ {
+ dumpn(fieldName + " is not a valid header field name!");
+ throw Cr.NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ return fieldName.toLowerCase();
+ },
+
+ /**
+* Ensures that fieldValue is a valid header field value (although not
+* necessarily as specified in RFC 2616 if the corresponding field name is
+* part of the HTTP protocol), normalizes the value if it is, and
+* returns the normalized value.
+*
+* @param fieldValue : string
+* a value to be normalized as an HTTP header field value
+* @throws NS_ERROR_INVALID_ARG
+* if fieldValue does not match the field-value production in RFC 2616
+* @returns string
+* fieldValue as a normalized HTTP header field value
+*/
+ normalizeFieldValue: function(fieldValue)
+ {
+ // field-value = *( field-content | LWS )
+ // field-content = <the OCTETs making up the field-value
+ // and consisting of either *TEXT or combinations
+ // of token, separators, and quoted-string>
+ // TEXT = <any OCTET except CTLs,
+ // but including LWS>
+ // LWS = [CRLF] 1*( SP | HT )
+ //
+ // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
+ // qdtext = <any TEXT except <">>
+ // quoted-pair = "\" CHAR
+ // CHAR = <any US-ASCII character (octets 0 - 127)>
+
+ // Any LWS that occurs between field-content MAY be replaced with a single
+ // SP before interpreting the field value or forwarding the message
+ // downstream (section 4.2); we replace 1*LWS with a single SP
+ var val = fieldValue.replace(/(?:(?:\r\n)?[ \t]+)+/g, " ");
+
+ // remove leading/trailing LWS (which has been converted to SP)
+ val = val.replace(/^ +/, "").replace(/ +$/, "");
+
+ // that should have taken care of all CTLs, so val should contain no CTLs
+ for (var i = 0, len = val.length; i < len; i++)
+ if (isCTL(val.charCodeAt(i)))
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ // XXX disallows quoted-pair where CHAR is a CTL -- will not invalidly
+ // normalize, however, so this can be construed as a tightening of the
+ // spec and not entirely as a bug
+ return val;
+ }
+};
+
+
+
+/**
+* Converts the given string into a string which is safe for use in an HTML
+* context.
+*
+* @param str : string
+* the string to make HTML-safe
+* @returns string
+* an HTML-safe version of str
+*/
+function htmlEscape(str)
+{
+ // this is naive, but it'll work
+ var s = "";
+ for (var i = 0; i < str.length; i++)
+ s += "&#" + str.charCodeAt(i) + ";";
+ return s;
+}
+
+
+/**
+* Constructs an object representing an HTTP version (see section 3.1).
+*
+* @param versionString
+* a string of the form "#.#", where # is an non-negative decimal integer with
+* or without leading zeros
+* @throws
+* if versionString does not specify a valid HTTP version number
+*/
+function nsHttpVersion(versionString)
+{
+ var matches = /^(\d+)\.(\d+)$/.exec(versionString);
+ if (!matches)
+ throw "Not a valid HTTP version!";
+
+ /** The major version number of this, as a number. */
+ this.major = parseInt(matches[1], 10);
+
+ /** The minor version number of this, as a number. */
+ this.minor = parseInt(matches[2], 10);
+
+ if (isNaN(this.major) || isNaN(this.minor) ||
+ this.major < 0 || this.minor < 0)
+ throw "Not a valid HTTP version!";
+}
+nsHttpVersion.prototype =
+{
+ /**
+* Returns the standard string representation of the HTTP version represented
+* by this (e.g., "1.1").
+*/
+ toString: function ()
+ {
+ return this.major + "." + this.minor;
+ },
+
+ /**
+* Returns true if this represents the same HTTP version as otherVersion,
+* false otherwise.
+*
+* @param otherVersion : nsHttpVersion
+* the version to compare against this
+*/
+ equals: function (otherVersion)
+ {
+ return this.major == otherVersion.major &&
+ this.minor == otherVersion.minor;
+ },
+
+ /** True if this >= otherVersion, false otherwise. */
+ atLeast: function(otherVersion)
+ {
+ return this.major > otherVersion.major ||
+ (this.major == otherVersion.major &&
+ this.minor >= otherVersion.minor);
+ }
+};
+
+nsHttpVersion.HTTP_1_0 = new nsHttpVersion("1.0");
+nsHttpVersion.HTTP_1_1 = new nsHttpVersion("1.1");
+
+
+/**
+* An object which stores HTTP headers for a request or response.
+*
+* Note that since headers are case-insensitive, this object converts headers to
+* lowercase before storing them. This allows the getHeader and hasHeader
+* methods to work correctly for any case of a header, but it means that the
+* values returned by .enumerator may not be equal case-sensitively to the
+* values passed to setHeader when adding headers to this.
+*/
+function nsHttpHeaders()
+{
+ /**
+* A hash of headers, with header field names as the keys and header field
+* values as the values. Header field names are case-insensitive, but upon
+* insertion here they are converted to lowercase. Header field values are
+* normalized upon insertion to contain no leading or trailing whitespace.
+*
+* Note also that per RFC 2616, section 4.2, two headers with the same name in
+* a message may be treated as one header with the same field name and a field
+* value consisting of the separate field values joined together with a "," in
+* their original order. This hash stores multiple headers with the same name
+* in this manner.
+*/
+ this._headers = {};
+}
+nsHttpHeaders.prototype =
+{
+ /**
+* Sets the header represented by name and value in this.
+*
+* @param name : string
+* the header name
+* @param value : string
+* the header value
+* @throws NS_ERROR_INVALID_ARG
+* if name or value is not a valid header component
+*/
+ setHeader: function(fieldName, fieldValue, merge)
+ {
+ var name = headerUtils.normalizeFieldName(fieldName);
+ var value = headerUtils.normalizeFieldValue(fieldValue);
+
+ // The following three headers are stored as arrays because their real-world
+ // syntax prevents joining individual headers into a single header using
+ // ",". See also <http://hg.mozilla.org/mozilla-central/diff/9b2a99adc05e/netwerk/protocol/http/src/nsHttpHeaderArray.cpp#l77>
+ if (merge && name in this._headers)
+ {
+ if (name === "www-authenticate" ||
+ name === "proxy-authenticate" ||
+ name === "set-cookie")
+ {
+ this._headers[name].push(value);
+ }
+ else
+ {
+ this._headers[name][0] += "," + value;
+ NS_ASSERT(this._headers[name].length === 1,
+ "how'd a non-special header have multiple values?")
+ }
+ }
+ else
+ {
+ this._headers[name] = [value];
+ }
+ },
+
+ /**
+* Returns the value for the header specified by this.
+*
+* @throws NS_ERROR_INVALID_ARG
+* if fieldName does not constitute a valid header field name
+* @throws NS_ERROR_NOT_AVAILABLE
+* if the given header does not exist in this
+* @returns string
+* the field value for the given header, possibly with non-semantic changes
+* (i.e., leading/trailing whitespace stripped, whitespace runs replaced
+* with spaces, etc.) at the option of the implementation; multiple
+* instances of the header will be combined with a comma, except for
+* the three headers noted in the description of getHeaderValues
+*/
+ getHeader: function(fieldName)
+ {
+ return this.getHeaderValues(fieldName).join("\n");
+ },
+
+ /**
+* Returns the value for the header specified by fieldName as an array.
+*
+* @throws NS_ERROR_INVALID_ARG
+* if fieldName does not constitute a valid header field name
+* @throws NS_ERROR_NOT_AVAILABLE
+* if the given header does not exist in this
+* @returns [string]
+* an array of all the header values in this for the given
+* header name. Header values will generally be collapsed
+* into a single header by joining all header values together
+* with commas, but certain headers (Proxy-Authenticate,
+* WWW-Authenticate, and Set-Cookie) violate the HTTP spec
+* and cannot be collapsed in this manner. For these headers
+* only, the returned array may contain multiple elements if
+* that header has been added more than once.
+*/
+ getHeaderValues: function(fieldName)
+ {
+ var name = headerUtils.normalizeFieldName(fieldName);
+
+ if (name in this._headers)
+ return this._headers[name];
+ else
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ },
+
+ /**
+* Returns true if a header with the given field name exists in this, false
+* otherwise.
+*
+* @param fieldName : string
+* the field name whose existence is to be determined in this
+* @throws NS_ERROR_INVALID_ARG
+* if fieldName does not constitute a valid header field name
+* @returns boolean
+* true if the header's present, false otherwise
+*/
+ hasHeader: function(fieldName)
+ {
+ var name = headerUtils.normalizeFieldName(fieldName);
+ return (name in this._headers);
+ },
+
+ /**
+* Returns a new enumerator over the field names of the headers in this, as
+* nsISupportsStrings. The names returned will be in lowercase, regardless of
+* how they were input using setHeader (header names are case-insensitive per
+* RFC 2616).
+*/
+ get enumerator()
+ {
+ var headers = [];
+ for (var i in this._headers)
+ {
+ var supports = new SupportsString();
+ supports.data = i;
+ headers.push(supports);
+ }
+
+ return new nsSimpleEnumerator(headers);
+ }
+};
+
+
+/**
+* Constructs an nsISimpleEnumerator for the given array of items.
+*
+* @param items : Array
+* the items, which must all implement nsISupports
+*/
+function nsSimpleEnumerator(items)
+{
+ this._items = items;
+ this._nextIndex = 0;
+}
+nsSimpleEnumerator.prototype =
+{
+ hasMoreElements: function()
+ {
+ return this._nextIndex < this._items.length;
+ },
+ getNext: function()
+ {
+ if (!this.hasMoreElements())
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ return this._items[this._nextIndex++];
+ },
+ QueryInterface: function(aIID)
+ {
+ if (Ci.nsISimpleEnumerator.equals(aIID) ||
+ Ci.nsISupports.equals(aIID))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+
+/**
+* A representation of the data in an HTTP request.
+*
+* @param port : uint
+* the port on which the server receiving this request runs
+*/
+function Request(port)
+{
+ /** Method of this request, e.g. GET or POST. */
+ this._method = "";
+
+ /** Path of the requested resource; empty paths are converted to '/'. */
+ this._path = "";
+
+ /** Query string, if any, associated with this request (not including '?'). */
+ this._queryString = "";
+
+ /** Scheme of requested resource, usually http, always lowercase. */
+ this._scheme = "http";
+
+ /** Hostname on which the requested resource resides. */
+ this._host = undefined;
+
+ /** Port number over which the request was received. */
+ this._port = port;
+
+ var bodyPipe = new Pipe(false, false, 0, PR_UINT32_MAX, null);
+
+ /** Stream from which data in this request's body may be read. */
+ this._bodyInputStream = bodyPipe.inputStream;
+
+ /** Stream to which data in this request's body is written. */
+ this._bodyOutputStream = bodyPipe.outputStream;
+
+ /**
+* The headers in this request.
+*/
+ this._headers = new nsHttpHeaders();
+
+ /**
+* For the addition of ad-hoc properties and new functionality without having
+* to change nsIHttpRequest every time; currently lazily created, as its only
+* use is in directory listings.
+*/
+ this._bag = null;
+}
+Request.prototype =
+{
+ // SERVER METADATA
+
+ //
+ // see nsIHttpRequest.scheme
+ //
+ get scheme()
+ {
+ return this._scheme;
+ },
+
+ //
+ // see nsIHttpRequest.host
+ //
+ get host()
+ {
+ return this._host;
+ },
+
+ //
+ // see nsIHttpRequest.port
+ //
+ get port()
+ {
+ return this._port;
+ },
+
+ // REQUEST LINE
+
+ //
+ // see nsIHttpRequest.method
+ //
+ get method()
+ {
+ return this._method;
+ },
+
+ //
+ // see nsIHttpRequest.httpVersion
+ //
+ get httpVersion()
+ {
+ return this._httpVersion.toString();
+ },
+
+ //
+ // see nsIHttpRequest.path
+ //
+ get path()
+ {
+ return this._path;
+ },
+
+ //
+ // see nsIHttpRequest.queryString
+ //
+ get queryString()
+ {
+ return this._queryString;
+ },
+
+ // HEADERS
+
+ //
+ // see nsIHttpRequest.getHeader
+ //
+ getHeader: function(name)
+ {
+ return this._headers.getHeader(name);
+ },
+
+ //
+ // see nsIHttpRequest.hasHeader
+ //
+ hasHeader: function(name)
+ {
+ return this._headers.hasHeader(name);
+ },
+
+ //
+ // see nsIHttpRequest.headers
+ //
+ get headers()
+ {
+ return this._headers.enumerator;
+ },
+
+ //
+ // see nsIPropertyBag.enumerator
+ //
+ get enumerator()
+ {
+ this._ensurePropertyBag();
+ return this._bag.enumerator;
+ },
+
+ //
+ // see nsIHttpRequest.headers
+ //
+ get bodyInputStream()
+ {
+ return this._bodyInputStream;
+ },
+
+ //
+ // see nsIPropertyBag.getProperty
+ //
+ getProperty: function(name)
+ {
+ this._ensurePropertyBag();
+ return this._bag.getProperty(name);
+ },
+
+
+ // NSISUPPORTS
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIHttpRequest) || iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // PRIVATE IMPLEMENTATION
+
+ /** Ensures a property bag has been created for ad-hoc behaviors. */
+ _ensurePropertyBag: function()
+ {
+ if (!this._bag)
+ this._bag = new WritablePropertyBag();
+ }
+};
+
+
+// XPCOM trappings
+if ("XPCOMUtils" in this && // Firefox 3.6 doesn't load XPCOMUtils in this scope for some reason...
+ "generateNSGetFactory" in XPCOMUtils) {
+ var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]);
+}
+
+
+
+/**
+* Creates a new HTTP server listening for loopback traffic on the given port,
+* starts it, and runs the server until the server processes a shutdown request,
+* spinning an event loop so that events posted by the server's socket are
+* processed.
+*
+* This method is primarily intended for use in running this script from within
+* xpcshell and running a functional HTTP server without having to deal with
+* non-essential details.
+*
+* Note that running multiple servers using variants of this method probably
+* doesn't work, simply due to how the internal event loop is spun and stopped.
+*
+* @note
+* This method only works with Mozilla 1.9 (i.e., Firefox 3 or trunk code);
+* you should use this server as a component in Mozilla 1.8.
+* @param port
+* the port on which the server will run, or -1 if there exists no preference
+* for a specific port; note that attempting to use some values for this
+* parameter (particularly those below 1024) may cause this method to throw or
+* may result in the server being prematurely shut down
+* @param basePath
+* a local directory from which requests will be served (i.e., if this is
+* "/home/jwalden/" then a request to /index.html will load
+* /home/jwalden/index.html); if this is omitted, only the default URLs in
+* this server implementation will be functional
+*/
+function server(port, basePath)
+{
+ if (basePath)
+ {
+ var lp = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ lp.initWithPath(basePath);
+ }
+
+ // if you're running this, you probably want to see debugging info
+ DEBUG = true;
+
+ var srv = new nsHttpServer();
+ if (lp)
+ srv.registerDirectory("/", lp);
+ srv.registerContentType("sjs", SJS_TYPE);
+ srv.identity.setPrimary("http", "localhost", port);
+ srv.start(port);
+
+ var thread = gThreadManager.currentThread;
+ while (!srv.isStopped())
+ thread.processNextEvent(true);
+
+ // get rid of any pending requests
+ while (thread.hasPendingEvents())
+ thread.processNextEvent(true);
+
+ DEBUG = false;
+}
+
+function startServerAsync(port, basePath)
+{
+ if (basePath)
+ {
+ var lp = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ lp.initWithPath(basePath);
+ }
+
+ var srv = new nsHttpServer();
+ if (lp)
+ srv.registerDirectory("/", lp);
+ srv.registerContentType("sjs", "sjs");
+ srv.identity.setPrimary("http", "localhost", port);
+ srv.start(port);
+ return srv;
+}
+
+exports.nsHttpServer = nsHttpServer;
+exports.ScriptableInputStream = ScriptableInputStream;
+exports.server = server;
+exports.startServerAsync = startServerAsync;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/keyboard/hotkeys.js b/tools/addon-sdk-1.7/packages/api-utils/lib/keyboard/hotkeys.js
new file mode 100644
index 0000000..bb296ed
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/keyboard/hotkeys.js
@@ -0,0 +1,107 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { observer: keyboardObserver } = require("./observer");
+const { getKeyForCode, normalize, isFunctionKey,
+ MODIFIERS } = require("./utils");
+
+/**
+ * Register a global `hotkey` that executes `listener` when the key combination
+ * in `hotkey` is pressed. If more then one `listener` is registered on the same
+ * key combination only last one will be executed.
+ *
+ * @param {string} hotkey
+ * Key combination in the format of 'modifier key'.
+ *
+ * Examples:
+ *
+ * "accel s"
+ * "meta shift i"
+ * "control alt d"
+ *
+ * Modifier keynames:
+ *
+ * - **shift**: The Shift key.
+ * - **alt**: The Alt key. On the Macintosh, this is the Option key. On
+ * Macintosh this can only be used in conjunction with another modifier,
+ * since `Alt+Letter` combinations are reserved for entering special
+ * characters in text.
+ * - **meta**: The Meta key. On the Macintosh, this is the Command key.
+ * - **control**: The Control key.
+ * - **accel**: The key used for keyboard shortcuts on the user's platform,
+ * which is Control on Windows and Linux, and Command on Mac. Usually, this
+ * would be the value you would use.
+ *
+ * @param {function} listener
+ * Function to execute when the `hotkey` is executed.
+ */
+exports.register = function register(hotkey, listener) {
+ hotkey = normalize(hotkey);
+ hotkeys[hotkey] = listener;
+};
+
+/**
+ * Unregister a global `hotkey`. If passed `listener` is not the one registered
+ * for the given `hotkey`, the call to this function will be ignored.
+ *
+ * @param {string} hotkey
+ * Key combination in the format of 'modifier key'.
+ * @param {function} listener
+ * Function that will be invoked when the `hotkey` is pressed.
+ */
+exports.unregister = function unregister(hotkey, listener) {
+ hotkey = normalize(hotkey);
+ if (hotkeys[hotkey] === listener)
+ delete hotkeys[hotkey];
+};
+
+/**
+ * Map of hotkeys and associated functions.
+ */
+const hotkeys = exports.hotkeys = {};
+
+keyboardObserver.on("keydown", function onKeypress(event, window) {
+ let key, modifiers = [];
+ let isChar = "isChar" in event && event.isChar;
+ let which = "which" in event ? event.which : null;
+ let keyCode = "keyCode" in event ? event.keyCode : null;
+
+ if ("shiftKey" in event && event.shiftKey)
+ modifiers.push("shift");
+ if ("altKey" in event && event.altKey)
+ modifiers.push("alt");
+ if ("ctrlKey" in event && event.ctrlKey)
+ modifiers.push("control");
+ if ("metaKey" in event && event.metaKey)
+ modifiers.push("meta");
+
+ // If it's not a printable character then we fall back to a human readable
+ // equivalent of one of the following constants.
+ // http://mxr.mozilla.org/mozilla-central/source/dom/interfaces/events/nsIDOMKeyEvent.idl
+ key = getKeyForCode(keyCode);
+
+ // If only non-function (f1 - f24) key or only modifiers are pressed we don't
+ // have a valid combination so we return immediately (Also, sometimes
+ // `keyCode` may be one for the modifier which means we do not have a
+ // modifier).
+ if (!key || (!isFunctionKey(key) && !modifiers.length) || key in MODIFIERS)
+ return;
+
+ let combination = normalize({ key: key, modifiers: modifiers });
+ let hotkey = hotkeys[combination];
+
+ if (hotkey) {
+ try {
+ hotkey();
+ } catch (exception) {
+ console.exception(exception);
+ } finally {
+ // Work around bug 582052 by preventing the (nonexistent) default action.
+ event.preventDefault();
+ }
+ }
+});
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/keyboard/observer.js b/tools/addon-sdk-1.7/packages/api-utils/lib/keyboard/observer.js
new file mode 100644
index 0000000..dd24865
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/keyboard/observer.js
@@ -0,0 +1,53 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { Trait } = require("../light-traits");
+const { EventEmitterTrait: EventEmitter } = require("../events");
+const { DOMEventAssembler } = require("../events/assembler");
+const { browserWindowIterator, isBrowser } = require('../window-utils');
+const { observer: windowObserver } = require("../windows/observer");
+
+// Event emitter objects used to register listeners and emit events on them
+// when they occur.
+const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
+ /**
+ * Method is implemented by `EventEmitter` and is used just for emitting
+ * events on registered listeners.
+ */
+ _emit: Trait.required,
+ /**
+ * Events that are supported and emitted by the module.
+ */
+ supportedEventsTypes: [ "keydown", "keyup", "keypress" ],
+ /**
+ * Function handles all the supported events on all the windows that are
+ * observed. Method is used to proxy events to the listeners registered on
+ * this event emitter.
+ * @param {Event} event
+ * Keyboard event being emitted.
+ */
+ handleEvent: function handleEvent(event) {
+ this._emit(event.type, event, event.target.ownerDocument.defaultView);
+ }
+});
+
+// Adding each opened window to a list of observed windows.
+windowObserver.on("open", function onOpen(window) {
+ if (isBrowser(window))
+ observer.observe(window);
+});
+// Removing each closed window form the list of observed windows.
+windowObserver.on("close", function onClose(window) {
+ if (isBrowser(window))
+ observer.ignore(window);
+});
+
+// Making observer aware of already opened windows.
+for each (let window in browserWindowIterator())
+ observer.observe(window);
+
+exports.observer = observer;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/keyboard/utils.js b/tools/addon-sdk-1.7/packages/api-utils/lib/keyboard/utils.js
new file mode 100644
index 0000000..4693a12
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/keyboard/utils.js
@@ -0,0 +1,186 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 } = require("chrome");
+const runtime = require("../runtime");
+const { isString } = require("../type");
+const array = require("../array");
+
+
+const SWP = "{{SEPARATOR}}";
+const SEPARATOR = "-"
+const INVALID_COMBINATION = "Hotkey key combination must contain one or more " +
+ "modifiers and only one key";
+
+// Map of modifier key mappings.
+const MODIFIERS = exports.MODIFIERS = {
+ 'accel': runtime.OS === "Darwin" ? 'meta' : 'control',
+ 'meta': 'meta',
+ 'control': 'control',
+ 'ctrl': 'control',
+ 'option': 'alt',
+ 'command': 'meta',
+ 'alt': 'alt',
+ 'shift': 'shift'
+};
+
+// Hash of key:code pairs for all the chars supported by `nsIDOMKeyEvent`.
+// This is just a copy of the `nsIDOMKeyEvent` hash with normalized names.
+// @See: http://mxr.mozilla.org/mozilla-central/source/dom/interfaces/events/nsIDOMKeyEvent.idl
+const CODES = exports.CODES = new function Codes() {
+ let nsIDOMKeyEvent = Ci.nsIDOMKeyEvent;
+ // Names that will be substituted with a shorter analogs.
+ let aliases = {
+ 'subtract': '-',
+ 'add': '+',
+ 'equals': '=',
+ 'slash': '/',
+ 'backslash': '\\',
+ 'openbracket': '[',
+ 'closebracket': ']',
+ 'quote': '\'',
+ 'backquote': '`',
+ 'period': '.',
+ 'semicolon': ';',
+ 'comma': ','
+ };
+
+ // Normalizing keys and copying values to `this` object.
+ Object.keys(nsIDOMKeyEvent).filter(function(key) {
+ // Filter out only key codes.
+ return key.indexOf('DOM_VK') === 0;
+ }).map(function(key) {
+ // Map to key:values
+ return [ key, nsIDOMKeyEvent[key] ];
+ }).map(function([key, value]) {
+ return [ key.replace('DOM_VK_', '').replace('_', '').toLowerCase(), value ];
+ }).forEach(function ([ key, value ]) {
+ this[aliases[key] || key] = value;
+ }, this);
+};
+
+// Inverted `CODES` hash of `code:key`.
+const KEYS = exports.KEYS = new function Keys() {
+ Object.keys(CODES).forEach(function(key) {
+ this[CODES[key]] = key;
+ }, this)
+}
+
+exports.getKeyForCode = function getKeyForCode(code) {
+ return (code in KEYS) && KEYS[code];
+};
+exports.getCodeForKey = function getCodeForKey(key) {
+ return (key in CODES) && CODES[key];
+};
+
+/**
+ * Utility function that takes string or JSON that defines a `hotkey` and
+ * returns normalized string version of it.
+ * @param {JSON|String} hotkey
+ * @param {String} [separator=" "]
+ * Optional string that represents separator used to concatenate keys in the
+ * given `hotkey`.
+ * @returns {String}
+ * @examples
+ *
+ * require("keyboard/hotkeys").normalize("b Shift accel");
+ * // 'control shift b' -> on windows & linux
+ * // 'meta shift b' -> on mac
+ * require("keyboard/hotkeys").normalize("alt-d-shift", "-");
+ * // 'alt shift d'
+ */
+var normalize = exports.normalize = function normalize(hotkey, separator) {
+ if (!isString(hotkey))
+ hotkey = toString(hotkey, separator);
+ return toString(toJSON(hotkey, separator), separator);
+};
+
+/*
+ * Utility function that splits a string of characters that defines a `hotkey`
+ * into modifier keys and the defining key.
+ * @param {String} hotkey
+ * @param {String} [separator=" "]
+ * Optional string that represents separator used to concatenate keys in the
+ * given `hotkey`.
+ * @returns {JSON}
+ * @examples
+ *
+ * require("keyboard/hotkeys").toJSON("accel shift b");
+ * // { key: 'b', modifiers: [ 'control', 'shift' ] } -> on windows & linux
+ * // { key: 'b', modifiers: [ 'meta', 'shift' ] } -> on mac
+ *
+ * require("keyboard/hotkeys").normalize("alt-d-shift", "-");
+ * // { key: 'd', modifiers: [ 'alt', 'shift' ] }
+ */
+var toJSON = exports.toJSON = function toJSON(hotkey, separator) {
+ separator = separator || SEPARATOR;
+ // Since default separator is `-`, combination may take form of `alt--`. To
+ // avoid misbehavior we replace `--` with `-{{SEPARATOR}}` where
+ // `{{SEPARATOR}}` can be swapped later.
+ hotkey = hotkey.toLowerCase().replace(separator + separator, separator + SWP);
+
+ let value = {};
+ let modifiers = [];
+ let keys = hotkey.split(separator);
+ keys.forEach(function(name) {
+ // If name is `SEPARATOR` than we swap it back.
+ if (name === SWP)
+ name = separator;
+ if (name in MODIFIERS) {
+ array.add(modifiers, MODIFIERS[name]);
+ } else {
+ if (!value.key)
+ value.key = name;
+ else
+ throw new TypeError(INVALID_COMBINATION);
+ }
+ });
+
+ if (!value.key)
+ throw new TypeError(INVALID_COMBINATION);
+
+ value.modifiers = modifiers.sort();
+ return value;
+};
+
+/**
+ * Utility function that takes object that defines a `hotkey` and returns
+ * string representation of it.
+ *
+ * _Please note that this function does not validates data neither it normalizes
+ * it, if you are unsure that data is well formed use `normalize` function
+ * instead.
+ *
+ * @param {JSON} hotkey
+ * @param {String} [separator=" "]
+ * Optional string that represents separator used to concatenate keys in the
+ * given `hotkey`.
+ * @returns {String}
+ * @examples
+ *
+ * require("keyboard/hotkeys").toString({
+ * key: 'b',
+ * modifiers: [ 'control', 'shift' ]
+ * }, '+');
+ * // 'control+shift+b
+ *
+ */
+var toString = exports.toString = function toString(hotkey, separator) {
+ let keys = hotkey.modifiers.slice();
+ keys.push(hotkey.key);
+ return keys.join(separator || SEPARATOR);
+};
+
+/**
+ * Utility function takes `key` name and returns `true` if it's function key
+ * (F1, ..., F24) and `false` if it's not.
+ */
+var isFunctionKey = exports.isFunctionKey = function isFunctionKey(key) {
+ var $
+ return key[0].toLowerCase() === 'f' &&
+ ($ = parseInt(key.substr(1)), 0 < $ && $ < 25);
+};
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/l10n/locale.js b/tools/addon-sdk-1.7/packages/api-utils/lib/l10n/locale.js
new file mode 100644
index 0000000..ed82789
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/l10n/locale.js
@@ -0,0 +1,122 @@
+/* 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 prefs = require("preferences-service");
+const { Cu, Cc, Ci } = require("chrome");
+const { Services } = Cu.import("resource://gre/modules/Services.jsm");
+
+
+/**
+ * Gets the currently selected locale for display.
+ * Gets all usable locale that we can use sorted by priority of relevance
+ * @return Array of locales, begins with highest priority
+ */
+const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
+const PREF_SELECTED_LOCALE = "general.useragent.locale";
+const PREF_ACCEPT_LANGUAGES = "intl.accept_languages";
+exports.getPreferedLocales = function getPreferedLocales() {
+ let locales = [];
+
+ function addLocale(locale) {
+ locale = locale.toLowerCase();
+ if (locales.indexOf(locale) === -1)
+ locales.push(locale);
+ }
+
+ // Most important locale is OS one. But we use it, only if
+ // "intl.locale.matchOS" pref is set to `true`.
+ // Currently only used for multi-locales mobile builds.
+ // http://mxr.mozilla.org/mozilla-central/source/mobile/android/installer/Makefile.in#46
+ if (prefs.get(PREF_MATCH_OS_LOCALE, false)) {
+ let localeService = Cc["@mozilla.org/intl/nslocaleservice;1"].
+ getService(Ci.nsILocaleService);
+ let osLocale = localeService.getLocaleComponentForUserAgent();
+ addLocale(osLocale);
+ }
+
+ // In some cases, mainly on Fennec and on Linux version,
+ // `general.useragent.locale` is a special 'localized' value, like:
+ // "chrome://global/locale/intl.properties"
+ let browserUiLocale = prefs.getLocalized(PREF_SELECTED_LOCALE, "") ||
+ prefs.get(PREF_SELECTED_LOCALE, "");
+ if (browserUiLocale)
+ addLocale(browserUiLocale);
+
+
+ // Third priority is the list of locales used for web content
+ let contentLocales = prefs.get(PREF_ACCEPT_LANGUAGES, "");
+ if (contentLocales) {
+ // This list is a string of locales seperated by commas.
+ // There is spaces after commas, so strip each item
+ for each(let locale in contentLocales.split(","))
+ addLocale(locale.replace(/(^\s+)|(\s+$)/g, ""));
+ }
+
+ // Finally, we ensure that en-US is the final fallback if it wasn't added
+ addLocale("en-US");
+
+ return locales;
+}
+
+/**
+ * Selects the closest matching locale from a list of locales.
+ *
+ * @param aLocales
+ * An array of available locales
+ * @param aMatchLocales
+ * An array of prefered locales, ordered by priority. Most wanted first.
+ * Locales have to be in lowercase.
+ * If null, uses getPreferedLocales() results
+ * @return the best match for the currently selected locale
+ *
+ * Stolen from http://mxr.mozilla.org/mozilla-central/source/toolkit/mozapps/extensions/XPIProvider.jsm
+ */
+exports.findClosestLocale = function findClosestLocale(aLocales, aMatchLocales) {
+
+ aMatchLocales = aMatchLocales || exports.getPreferedLocales();
+
+ // Holds the best matching localized resource
+ let bestmatch = null;
+ // The number of locale parts it matched with
+ let bestmatchcount = 0;
+ // The number of locale parts in the match
+ let bestpartcount = 0;
+
+ for each (let locale in aMatchLocales) {
+ let lparts = locale.split("-");
+ for each (let localized in aLocales) {
+ let found = localized.toLowerCase();
+ // Exact match is returned immediately
+ if (locale == found)
+ return localized;
+
+ let fparts = found.split("-");
+ /* If we have found a possible match and this one isn't any longer
+ then we dont need to check further. */
+ if (bestmatch && fparts.length < bestmatchcount)
+ continue;
+
+ // Count the number of parts that match
+ let maxmatchcount = Math.min(fparts.length, lparts.length);
+ let matchcount = 0;
+ while (matchcount < maxmatchcount &&
+ fparts[matchcount] == lparts[matchcount])
+ matchcount++;
+
+ /* If we matched more than the last best match or matched the same and
+ this locale is less specific than the last best match. */
+ if (matchcount > bestmatchcount ||
+ (matchcount == bestmatchcount && fparts.length < bestpartcount)) {
+ bestmatch = localized;
+ bestmatchcount = matchcount;
+ bestpartcount = fparts.length;
+ }
+ }
+ // If we found a valid match for this locale return it
+ if (bestmatch)
+ return bestmatch;
+ }
+ return null;
+}
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/l10n/plural-rules.js b/tools/addon-sdk-1.7/packages/api-utils/lib/l10n/plural-rules.js
new file mode 100644
index 0000000..c891870
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/l10n/plural-rules.js
@@ -0,0 +1,387 @@
+/* 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/. */
+
+// This file is automatically generated with /python-lib/plural-rules-generator.py
+// Fetching data from: http://unicode.org/repos/cldr/trunk/common/supplemental/plurals.xml
+
+// Mapping of short locale name == to == > rule index in following list
+const LOCALES_TO_RULES = {
+ "af": 2,
+ "ak": 3,
+ "am": 3,
+ "ar": 1,
+ "asa": 2,
+ "az": 0,
+ "be": 10,
+ "bem": 2,
+ "bez": 2,
+ "bg": 2,
+ "bh": 3,
+ "bm": 0,
+ "bn": 2,
+ "bo": 0,
+ "br": 19,
+ "brx": 2,
+ "bs": 10,
+ "ca": 2,
+ "cgg": 2,
+ "chr": 2,
+ "cs": 11,
+ "cy": 16,
+ "da": 2,
+ "de": 2,
+ "dv": 2,
+ "dz": 0,
+ "ee": 2,
+ "el": 2,
+ "en": 2,
+ "eo": 2,
+ "es": 2,
+ "et": 2,
+ "eu": 2,
+ "fa": 0,
+ "ff": 4,
+ "fi": 2,
+ "fil": 3,
+ "fo": 2,
+ "fr": 4,
+ "fur": 2,
+ "fy": 2,
+ "ga": 7,
+ "gd": 23,
+ "gl": 2,
+ "gsw": 2,
+ "gu": 2,
+ "guw": 3,
+ "gv": 22,
+ "ha": 2,
+ "haw": 2,
+ "he": 2,
+ "hi": 3,
+ "hr": 10,
+ "hu": 0,
+ "id": 0,
+ "ig": 0,
+ "ii": 0,
+ "is": 2,
+ "it": 2,
+ "iu": 6,
+ "ja": 0,
+ "jmc": 2,
+ "jv": 0,
+ "ka": 0,
+ "kab": 4,
+ "kaj": 2,
+ "kcg": 2,
+ "kde": 0,
+ "kea": 0,
+ "kk": 2,
+ "kl": 2,
+ "km": 0,
+ "kn": 0,
+ "ko": 0,
+ "ksb": 2,
+ "ksh": 20,
+ "ku": 2,
+ "kw": 6,
+ "lag": 17,
+ "lb": 2,
+ "lg": 2,
+ "ln": 3,
+ "lo": 0,
+ "lt": 9,
+ "lv": 5,
+ "mas": 2,
+ "mg": 3,
+ "mk": 15,
+ "ml": 2,
+ "mn": 2,
+ "mo": 8,
+ "mr": 2,
+ "ms": 0,
+ "mt": 14,
+ "my": 0,
+ "nah": 2,
+ "naq": 6,
+ "nb": 2,
+ "nd": 2,
+ "ne": 2,
+ "nl": 2,
+ "nn": 2,
+ "no": 2,
+ "nr": 2,
+ "nso": 3,
+ "ny": 2,
+ "nyn": 2,
+ "om": 2,
+ "or": 2,
+ "pa": 2,
+ "pap": 2,
+ "pl": 12,
+ "ps": 2,
+ "pt": 2,
+ "rm": 2,
+ "ro": 8,
+ "rof": 2,
+ "ru": 10,
+ "rwk": 2,
+ "sah": 0,
+ "saq": 2,
+ "se": 6,
+ "seh": 2,
+ "ses": 0,
+ "sg": 0,
+ "sh": 10,
+ "shi": 18,
+ "sk": 11,
+ "sl": 13,
+ "sma": 6,
+ "smi": 6,
+ "smj": 6,
+ "smn": 6,
+ "sms": 6,
+ "sn": 2,
+ "so": 2,
+ "sq": 2,
+ "sr": 10,
+ "ss": 2,
+ "ssy": 2,
+ "st": 2,
+ "sv": 2,
+ "sw": 2,
+ "syr": 2,
+ "ta": 2,
+ "te": 2,
+ "teo": 2,
+ "th": 0,
+ "ti": 3,
+ "tig": 2,
+ "tk": 2,
+ "tl": 3,
+ "tn": 2,
+ "to": 0,
+ "tr": 0,
+ "ts": 2,
+ "tzm": 21,
+ "uk": 10,
+ "ur": 2,
+ "ve": 2,
+ "vi": 0,
+ "vun": 2,
+ "wa": 3,
+ "wae": 2,
+ "wo": 0,
+ "xh": 2,
+ "xog": 2,
+ "yo": 0,
+ "zh": 0,
+ "zu": 2
+};
+
+// Utility functions for plural rules methods
+function isIn(n, list) list.indexOf(n) !== -1;
+function isBetween(n, start, end) start <= n && n <= end;
+
+// List of all plural rules methods, that maps an integer to the plural form name to use
+const RULES = {
+ "0": function (n) {
+
+ return "other"
+ },
+ "1": function (n) {
+ if ((isBetween((n % 100), 3, 10)))
+ return "few";
+ if (n == 0)
+ return "zero";
+ if ((isBetween((n % 100), 11, 99)))
+ return "many";
+ if (n == 2)
+ return "two";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "2": function (n) {
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "3": function (n) {
+ if ((isBetween(n, 0, 1)))
+ return "one";
+ return "other"
+ },
+ "4": function (n) {
+ if ((isBetween(n, 0, 2)) && n != 2)
+ return "one";
+ return "other"
+ },
+ "5": function (n) {
+ if (n == 0)
+ return "zero";
+ if ((n % 10) == 1 && (n % 100) != 11)
+ return "one";
+ return "other"
+ },
+ "6": function (n) {
+ if (n == 2)
+ return "two";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "7": function (n) {
+ if ((isBetween(n, 3, 6)))
+ return "few";
+ if ((isBetween(n, 7, 10)))
+ return "many";
+ if (n == 2)
+ return "two";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "8": function (n) {
+ if (n == 0 || n != 1 && (isBetween((n % 100), 1, 19)))
+ return "few";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "9": function (n) {
+ if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19)))
+ return "few";
+ if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19)))
+ return "one";
+ return "other"
+ },
+ "10": function (n) {
+ if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
+ return "few";
+ if ((n % 10) == 0 || (isBetween((n % 10), 5, 9)) || (isBetween((n % 100), 11, 14)))
+ return "many";
+ if ((n % 10) == 1 && (n % 100) != 11)
+ return "one";
+ return "other"
+ },
+ "11": function (n) {
+ if ((isBetween(n, 2, 4)))
+ return "few";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "12": function (n) {
+ if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
+ return "few";
+ if (n != 1 && (isBetween((n % 10), 0, 1)) || (isBetween((n % 10), 5, 9)) || (isBetween((n % 100), 12, 14)))
+ return "many";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "13": function (n) {
+ if ((isBetween((n % 100), 3, 4)))
+ return "few";
+ if ((n % 100) == 2)
+ return "two";
+ if ((n % 100) == 1)
+ return "one";
+ return "other"
+ },
+ "14": function (n) {
+ if (n == 0 || (isBetween((n % 100), 2, 10)))
+ return "few";
+ if ((isBetween((n % 100), 11, 19)))
+ return "many";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "15": function (n) {
+ if ((n % 10) == 1 && n != 11)
+ return "one";
+ return "other"
+ },
+ "16": function (n) {
+ if (n == 3)
+ return "few";
+ if (n == 0)
+ return "zero";
+ if (n == 6)
+ return "many";
+ if (n == 2)
+ return "two";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "17": function (n) {
+ if (n == 0)
+ return "zero";
+ if ((isBetween(n, 0, 2)) && n != 0 && n != 2)
+ return "one";
+ return "other"
+ },
+ "18": function (n) {
+ if ((isBetween(n, 2, 10)))
+ return "few";
+ if ((isBetween(n, 0, 1)))
+ return "one";
+ return "other"
+ },
+ "19": function (n) {
+ if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !(isBetween((n % 100), 10, 19) || isBetween((n % 100), 70, 79) || isBetween((n % 100), 90, 99)))
+ return "few";
+ if ((n % 1000000) == 0 && n != 0)
+ return "many";
+ if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92]))
+ return "two";
+ if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91]))
+ return "one";
+ return "other"
+ },
+ "20": function (n) {
+ if (n == 0)
+ return "zero";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "21": function (n) {
+ if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99)))
+ return "one";
+ return "other"
+ },
+ "22": function (n) {
+ if ((isBetween((n % 10), 1, 2)) || (n % 20) == 0)
+ return "one";
+ return "other"
+ },
+ "23": function (n) {
+ if ((isBetween(n, 3, 10) || isBetween(n, 13, 19)))
+ return "few";
+ if (isIn(n, [2, 12]))
+ return "two";
+ if (isIn(n, [1, 11]))
+ return "one";
+ return "other"
+ },
+};
+
+/**
+ * Return a function that gives the plural form name for a given integer
+ * for the specified `locale`
+ * let fun = getRulesForLocale('en');
+ * fun(1) -> 'one'
+ * fun(0) -> 'other'
+ * fun(1000) -> 'other'
+ */
+exports.getRulesForLocale = function getRulesForLocale(locale) {
+ let index = LOCALES_TO_RULES[locale];
+ if (!(index in RULES))
+ throw new Error('Plural form unknown for locale "' + locale + '"');
+ return RULES[index];
+}
+
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/light-traits.js b/tools/addon-sdk-1.7/packages/api-utils/lib/light-traits.js
new file mode 100644
index 0000000..954d093
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/light-traits.js
@@ -0,0 +1,596 @@
+/* vim:ts=2:sts=2:sw=2:
+ * 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";
+
+// `var` is being used in the module in order to make it reusable in
+// environments in which `let` is not yet supported.
+
+// Shortcut to `Object.prototype.hasOwnProperty.call`.
+// owns(object, name) would be the same as
+// Object.prototype.hasOwnProperty.call(object, name);
+var owns = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
+
+/**
+ * Whether or not given property descriptors are equivalent. They are
+ * equivalent either if both are marked as 'conflict' or 'required' property
+ * or if all the properties of descriptors are equal.
+ * @param {Object} actual
+ * @param {Object} expected
+ */
+function equivalentDescriptors(actual, expected) {
+ return (actual.conflict && expected.conflict) ||
+ (actual.required && expected.required) ||
+ equalDescriptors(actual, expected);
+}
+/**
+ * Whether or not given property descriptors define equal properties.
+ */
+function equalDescriptors(actual, expected) {
+ return actual.get === expected.get &&
+ actual.set === expected.set &&
+ actual.value === expected.value &&
+ !!actual.enumerable === !!expected.enumerable &&
+ !!actual.configurable === !!expected.configurable &&
+ !!actual.writable === !!expected.writable;
+}
+
+// Utilities that throwing exceptions for a properties that are marked
+// as "required" or "conflict" properties.
+function throwConflictPropertyError(name) {
+ throw new Error("Remaining conflicting property: `" + name + "`");
+}
+function throwRequiredPropertyError(name) {
+ throw new Error("Missing required property: `" + name + "`");
+}
+
+/**
+ * Generates custom **required** property descriptor. Descriptor contains
+ * non-standard property `required` that is equal to `true`.
+ * @param {String} name
+ * property name to generate descriptor for.
+ * @returns {Object}
+ * custom property descriptor
+ */
+function RequiredPropertyDescriptor(name) {
+ // Creating function by binding first argument to a property `name` on the
+ // `throwConflictPropertyError` function. Created function is used as a
+ // getter & setter of the created property descriptor. This way we ensure
+ // that we throw exception late (on property access) if object with
+ // `required` property was instantiated using built-in `Object.create`.
+ var accessor = throwRequiredPropertyError.bind(null, name);
+ return { get: accessor, set: accessor, required: true };
+}
+
+/**
+ * Generates custom **conflicting** property descriptor. Descriptor contains
+ * non-standard property `conflict` that is equal to `true`.
+ * @param {String} name
+ * property name to generate descriptor for.
+ * @returns {Object}
+ * custom property descriptor
+ */
+function ConflictPropertyDescriptor(name) {
+ // For details see `RequiredPropertyDescriptor` since idea is same.
+ var accessor = throwConflictPropertyError.bind(null, name);
+ return { get: accessor, set: accessor, conflict: true };
+}
+
+/**
+ * Tests if property is marked as `required` property.
+ */
+function isRequiredProperty(object, name) {
+ return !!object[name].required;
+}
+
+/**
+ * Tests if property is marked as `conflict` property.
+ */
+function isConflictProperty(object, name) {
+ return !!object[name].conflict;
+}
+
+/**
+ * Function tests whether or not method of the `source` object with a given
+ * `name` is inherited from `Object.prototype`.
+ */
+function isBuiltInMethod(name, source) {
+ var target = Object.prototype[name];
+
+ // If methods are equal then we know it's `true`.
+ return target == source ||
+ // If `source` object comes form a different sandbox `==` will evaluate
+ // to `false`, in that case we check if functions names and sources match.
+ (String(target) === String(source) && target.name === source.name);
+}
+
+/**
+ * Function overrides `toString` and `constructor` methods of a given `target`
+ * object with a same-named methods of a given `source` if methods of `target`
+ * object are inherited / copied from `Object.prototype`.
+ * @see create
+ */
+function overrideBuiltInMethods(target, source) {
+ if (isBuiltInMethod("toString", target.toString)) {
+ Object.defineProperty(target, "toString", {
+ value: source.toString,
+ configurable: true,
+ enumerable: false
+ });
+ }
+
+ if (isBuiltInMethod("constructor", target.constructor)) {
+ Object.defineProperty(target, "constructor", {
+ value: source.constructor,
+ configurable: true,
+ enumerable: false
+ });
+ }
+}
+
+/**
+ * Composes new trait with the same own properties as the original trait,
+ * except that all property names appearing in the first argument are replaced
+ * by "required" property descriptors.
+ * @param {String[]} keys
+ * Array of strings property names.
+ * @param {Object} trait
+ * A trait some properties of which should be excluded.
+ * @returns {Object}
+ * @example
+ * var newTrait = exclude(["name", ...], trait)
+ */
+function exclude(names, trait) {
+ var map = {};
+
+ Object.keys(trait).forEach(function(name) {
+
+ // If property is not excluded (the array of names does not contain it),
+ // or it is a "required" property, copy it to the property descriptor `map`
+ // that will be used for creation of resulting trait.
+ if (!~names.indexOf(name) || isRequiredProperty(trait, name))
+ map[name] = { value: trait[name], enumerable: true };
+
+ // For all the `names` in the exclude name array we create required
+ // property descriptors and copy them to the `map`.
+ else
+ map[name] = { value: RequiredPropertyDescriptor(name), enumerable: true };
+ });
+
+ return Object.create(Trait.prototype, map);
+}
+
+/**
+ * Composes new instance of `Trait` with a properties of a given `trait`,
+ * except that all properties whose name is an own property of `renames` will
+ * be renamed to `renames[name]` and a `"required"` property for name will be
+ * added instead.
+ *
+ * For each renamed property, a required property is generated. If
+ * the `renames` map two properties to the same name, a conflict is generated.
+ * If the `renames` map a property to an existing unrenamed property, a
+ * conflict is generated.
+ *
+ * @param {Object} renames
+ * An object whose own properties serve as a mapping from old names to new
+ * names.
+ * @param {Object} trait
+ * A new trait with renamed properties.
+ * @returns {Object}
+ * @example
+ *
+ * // Return trait with `bar` property equal to `trait.foo` and with
+ * // `foo` and `baz` "required" properties.
+ * var renamedTrait = rename({ foo: "bar", baz: null }), trait);
+ *
+ * // t1 and t2 are equivalent traits
+ * var t1 = rename({a: "b"}, t);
+ * var t2 = compose(exclude(["a"], t), { a: { required: true }, b: t[a] });
+ */
+function rename(renames, trait) {
+ var map = {};
+
+ // Loop over all the properties of the given `trait` and copy them to a
+ // property descriptor `map` that will be used for the creation of the
+ // resulting trait. Also, rename properties in the `map` as specified by
+ // `renames`.
+ Object.keys(trait).forEach(function(name) {
+ var alias;
+
+ // If the property is in the `renames` map, and it isn't a "required"
+ // property (which should never need to be aliased because "required"
+ // properties never conflict), then we must try to rename it.
+ if (owns(renames, name) && !isRequiredProperty(trait, name)) {
+ alias = renames[name];
+
+ // If the `map` already has the `alias`, and it isn't a "required"
+ // property, that means the `alias` conflicts with an existing name for a
+ // provided trait (that can happen if >=2 properties are aliased to the
+ // same name). In this case we mark it as a conflicting property.
+ // Otherwise, everything is fine, and we copy property with an `alias`
+ // name.
+ if (owns(map, alias) && !map[alias].value.required) {
+ map[alias] = {
+ value: ConflictPropertyDescriptor(alias),
+ enumerable: true
+ };
+ }
+ else {
+ map[alias] = {
+ value: trait[name],
+ enumerable: true
+ };
+ }
+
+ // Regardless of whether or not the rename was successful, we check to
+ // see if the original `name` exists in the map (such a property
+ // could exist if previous another property was aliased to this `name`).
+ // If it isn't, we mark it as "required", to make sure the caller
+ // provides another value for the old name, which methods of the trait
+ // might continue to reference.
+ if (!owns(map, name)) {
+ map[name] = {
+ value: RequiredPropertyDescriptor(name),
+ enumerable: true
+ };
+ }
+ }
+
+ // Otherwise, either the property isn't in the `renames` map (thus the
+ // caller is not trying to rename it) or it is a "required" property.
+ // Either way, we don't have to alias the property, we just have to copy it
+ // to the map.
+ else {
+ // The property isn't in the map yet, so we copy it over.
+ if (!owns(map, name)) {
+ map[name] = { value: trait[name], enumerable: true };
+ }
+
+ // The property is already in the map (that means another property was
+ // aliased with this `name`, which creates a conflict if the property is
+ // not marked as "required"), so we have to mark it as a "conflict"
+ // property.
+ else if (!isRequiredProperty(trait, name)) {
+ map[name] = {
+ value: ConflictPropertyDescriptor(name),
+ enumerable: true
+ };
+ }
+ }
+ });
+ return Object.create(Trait.prototype, map);
+}
+
+/**
+ * Composes new resolved trait, with all the same properties as the original
+ * `trait`, except that all properties whose name is an own property of
+ * `resolutions` will be renamed to `resolutions[name]`.
+ *
+ * If `resolutions[name]` is `null`, the value is mapped to a property
+ * descriptor that is marked as a "required" property.
+ */
+function resolve(resolutions, trait) {
+ var renames = {};
+ var exclusions = [];
+
+ // Go through each mapping in `resolutions` object and distribute it either
+ // to `renames` or `exclusions`.
+ Object.keys(resolutions).forEach(function(name) {
+
+ // If `resolutions[name]` is a truthy value then it's a mapping old -> new
+ // so we copy it to `renames` map.
+ if (resolutions[name])
+ renames[name] = resolutions[name];
+
+ // Otherwise it's not a mapping but an exclusion instead in which case we
+ // add it to the `exclusions` array.
+ else
+ exclusions.push(name);
+ });
+
+ // First `exclude` **then** `rename` and order is important since
+ // `exclude` and `rename` are not associative.
+ return rename(renames, exclude(exclusions, trait));
+}
+
+/**
+ * Create a Trait (a custom property descriptor map) that represents the given
+ * `object`'s own properties. Property descriptor map is a "custom", because it
+ * inherits from `Trait.prototype` and it's property descriptors may contain
+ * two attributes that is not part of the ES5 specification:
+ *
+ * - "required" (this property must be provided by another trait
+ * before an instance of this trait can be created)
+ * - "conflict" (when the trait is composed with another trait,
+ * a unique value for this property is provided by two or more traits)
+ *
+ * Data properties bound to the `Trait.required` singleton exported by
+ * this module will be marked as "required" properties.
+ *
+ * @param {Object} object
+ * Map of properties to compose trait from.
+ * @returns {Trait}
+ * Trait / Property descriptor map containing all the own properties of the
+ * given argument.
+ */
+function trait(object) {
+ var map;
+ var trait = object;
+
+ if (!(object instanceof Trait)) {
+ // If the passed `object` is not already an instance of `Trait`, we create
+ // a property descriptor `map` containing descriptors for the own properties
+ // of the given `object`. `map` is then used to create a `Trait` instance
+ // after all properties are mapped. Note that we can't create a trait and
+ // then just copy properties into it since that will fail for inherited
+ // read-only properties.
+ map = {};
+
+ // Each own property of the given `object` is mapped to a data property
+ // whose value is a property descriptor.
+ Object.keys(object).forEach(function (name) {
+
+ // If property of an `object` is equal to a `Trait.required`, it means
+ // that it was marked as "required" property, in which case we map it
+ // to "required" property.
+ if (Trait.required ==
+ Object.getOwnPropertyDescriptor(object, name).value) {
+ map[name] = {
+ value: RequiredPropertyDescriptor(name),
+ enumerable: true
+ };
+ }
+ // Otherwise property is mapped to it's property descriptor.
+ else {
+ map[name] = {
+ value: Object.getOwnPropertyDescriptor(object, name),
+ enumerable: true
+ };
+ }
+ });
+
+ trait = Object.create(Trait.prototype, map);
+ }
+ return trait;
+}
+
+/**
+ * Compose a property descriptor map that inherits from `Trait.prototype` and
+ * contains property descriptors for all the own properties of the passed
+ * traits.
+ *
+ * If two or more traits have own properties with the same name, the returned
+ * trait will contain a "conflict" property for that name. Composition is a
+ * commutative and associative operation, and the order of its arguments is
+ * irrelevant.
+ */
+function compose(trait1, trait2/*, ...*/) {
+ // Create a new property descriptor `map` to which all the own properties
+ // of the passed traits are copied. This map will be used to create a `Trait`
+ // instance that will be the result of this composition.
+ var map = {};
+
+ // Properties of each passed trait are copied to the composition.
+ Array.prototype.forEach.call(arguments, function(trait) {
+ // Copying each property of the given trait.
+ Object.keys(trait).forEach(function(name) {
+
+ // If `map` already owns a property with the `name` and it is not
+ // marked "required".
+ if (owns(map, name) && !map[name].value.required) {
+
+ // If the source trait's property with the `name` is marked as
+ // "required", we do nothing, as the requirement was already resolved
+ // by a property in the `map` (because it already contains a
+ // non-required property with that `name`). But if properties are just
+ // different, we have a name clash and we substitute it with a property
+ // that is marked "conflict".
+ if (!isRequiredProperty(trait, name) &&
+ !equivalentDescriptors(map[name].value, trait[name])
+ ) {
+ map[name] = {
+ value: ConflictPropertyDescriptor(name),
+ enumerable: true
+ };
+ }
+ }
+
+ // Otherwise, the `map` does not have an own property with the `name`, or
+ // it is marked "required". Either way, the trait's property is copied to
+ // the map (if the property of the `map` is marked "required", it is going
+ // to be resolved by the property that is being copied).
+ else {
+ map[name] = { value: trait[name], enumerable: true };
+ }
+ });
+ });
+
+ return Object.create(Trait.prototype, map);
+}
+
+/**
+ * `defineProperties` is like `Object.defineProperties`, except that it
+ * ensures that:
+ * - An exception is thrown if any property in a given `properties` map
+ * is marked as "required" property and same named property is not
+ * found in a given `prototype`.
+ * - An exception is thrown if any property in a given `properties` map
+ * is marked as "conflict" property.
+ * @param {Object} object
+ * Object to define properties on.
+ * @param {Object} properties
+ * Properties descriptor map.
+ * @returns {Object}
+ * `object` that was passed as a first argument.
+ */
+function defineProperties(object, properties) {
+
+ // Create a map into which we will copy each verified property from the given
+ // `properties` description map. We use it to verify that none of the
+ // provided properties is marked as a "conflict" property and that all
+ // "required" properties are resolved by a property of an `object`, so we
+ // can throw an exception before mutating object if that isn't the case.
+ var verifiedProperties = {};
+
+ // Coping each property from a given `properties` descriptor map to a
+ // verified map of property descriptors.
+ Object.keys(properties).forEach(function(name) {
+
+ // If property is marked as "required" property and we don't have a same
+ // named property in a given `object` we throw an exception. If `object`
+ // has same named property just skip this property since required property
+ // is was inherited and there for requirement was satisfied.
+ if (isRequiredProperty(properties, name)) {
+ if (!(name in object))
+ throwRequiredPropertyError(name);
+ }
+
+ // If property is marked as "conflict" property we throw an exception.
+ else if (isConflictProperty(properties, name)) {
+ throwConflictPropertyError(name);
+ }
+
+ // If property is not marked neither as "required" nor "conflict" property
+ // we copy it to verified properties map.
+ else {
+ verifiedProperties[name] = properties[name];
+ }
+ });
+
+ // If no exceptions were thrown yet, we know that our verified property
+ // descriptor map has no properties marked as "conflict" or "required",
+ // so we just delegate to the built-in `Object.defineProperties`.
+ return Object.defineProperties(object, verifiedProperties);
+}
+
+/**
+ * `create` is like `Object.create`, except that it ensures that:
+ * - An exception is thrown if any property in a given `properties` map
+ * is marked as "required" property and same named property is not
+ * found in a given `prototype`.
+ * - An exception is thrown if any property in a given `properties` map
+ * is marked as "conflict" property.
+ * @param {Object} prototype
+ * prototype of the composed object
+ * @param {Object} properties
+ * Properties descriptor map.
+ * @returns {Object}
+ * An object that inherits form a given `prototype` and implements all the
+ * properties defined by a given `properties` descriptor map.
+ */
+function create(prototype, properties) {
+
+ // Creating an instance of the given `prototype`.
+ var object = Object.create(prototype);
+
+ // Overriding `toString`, `constructor` methods if they are just inherited
+ // from `Object.prototype` with a same named methods of the `Trait.prototype`
+ // that will have more relevant behavior.
+ overrideBuiltInMethods(object, Trait.prototype);
+
+ // Trying to define given `properties` on the `object`. We use our custom
+ // `defineProperties` function instead of build-in `Object.defineProperties`
+ // that behaves exactly the same, except that it will throw if any
+ // property in the given `properties` descriptor is marked as "required" or
+ // "conflict" property.
+ return defineProperties(object, properties);
+}
+
+/**
+ * Composes new trait. If two or more traits have own properties with the
+ * same name, the new trait will contain a "conflict" property for that name.
+ * "compose" is a commutative and associative operation, and the order of its
+ * arguments is not significant.
+ *
+ * **Note:** Use `Trait.compose` instead of calling this function with more
+ * than one argument. The multiple-argument functionality is strictly for
+ * backward compatibility.
+ *
+ * @params {Object} trait
+ * Takes traits as an arguments
+ * @returns {Object}
+ * New trait containing the combined own properties of all the traits.
+ * @example
+ * var newTrait = compose(trait_1, trait_2, ..., trait_N)
+ */
+function Trait(trait1, trait2) {
+
+ // If the function was called with one argument, the argument should be
+ // an object whose properties are mapped to property descriptors on a new
+ // instance of Trait, so we delegate to the trait function.
+ // If the function was called with more than one argument, those arguments
+ // should be instances of Trait or plain property descriptor maps
+ // whose properties should be mixed into a new instance of Trait,
+ // so we delegate to the compose function.
+
+ return trait2 === undefined ? trait(trait1) : compose.apply(null, arguments);
+}
+
+Object.freeze(Object.defineProperties(Trait.prototype, {
+ toString: {
+ value: function toString() {
+ return "[object " + this.constructor.name + "]";
+ }
+ },
+
+ /**
+ * `create` is like `Object.create`, except that it ensures that:
+ * - An exception is thrown if this trait defines a property that is
+ * marked as required property and same named property is not
+ * found in a given `prototype`.
+ * - An exception is thrown if this trait contains property that is
+ * marked as "conflict" property.
+ * @param {Object}
+ * prototype of the compared object
+ * @returns {Object}
+ * An object with all of the properties described by the trait.
+ */
+ create: {
+ value: function createTrait(prototype) {
+ return create(undefined === prototype ? Object.prototype : prototype,
+ this);
+ },
+ enumerable: true
+ },
+
+ /**
+ * Composes a new resolved trait, with all the same properties as the original
+ * trait, except that all properties whose name is an own property of
+ * `resolutions` will be renamed to the value of `resolutions[name]`. If
+ * `resolutions[name]` is `null`, the property is marked as "required".
+ * @param {Object} resolutions
+ * An object whose own properties serve as a mapping from old names to new
+ * names, or to `null` if the property should be excluded.
+ * @returns {Object}
+ * New trait with the same own properties as the original trait but renamed.
+ */
+ resolve: {
+ value: function resolveTrait(resolutions) {
+ return resolve(resolutions, this);
+ },
+ enumerable: true
+ }
+}));
+
+/**
+ * @see compose
+ */
+Trait.compose = Object.freeze(compose);
+Object.freeze(compose.prototype);
+
+/**
+ * Constant singleton, representing placeholder for required properties.
+ * @type {Object}
+ */
+Trait.required = Object.freeze(Object.create(Object.prototype, {
+ toString: {
+ value: Object.freeze(function toString() {
+ return "<Trait.required>";
+ })
+ }
+}));
+Object.freeze(Trait.required.toString.prototype);
+
+exports.Trait = Object.freeze(Trait);
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/list.js b/tools/addon-sdk-1.7/packages/api-utils/lib/list.js
new file mode 100644
index 0000000..4c6c126
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/list.js
@@ -0,0 +1,114 @@
+/* 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 { Trait } = require('./traits');
+
+/**
+ * @see https://jetpack.mozillalabs.com/sdk/latest/docs/#module/api-utils/list
+ */
+const Iterable = Trait.compose({
+ /**
+ * Hash map of key-values to iterate over.
+ * Note: That this property can be a getter if you need dynamic behavior.
+ * @type {Object}
+ */
+ _keyValueMap: Trait.required,
+ /**
+ * Custom iterator providing `Iterable`s enumeration behavior.
+ * @param {Boolean} onKeys
+ */
+ __iterator__: function __iterator__(onKeys, onKeyValue) {
+ let map = this._keyValueMap;
+ for (let key in map)
+ yield onKeyValue ? [key, map[key]] : onKeys ? key : map[key];
+ }
+});
+exports.Iterable = Iterable;
+
+/**
+ * An ordered collection (also known as a sequence) disallowing duplicate
+ * elements. List is composed out of `Iterable` there for it provides custom
+ * enumeration behavior that is similar to array (enumerates only on the
+ * elements of the list). List is a base trait and is meant to be a part of
+ * composition, since all of it's API is private except length property.
+ */
+const List = Trait.resolve({ toString: null }).compose({
+ _keyValueMap: null,
+ /**
+ * List constructor can take any number of element to populate itself.
+ * @params {Object|String|Number} element
+ * @example
+ * List(1,2,3).length == 3 // true
+ */
+ constructor: function List() {
+ this._keyValueMap = [];
+ for (let i = 0, ii = arguments.length; i < ii; i++)
+ this._add(arguments[i]);
+ },
+ /**
+ * Number of elements in this list.
+ * @type {Number}
+ */
+ get length() this._keyValueMap.length,
+ /**
+ * Returns a string representing this list.
+ * @returns {String}
+ */
+ toString: function toString() 'List(' + this._keyValueMap + ')',
+ /**
+ * Returns `true` if this list contains the specified `element`.
+ * @param {Object|Number|String} element
+ * @returns {Boolean}
+ */
+ _has: function _has(element) 0 <= this._keyValueMap.indexOf(element),
+ /**
+ * Appends the specified `element` to the end of this list, if it doesn't
+ * contains it. Ignores the call if `element` is already contained.
+ * @param {Object|Number|String} element
+ */
+ _add: function _add(element) {
+ let list = this._keyValueMap,
+ index = list.indexOf(element);
+ if (0 > index)
+ list.push(this._public[list.length] = element);
+ },
+ /**
+ * Removes specified `element` from this list, if it contains it.
+ * Ignores the call if `element` is not contained.
+ * @param {Object|Number|String} element
+ */
+ _remove: function _remove(element) {
+ let list = this._keyValueMap,
+ index = list.indexOf(element);
+ if (0 <= index) {
+ delete this._public[list.length - 1];
+ list.splice(index, 1);
+ for (let length = list.length; index < length; index++)
+ this._public[index] = list[index];
+ }
+ },
+ /**
+ * Removes all of the elements from this list.
+ */
+ _clear: function _clear() {
+ for (let i = 0, ii = this._keyValueMap.length; i < ii; i ++)
+ delete this._public[i];
+ this._keyValueMap.splice(0);
+ },
+ /**
+ * Custom iterator providing `List`s enumeration behavior.
+ * We cant reuse `_iterator` that is defined by `Iterable` since it provides
+ * iteration in an arbitrary order.
+ * @see https://developer.mozilla.org/en/JavaScript/Reference/Statements/for...in
+ * @param {Boolean} onKeys
+ */
+ __iterator__: function __iterator__(onKeys, onKeyValue) {
+ let array = this._keyValueMap.slice(0),
+ i = -1;
+ for each(let element in array)
+ yield onKeyValue ? [++i, element] : onKeys ? ++i : element;
+ }
+});
+exports.List = List;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/match-pattern.js b/tools/addon-sdk-1.7/packages/api-utils/lib/match-pattern.js
new file mode 100644
index 0000000..60753d2
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/match-pattern.js
@@ -0,0 +1,103 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { URL } = require("./url");
+
+exports.MatchPattern = MatchPattern;
+
+function MatchPattern(pattern) {
+ if (typeof pattern.test == "function") {
+
+ // For compatibility with -moz-document rules, we require the RegExp's
+ // global, ignoreCase, and multiline flags to be set to false.
+ if (pattern.global) {
+ throw new Error("A RegExp match pattern cannot be set to `global` " +
+ "(i.e. //g).");
+ }
+ if (pattern.ignoreCase) {
+ throw new Error("A RegExp match pattern cannot be set to `ignoreCase` " +
+ "(i.e. //i).");
+ }
+ if (pattern.multiline) {
+ throw new Error("A RegExp match pattern cannot be set to `multiline` " +
+ "(i.e. //m).");
+ }
+
+ this.regexp = pattern;
+ }
+ else {
+ let firstWildcardPosition = pattern.indexOf("*");
+ let lastWildcardPosition = pattern.lastIndexOf("*");
+ if (firstWildcardPosition != lastWildcardPosition)
+ throw new Error("There can be at most one '*' character in a wildcard.");
+
+ if (firstWildcardPosition == 0) {
+ if (pattern.length == 1)
+ this.anyWebPage = true;
+ else if (pattern[1] != ".")
+ throw new Error("Expected a *.<domain name> string, got: " + pattern);
+ else
+ this.domain = pattern.substr(2);
+ }
+ else {
+ if (pattern.indexOf(":") == -1) {
+ throw new Error("When not using *.example.org wildcard, the string " +
+ "supplied is expected to be either an exact URL to " +
+ "match or a URL prefix. The provided string ('" +
+ pattern + "') is unlikely to match any pages.");
+ }
+
+ if (firstWildcardPosition == -1)
+ this.exactURL = pattern;
+ else if (firstWildcardPosition == pattern.length - 1)
+ this.urlPrefix = pattern.substr(0, pattern.length - 1);
+ else {
+ throw new Error("The provided wildcard ('" + pattern + "') has a '*' " +
+ "in an unexpected position. It is expected to be the " +
+ "first or the last character in the wildcard.");
+ }
+ }
+ }
+}
+
+MatchPattern.prototype = {
+
+ test: function MatchPattern_test(urlStr) {
+ try {
+ var url = URL(urlStr);
+ }
+ catch (err) {
+ return false;
+ }
+
+ // Test the URL against a RegExp pattern. For compatibility with
+ // -moz-document rules, we require the RegExp to match the entire URL,
+ // so we not only test for a match, we also make sure the matched string
+ // is the entire URL string.
+ //
+ // Assuming most URLs don't match most match patterns, we call `test` for
+ // speed when determining whether or not the URL matches, then call `exec`
+ // for the small subset that match to make sure the entire URL matches.
+ //
+ if (this.regexp && this.regexp.test(urlStr) &&
+ this.regexp.exec(urlStr)[0] == urlStr)
+ return true;
+
+ if (this.anyWebPage && /^(https?|ftp)$/.test(url.scheme))
+ return true;
+ if (this.exactURL && this.exactURL == urlStr)
+ return true;
+ if (this.domain && url.host &&
+ url.host.slice(-this.domain.length) == this.domain)
+ return true;
+ if (this.urlPrefix && 0 == urlStr.indexOf(this.urlPrefix))
+ return true;
+
+ return false;
+ }
+
+};
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/memory.js b/tools/addon-sdk-1.7/packages/api-utils/lib/memory.js
new file mode 100644
index 0000000..be84969
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/memory.js
@@ -0,0 +1,114 @@
+/* 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,components} = require("chrome");
+var trackedObjects = {};
+
+var Compacter = {
+ INTERVAL: 5000,
+ notify: function(timer) {
+ var newTrackedObjects = {};
+ for (let name in trackedObjects) {
+ var oldBin = trackedObjects[name];
+ var newBin = [];
+ var strongRefs = [];
+ for (var i = 0; i < oldBin.length; i++) {
+ var strongRef = oldBin[i].weakref.get();
+ if (strongRef && strongRefs.indexOf(strongRef) == -1) {
+ strongRefs.push(strongRef);
+ newBin.push(oldBin[i]);
+ }
+ }
+ if (newBin.length)
+ newTrackedObjects[name] = newBin;
+ }
+ trackedObjects = newTrackedObjects;
+ }
+};
+
+var timer = Cc["@mozilla.org/timer;1"]
+ .createInstance(Ci.nsITimer);
+
+timer.initWithCallback(Compacter,
+ Compacter.INTERVAL,
+ Ci.nsITimer.TYPE_REPEATING_SLACK);
+
+var track = exports.track = function track(object, bin, stackFrameNumber) {
+ var frame = components.stack.caller;
+ var weakref = Cu.getWeakReference(object);
+ if (!bin && 'constructor' in object)
+ bin = object.constructor.name;
+ if (bin == "Object")
+ bin = frame.name;
+ if (!bin)
+ bin = "generic";
+ if (!(bin in trackedObjects))
+ trackedObjects[bin] = [];
+
+ if (stackFrameNumber > 0)
+ for (var i = 0; i < stackFrameNumber; i++)
+ frame = frame.caller;
+
+ trackedObjects[bin].push({weakref: weakref,
+ created: new Date(),
+ filename: frame.filename,
+ lineNo: frame.lineNumber,
+ bin: bin});
+};
+
+var getBins = exports.getBins = function getBins() {
+ var names = [];
+ for (let name in trackedObjects)
+ names.push(name);
+ return names;
+};
+
+var getObjects = exports.getObjects = function getObjects(bin) {
+ function getLiveObjectsInBin(bin, array) {
+ for (var i = 0; i < bin.length; i++) {
+ var object = bin[i].weakref.get();
+ if (object)
+ array.push(bin[i]);
+ }
+ }
+
+ var results = [];
+ if (bin) {
+ if (bin in trackedObjects)
+ getLiveObjectsInBin(trackedObjects[bin], results);
+ } else
+ for (let name in trackedObjects)
+ getLiveObjectsInBin(trackedObjects[name], results);
+ return results;
+};
+
+var gc = exports.gc = function gc() {
+ // Components.utils.forceGC() doesn't currently perform
+ // cycle collection, which means that e.g. DOM elements
+ // won't be collected by it. Fortunately, there are
+ // other ways...
+
+ var window = Cc["@mozilla.org/appshell/appShellService;1"]
+ .getService(Ci.nsIAppShellService)
+ .hiddenDOMWindow;
+ var test_utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ test_utils.garbageCollect();
+ Compacter.notify();
+
+ // Not sure why, but sometimes it appears that we don't get
+ // them all with just one CC, so let's do it again.
+ test_utils.garbageCollect();
+};
+
+require("./unload").when(
+ function() {
+ trackedObjects = {};
+ if (timer) {
+ timer.cancel();
+ timer = null;
+ }
+ });
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/message-manager.js b/tools/addon-sdk-1.7/packages/api-utils/lib/message-manager.js
new file mode 100644
index 0000000..235586f
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/message-manager.js
@@ -0,0 +1,203 @@
+/* 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 BAD_LISTENER = "The event listener must be a function.";
+
+const { Cc, Ci, Cu, CC } = require("chrome");
+const { setTimeout } = require("./timer");
+
+const { ns } = require("./namespace");
+
+const { curry, invoke } = require("./functional");
+
+const Sandbox = require("./sandbox");
+
+// JSON.stringify is buggy with cross-sandbox values,
+// it may return "{}" on functions. Use a replacer to match them correctly.
+const jsonFixer = function (k, v) typeof v === "function" ? undefined : v;
+
+/**
+ * Defers invoking the function until the current call stack has cleared.
+ *
+ * @param {Function} fn
+ * The function to defer.
+ *
+ * @returns {Function}
+ * The deferred function
+ */
+const defer = function(fn) function() {
+ setTimeout(invoke, 0, fn, arguments, this)
+};
+
+/**
+ * Adds a message listener.
+ * This listener will receive messages sent from the remote frame.
+ *
+ * @param {String} name
+ * The name of the message for which to add a listener.
+ * @param {Function} listener
+ * The listener function called when the message is received.
+ */
+function addMessageListener(name, listener) {
+ if (typeof listener !== "function")
+ throw new Error(BAD_LISTENER);
+
+ let listeners = frame(this).listeners;
+
+ if (name in listeners) {
+ if (~listeners[name].indexOf(listener))
+ return;
+ } else {
+ listeners[name] = [];
+ }
+
+ listeners[name].push(listener);
+}
+
+/**
+ * Removes a message listener previously added by calling addMessageListener.
+ *
+ * @param {String} name
+ * The name of the message for which to remove a listener.
+ * @param {Function} listener
+ * The listener function has to be removed.
+ */
+function removeMessageListener(name, listener) {
+ if (typeof listener !== "function")
+ throw new Error(BAD_LISTENER);
+
+ let listeners = frame(this).listeners;
+
+ if (!(name in listeners))
+ return;
+
+ let index = listeners[name].indexOf(listener);
+
+ if (~index) {
+ listeners[name].splice(index, 1);
+ }
+}
+
+/**
+ * Sends a message to the listeners.
+ *
+ * @param {Boolean} sync
+ * Indicates if the call is synchronous or asynchronous
+ * @param {String} name
+ * The name of the message to send to the listeners.
+ * @param {Object} [data=null]
+ * A JSON object containing data to be delivered to the listeners.
+ *
+ * @returns {Array|undefined}
+ * An array with the return values of the listeners if `sync` is `true`,
+ * otherwise `undefined`.
+ */
+function sendMessage(sync, name, data) {
+ typeof data === "undefined" && (data = null);
+
+ let listeners = frame(frame(this).receiver).listeners;
+
+ let responses = [];
+
+ let returnValue = sync ? responses : undefined;
+
+ if (!(name in listeners))
+ return returnValue;
+
+ let json = JSON.parse(JSON.stringify(data, jsonFixer));
+
+ for each(let listener in listeners[name]) {
+ try {
+ let response = listener.call(null, {
+ sync : sync,
+ name : name,
+ json : json,
+ target : null
+ });
+
+ if (sync) {
+ if (typeof response === "undefined")
+ responses.push(response);
+ else
+ responses.push(JSON.parse(JSON.stringify(response, jsonFixer)));
+ }
+
+ } catch (e) {
+ console.exception(e);
+ }
+ }
+ return returnValue;
+};
+
+let sendSyncMessage = curry(sendMessage, true);
+let sendAsyncMessage = curry(defer(sendMessage), false);
+
+let frame = ns({receiver: null, listeners: null});
+
+/**
+ * The MessageManager object emulates the Message Manager API, without creating
+ * new processes. It useful in mono process context, like Fennec.
+ *
+ * @see
+ * https://developer.mozilla.org/en/The_message_manager
+ */
+function MessageManager() {
+
+ let sandbox = Sandbox.sandbox(null, { wantXrays : false });
+
+ Object.defineProperties(sandbox, {
+ addMessageListener: {value: addMessageListener.bind(sandbox)},
+
+ removeMessageListener: { value: removeMessageListener.bind(sandbox)},
+
+ sendAsyncMessage: {value: sendAsyncMessage.bind(sandbox)},
+
+ sendSyncMessage: { value: sendSyncMessage.bind(sandbox) }
+ });
+
+ frame(this).receiver = sandbox;
+ frame(sandbox).receiver = this;
+
+ frame(this).listeners = {};
+ frame(sandbox).listeners = {};
+}
+
+MessageManager.prototype = {
+ constructor: MessageManager,
+
+ addMessageListener : addMessageListener,
+
+ removeMessageListener : removeMessageListener,
+
+ sendAsyncMessage : sendAsyncMessage,
+
+ /**
+ * Loads a script into the remote frame.
+ *
+ * @param {String} uri
+ * The URL of the script to load into the frame; this must be an absolute
+ * local URL, but data: URLs are supported.
+ * @param {Boolean} allowDelayedLoad
+ * Not used.
+ */
+ loadFrameScript: function loadFrameScript(uri, async) {
+ if (arguments.length < loadFrameScript.length)
+ throw new Error("Not enough arguments");
+
+ let sandbox = frame(this).receiver;
+
+ try {
+ Sandbox.load(sandbox, uri);
+ } catch (e) {
+ console.exception(e)
+ }
+ }
+}
+
+Object.freeze(MessageManager);
+Object.freeze(MessageManager.prototype);
+
+exports.MessageManager = MessageManager;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/namespace.js b/tools/addon-sdk-1.7/packages/api-utils/lib/namespace.js
new file mode 100644
index 0000000..612910d
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/namespace.js
@@ -0,0 +1,43 @@
+/* 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";
+
+// This is a temporary workaround until bug 673468 is fixed, which causes
+// entries associated with `XPCWrappedNative` wrapped keys to be GC-ed. To
+// workaround that we create a cross reference with an object from the same
+// compartment as `WeakMap` and use that as a key. Cross reference prevents
+// wrapper to be GC-ed until reference to it's value is kept.
+function handle(target) {
+ return target[handle.key] || Object.defineProperty(target, handle.key, {
+ value: { '::': target },
+ enumerable: false,
+ configurable: false,
+ writable: false
+ })[handle.key];
+}
+handle.key = '::ns::' + Math.round(Math.random() * 100000000000000000);
+
+/**
+ * Function creates a new namespace. Optionally `prototype` object may be
+ * passed, in which case namespace objects will inherit from it. Returned value
+ * is a function that can be used to get access to the namespaced properties
+ * for the passed object.
+ * @examples
+ * const ns = Namespace();
+ * ns(myObject).secret = secret;
+ */
+exports.Namespace = function Namespace(prototype) {
+ prototype = prototype || Object.prototype;
+ const map = new WeakMap();
+ return function namespace(target) {
+ let key = handle(target);
+ return map.get(key) ||
+ map.set(key, Object.create(prototype)), map.get(key);
+ };
+};
+
+// `Namespace` is a e4x function in the scope, so we export the function also as
+// `ns` as alias to avoid clashing.
+exports.ns = exports.Namespace;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/observer-service.js b/tools/addon-sdk-1.7/packages/api-utils/lib/observer-service.js
new file mode 100644
index 0000000..04222c7
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/observer-service.js
@@ -0,0 +1,175 @@
+/* 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 } = require("chrome");
+const { Unknown } = require("./xpcom");
+const { when: unload } = require("./unload");
+const memory = require("./memory");
+
+
+/**
+ * A service for adding, removing and notifying observers of notifications.
+ * Wraps the nsIObserverService interface.
+ *
+ * @version 0.2
+ */
+
+var service = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+
+/**
+ * A cache of observers that have been added.
+ *
+ * We use this to remove observers when a caller calls |Observers.remove|.
+ */
+var cache = [];
+
+/**
+ * Topics specifically available to Jetpack-generated extensions.
+ *
+ * Using these predefined consts instead of the platform strings is good:
+ * - allows us to scope topics specifically for Jetpacks
+ * - addons aren't dependent on strings nor behavior of core platform topics
+ * - the core platform topics are not clearly named
+ *
+ */
+exports.topics = {
+ /**
+ * A topic indicating that the application is in a state usable
+ * by add-ons.
+ */
+ get APPLICATION_READY() packaging.jetpackID + "_APPLICATION_READY"
+};
+
+/**
+ * Register the given callback as an observer of the given topic.
+ *
+ * @param topic {String}
+ * the topic to observe
+ *
+ * @param callback {Object}
+ * the callback; an Object that implements nsIObserver or a Function
+ * that gets called when the notification occurs
+ *
+ * @param target {Object} [optional]
+ * the object to use as |this| when calling a Function callback
+ *
+ * @returns the observer
+ */
+var add = exports.add = function add(topic, callback, target) {
+ var observer = Observer.new(topic, callback, target);
+ service.addObserver(observer, topic, true);
+ cache.push(observer);
+
+ return observer;
+};
+
+/**
+ * Unregister the given callback as an observer of the given topic.
+ *
+ * @param topic {String}
+ * the topic being observed
+ *
+ * @param callback {Object}
+ * the callback doing the observing
+ *
+ * @param target {Object} [optional]
+ * the object being used as |this| when calling a Function callback
+ */
+var remove = exports.remove = function remove(topic, callback, target) {
+ // This seems fairly inefficient, but I'm not sure how much better
+ // we can make it. We could index by topic, but we can't index by callback
+ // or target, as far as I know, since the keys to JavaScript hashes
+ // (a.k.a. objects) can apparently only be primitive values.
+ let observers = cache.filter(function(v) {
+ return (v.topic == topic &&
+ v.callback == callback &&
+ v.target == target);
+ });
+
+ if (observers.length) {
+ service.removeObserver(observers[0], topic);
+ cache.splice(cache.indexOf(observers[0]), 1);
+ }
+};
+
+/**
+ * Notify observers about something.
+ *
+ * @param topic {String}
+ * the topic to notify observers about
+ *
+ * @param subject {Object} [optional]
+ * some information about the topic; can be any JS object or primitive
+ *
+ * @param data {String} [optional] [deprecated]
+ * some more information about the topic; deprecated as the subject
+ * is sufficient to pass all needed information to the JS observers
+ * that this module targets; if you have multiple values to pass to
+ * the observer, wrap them in an object and pass them via the subject
+ * parameter (i.e.: { foo: 1, bar: "some string", baz: myObject })
+ */
+var notify = exports.notify = function notify(topic, subject, data) {
+ subject = (typeof subject == "undefined") ? null : Subject.new(subject);
+ data = (typeof data == "undefined") ? null : data;
+ service.notifyObservers(subject, topic, data);
+};
+
+const Observer = Unknown.extend({
+ initialize: function initialize(topic, callback, target) {
+ memory.track(this);
+ this.topic = topic;
+ this.callback = callback;
+ this.target = target;
+ },
+ interfaces: [ 'nsIObserver', 'nsISupportsWeakReference' ],
+ observe: function(subject, topic, data) {
+ // Extract the wrapped object for subjects that are one of our
+ // wrappers around a JS object. This way we support both wrapped
+ // subjects created using this module and those that are real
+ // XPCOM components.
+ if (subject && typeof subject == "object" &&
+ ("wrappedJSObject" in subject) &&
+ ("observersModuleSubjectWrapper" in subject.wrappedJSObject))
+ subject = subject.wrappedJSObject.object;
+
+ try {
+ if (typeof this.callback == "function") {
+ if (this.target)
+ this.callback.call(this.target, subject, data);
+ else
+ this.callback(subject, data);
+ } else // typeof this.callback == "object" (nsIObserver)
+ this.callback.observe(subject, topic, data);
+ } catch (e) {
+ console.exception(e);
+ }
+ }
+});
+
+const Subject = Unknown.extend({
+ initialize: function initialize(object) {
+ // Double-wrap the object and set a property identifying the
+ // wrappedJSObject as one of our wrappers to distinguish between
+ // subjects that are one of our wrappers (which we should unwrap
+ // when notifying our observers) and those that are real JS XPCOM
+ // components (which we should pass through unaltered).
+ this.wrappedJSObject = {
+ observersModuleSubjectWrapper: true,
+ object: object
+ };
+ },
+ getHelperForLanguage: function() {},
+ getInterfaces: function() {}
+});
+
+unload(function() {
+ // Make a copy of cache first, since cache will be changing as we
+ // iterate through it.
+ cache.slice().forEach(function({ topic, callback, target }) {
+ remove(topic, callback, target);
+ });
+})
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/passwords/utils.js b/tools/addon-sdk-1.7/packages/api-utils/lib/passwords/utils.js
new file mode 100644
index 0000000..5dc6098
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/passwords/utils.js
@@ -0,0 +1,101 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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, components: { Constructor: CConstructor } } = require("chrome");
+const { uri: ADDON_URI } = require("self");
+const loginManager = Cc["@mozilla.org/login-manager;1"].
+ getService(Ci.nsILoginManager);
+const { URL: parseURL } = require("../url");
+const LoginInfo = CConstructor("@mozilla.org/login-manager/loginInfo;1",
+ "nsILoginInfo", "init");
+
+function filterMatchingLogins(loginInfo)
+ Object.keys(this).every(function(key) loginInfo[key] === this[key], this);
+
+/**
+ * Removes `user`, `password` and `path` fields from the given `url` if it's
+ * 'http', 'https' or 'ftp'. All other URLs are returned unchanged.
+ * @example
+ * http://user:pass@www.site.com/foo/?bar=baz#bang -> http://www.site.com
+ */
+function normalizeURL(url) {
+ let { scheme, host, port } = parseURL(url);
+ // We normalize URL only if it's `http`, `https` or `ftp`. All other types of
+ // URLs (`resource`, `chrome`, etc..) should not be normalized as they are
+ // used with add-on associated credentials path.
+ return scheme === "http" || scheme === "https" || scheme === "ftp" ?
+ scheme + "://" + (host || "") + (port ? ":" + port : "") :
+ url
+}
+
+function Login(options) {
+ let login = Object.create(Login.prototype);
+ Object.keys(options || {}).forEach(function(key) {
+ if (key === 'url')
+ login.hostname = normalizeURL(options.url);
+ else if (key === 'formSubmitURL')
+ login.formSubmitURL = options.formSubmitURL ?
+ normalizeURL(options.formSubmitURL) : null;
+ else if (key === 'realm')
+ login.httpRealm = options.realm;
+ else
+ login[key] = options[key];
+ });
+
+ return login;
+}
+Login.prototype.toJSON = function toJSON() {
+ return {
+ url: this.hostname || ADDON_URI,
+ realm: this.httpRealm || null,
+ formSubmitURL: this.formSubmitURL || null,
+ username: this.username || null,
+ password: this.password || null,
+ usernameField: this.usernameField || '',
+ passwordField: this.passwordField || '',
+ }
+};
+Login.prototype.toLoginInfo = function toLoginInfo() {
+ let { url, realm, formSubmitURL, username, password, usernameField,
+ passwordField } = this.toJSON();
+
+ return new LoginInfo(url, formSubmitURL, realm, username, password,
+ usernameField, passwordField);
+};
+
+function loginToJSON(value) Login(value).toJSON()
+
+/**
+ * Returns array of `nsILoginInfo` objects that are stored in the login manager
+ * and have all the properties with matching values as a given `options` object.
+ * @param {Object} options
+ * @returns {nsILoginInfo[]}
+ */
+exports.search = function search(options) {
+ return loginManager.getAllLogins()
+ .filter(filterMatchingLogins, Login(options))
+ .map(loginToJSON);
+};
+
+/**
+ * Stores login info created from the given `options` to the applications
+ * built-in login management system.
+ * @param {Object} options.
+ */
+exports.store = function store(options) {
+ loginManager.addLogin(Login(options).toLoginInfo());
+};
+
+/**
+ * Removes login info from the applications built-in login management system.
+ * _Please note: When removing a login info the specified properties must
+ * exactly match to the one that is already stored or exception will be thrown._
+ * @param {Object} options.
+ */
+exports.remove = function remove(options) {
+ loginManager.removeLogin(Login(options).toLoginInfo());
+};
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/plain-text-console.js b/tools/addon-sdk-1.7/packages/api-utils/lib/plain-text-console.js
new file mode 100644
index 0000000..d76fddb
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/plain-text-console.js
@@ -0,0 +1,82 @@
+/* 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} = require("chrome");
+
+function stringify(arg) {
+ try {
+ return String(arg);
+ }
+ catch(ex) {
+ return "<toString() error>";
+ }
+}
+
+function stringifyArgs(args) {
+ return Array.map(args, stringify).join(" ");
+}
+
+function message(print, level, args) {
+ print(level + ": " + stringifyArgs(args) + "\n", level);
+}
+
+var Console = exports.PlainTextConsole = function PlainTextConsole(print) {
+ if (!print)
+ print = dump;
+ if (print === dump) {
+ // If we're just using dump(), auto-enable preferences so
+ // that the developer actually sees the console output.
+ var prefs = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ prefs.setBoolPref("browser.dom.window.dump.enabled", true);
+ }
+ this.print = print;
+
+ // Binding all the public methods to an instance so that they can be used
+ // as callback / listener functions straightaway.
+ this.log = this.log.bind(this);
+ this.info = this.info.bind(this);
+ this.warn = this.warn.bind(this);
+ this.error = this.error.bind(this);
+ this.debug = this.debug.bind(this);
+ this.exception = this.exception.bind(this);
+ this.trace = this.trace.bind(this);
+};
+
+Console.prototype = {
+ log: function log() {
+ message(this.print, "info", arguments);
+ },
+
+ info: function info() {
+ message(this.print, "info", arguments);
+ },
+
+ warn: function warn() {
+ message(this.print, "warning", arguments);
+ },
+
+ error: function error() {
+ message(this.print, "error", arguments);
+ },
+
+ debug: function debug() {
+ message(this.print, "debug", arguments);
+ },
+
+ exception: function exception(e) {
+ var fullString = ("An exception occurred.\n" +
+ require("./traceback").format(e) + "\n" + e);
+ this.error(fullString);
+ },
+
+ trace: function trace() {
+ var traceback = require("./traceback");
+ var stack = traceback.get();
+ stack.splice(-1, 1);
+ message(this.print, "info", [traceback.format(stack)]);
+ }
+};
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/preferences-service.js b/tools/addon-sdk-1.7/packages/api-utils/lib/preferences-service.js
new file mode 100644
index 0000000..c5ffb2a
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/preferences-service.js
@@ -0,0 +1,124 @@
+/* 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";
+
+// The minimum and maximum integers that can be set as preferences.
+// The range of valid values is narrower than the range of valid JS values
+// because the native preferences code treats integers as NSPR PRInt32s,
+// which are 32-bit signed integers on all platforms.
+const MAX_INT = 0x7FFFFFFF;
+const MIN_INT = -0x80000000;
+
+const {Cc,Ci,Cr} = require("chrome");
+
+const prefService = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+const prefSvc = prefService.getBranch(null);
+const defaultBranch = prefService.getDefaultBranch(null);
+
+var get = exports.get = function get(name, defaultValue) {
+ switch (prefSvc.getPrefType(name)) {
+ case Ci.nsIPrefBranch.PREF_STRING:
+ return prefSvc.getComplexValue(name, Ci.nsISupportsString).data;
+
+ case Ci.nsIPrefBranch.PREF_INT:
+ return prefSvc.getIntPref(name);
+
+ case Ci.nsIPrefBranch.PREF_BOOL:
+ return prefSvc.getBoolPref(name);
+
+ case Ci.nsIPrefBranch.PREF_INVALID:
+ return defaultValue;
+
+ default:
+ // This should never happen.
+ throw new Error("Error getting pref " + name +
+ "; its value's type is " +
+ prefSvc.getPrefType(name) +
+ ", which I don't know " +
+ "how to handle.");
+ }
+};
+
+var set = exports.set = function set(name, value) {
+ var prefType;
+ if (typeof value != "undefined" && value != null)
+ prefType = value.constructor.name;
+
+ switch (prefType) {
+ case "String":
+ {
+ var string = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ string.data = value;
+ prefSvc.setComplexValue(name, Ci.nsISupportsString, string);
+ }
+ break;
+
+ case "Number":
+ // We throw if the number is outside the range or not an integer, since
+ // the result will not be what the consumer wanted to store.
+ if (value > MAX_INT || value < MIN_INT)
+ throw new Error("you cannot set the " + name +
+ " pref to the number " + value +
+ ", as number pref values must be in the signed " +
+ "32-bit integer range -(2^31) to 2^31-1. " +
+ "To store numbers outside that range, store " +
+ "them as strings.");
+ if (value % 1 != 0)
+ throw new Error("cannot store non-integer number: " + value);
+ prefSvc.setIntPref(name, value);
+ break;
+
+ case "Boolean":
+ prefSvc.setBoolPref(name, value);
+ break;
+
+ default:
+ throw new Error("can't set pref " + name + " to value '" + value +
+ "'; it isn't a string, integer, or boolean");
+ }
+};
+
+var has = exports.has = function has(name) {
+ return (prefSvc.getPrefType(name) != Ci.nsIPrefBranch.PREF_INVALID);
+};
+
+var isSet = exports.isSet = function isSet(name) {
+ return (has(name) && prefSvc.prefHasUserValue(name));
+};
+
+var reset = exports.reset = function reset(name) {
+ try {
+ prefSvc.clearUserPref(name);
+ } catch (e if e.result == Cr.NS_ERROR_UNEXPECTED) {
+ // The pref service throws NS_ERROR_UNEXPECTED when the caller tries
+ // to reset a pref that doesn't exist or is already set to its default
+ // value. This interface fails silently in those cases, so callers
+ // can unconditionally reset a pref without having to check if it needs
+ // resetting first or trap exceptions after the fact. It passes through
+ // other exceptions, however, so callers know about them, since we don't
+ // know what other exceptions might be thrown and what they might mean.
+ }
+};
+
+exports.getLocalized = function getLocalized(name, defaultValue) {
+ let value = null;
+ try {
+ value = prefSvc.getComplexValue(name, Ci.nsIPrefLocalizedString).data;
+ }
+ finally {
+ return value || defaultValue;
+ }
+}
+
+exports.setLocalized = function setLocalized(name, value) {
+ // We can't use `prefs.set` here as we have to use `getDefaultBranch`
+ // (instead of `getBranch`) in order to have `mIsDefault` set to true, here:
+ // http://mxr.mozilla.org/mozilla-central/source/modules/libpref/src/nsPrefBranch.cpp#233
+ // Otherwise, we do not enter into this expected condition:
+ // http://mxr.mozilla.org/mozilla-central/source/modules/libpref/src/nsPrefBranch.cpp#244
+ defaultBranch.setCharPref(name, value);
+}
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/process.js b/tools/addon-sdk-1.7/packages/api-utils/lib/process.js
new file mode 100644
index 0000000..68ef2f9
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/process.js
@@ -0,0 +1,76 @@
+/* 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 } = require("chrome");
+const { createRemoteBrowser } = require("api-utils/window-utils");
+const { channel } = require("./channel");
+const packaging = require('@packaging');
+const { when } = require('./unload');
+const { MessageManager } = require('./message-manager');
+
+const addonService = '@mozilla.org/addon/service;1' in Cc ?
+ Cc['@mozilla.org/addon/service;1'].getService(Ci.nsIAddonService) : null
+
+const ENABLE_E10S = packaging.enable_e10s;
+
+const isFennec = require("./xul-app").is("Fennec");
+
+function loadScript(target, uri, sync) {
+ return 'loadScript' in target ? target.loadScript(uri, sync)
+ : target.loadFrameScript(uri, sync)
+}
+
+function process(target, id, path, scope) {
+ // Please note that even though `loadScript`, is executed before channel is
+ // returned, users still are able to subscribe for messages before any message
+ // will be sent. That's because `loadScript` queues script execution on the
+ // other process, which means they will execute async (on the next turn of
+ // event loop), while the channel for messages is returned immediately (in
+ // the same turn of event loop).
+
+ let load = loadScript.bind(null, target);
+
+ load(packaging.uriPrefix + packaging.loader, false);
+ load('data:,' + encodeURIComponent(
+ 'let loader = Loader.new(' + JSON.stringify(packaging) + ');\n' +
+ 'loader.main("' + id + '", "' + path + '");'), false);
+
+ when(function (reason) {
+ // Please note that it's important to unload remote loader
+ // synchronously (using synchronous frame script), to make sure that we
+ // don't stop during unload.
+ // Bug 724433: Take care to nullify all globals set by `cuddlefish.js`
+ // otherwise, we will leak any still defined global.
+ // `dump` is set in Loader.new method, `dump = globals.dump;`
+ load('data:,loader.unload("' + reason + '");' +
+ 'loader = null; Loader = null; dump = null;', true);
+ });
+
+ return {
+ channel: channel.bind(null, scope, target),
+ loadScript: load
+ };
+}
+
+exports.spawn = function spawn(id, path) {
+ return function promise(deliver) {
+ // If `nsIAddonService` is available we use it to create an add-on process,
+ // otherwise we fallback to the remote browser's message manager.
+ if (ENABLE_E10S && addonService) {
+ console.log('!!!!!!!!!!!!!!!!!!!! Using addon process !!!!!!!!!!!!!!!!!!');
+ deliver(process(addonService.createAddon(), id, path));
+ } else if (isFennec) {
+ deliver(process(new MessageManager(), id, path));
+ } else {
+ createRemoteBrowser(ENABLE_E10S)(function(browser) {
+ let messageManager = browser.QueryInterface(Ci.nsIFrameLoaderOwner).
+ frameLoader.messageManager
+ let window = browser.ownerDocument.defaultView;
+ deliver(process(messageManager, id, path, window));
+ });
+ }
+ };
+};
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/promise.js b/tools/addon-sdk-1.7/packages/api-utils/lib/promise.js
new file mode 100644
index 0000000..7c8d7a5
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/promise.js
@@ -0,0 +1,219 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/*jshint asi: true undef: true es5: true node: true devel: true
+ forin: true latedef: false supernew: true */
+/*global define: true */
+!function(factory) {
+ if (typeof(define) === 'function') { // RequireJS
+ define(factory)
+ } else if (typeof(exports) === 'object') { // CommonJS
+ factory(require, exports)
+ } else if (~String(this).indexOf('BackstagePass')) { // JSM
+ factory(undefined, this)
+ this.EXPORTED_SYMBOLS = Object.keys(this)
+ } else {
+ factory(undefined, (this.promise = {}))
+ }
+}.call(this, function(require, exports) {
+
+'use strict';
+
+function resolution(value) {
+ /**
+ Returns non-standard compliant (`then` does not returns a promise) promise
+ that resolves to a given `value`. Used just internally only.
+ **/
+ return { then: function then(resolve) { resolve(value) } }
+}
+
+function rejection(reason) {
+ /**
+ Returns non-standard compliant promise (`then` does not returns a promise)
+ that rejects with a given `reason`. This is used internally only.
+ **/
+ return { then: function then(resolve, reject) { reject(reason) } }
+}
+
+function attempt(f) {
+ /**
+ Returns wrapper function that delegates to `f`. If `f` throws then captures
+ error and returns promise that rejects with a thrown error. Otherwise returns
+ return value. (Internal utility)
+ **/
+ return function effort(options) {
+ try { return f(options) }
+ catch(error) { return rejection(error) }
+ }
+}
+
+function isPromise(value) {
+ /**
+ Returns true if given `value` is promise. Value is assumed to be promise if
+ it implements `then` method.
+ **/
+ return value && typeof(value.then) === 'function'
+}
+
+function defer(prototype) {
+ /**
+ Returns object containing following properties:
+ - `promise` Eventual value representation implementing CommonJS [Promises/A]
+ (http://wiki.commonjs.org/wiki/Promises/A) API.
+ - `resolve` Single shot function that resolves returned `promise` with a given
+ `value` argument.
+ - `reject` Single shot function that rejects returned `promise` with a given
+ `reason` argument.
+
+ Given `prototype` argument is used as a prototype of the returned `promise`
+ allowing one to implement additional API. If prototype is not passed then
+ it falls back to `Object.prototype`.
+
+ ## Examples
+
+ // Simple usage.
+ var deferred = defer()
+ deferred.promise.then(console.log, console.error)
+ deferred.resolve(value)
+
+ // Advanced usage
+ var prototype = {
+ get: function get(name) {
+ return this.then(function(value) {
+ return value[name];
+ })
+ }
+ }
+
+ var foo = defer(prototype)
+ deferred.promise.get('name').then(console.log)
+ deferred.resolve({ name: 'Foo' })
+ //=> 'Foo'
+ */
+ var pending = [], result
+ prototype = (prototype || prototype === null) ? prototype : Object.prototype
+
+ // Create an object implementing promise API.
+ var promise = Object.create(prototype, {
+ then: { value: function then(resolve, reject) {
+ // create a new deferred using a same `prototype`.
+ var deferred = defer(prototype)
+ // If `resolve / reject` callbacks are not provided.
+ resolve = resolve ? attempt(resolve) : resolution
+ reject = reject ? attempt(reject) : rejection
+
+ // Create a listeners for a enclosed promise resolution / rejection that
+ // delegate to an actual callbacks and resolve / reject returned promise.
+ function resolved(value) { deferred.resolve(resolve(value)) }
+ function rejected(reason) { deferred.resolve(reject(reason)) }
+
+ // If promise is pending register listeners. Otherwise forward them to
+ // resulting resolution.
+ if (pending) pending.push([ resolved, rejected ])
+ else result.then(resolved, rejected)
+
+ return deferred.promise
+ }}
+ })
+
+ var deferred = {
+ promise: promise,
+ resolve: function resolve(value) {
+ /**
+ Resolves associated `promise` to a given `value`, unless it's already
+ resolved or rejected.
+ **/
+ if (pending) {
+ // store resolution `value` as a promise (`value` itself may be a
+ // promise), so that all subsequent listeners can be forwarded to it,
+ // which either resolves immediately or forwards if `value` is
+ // a promise.
+ result = isPromise(value) ? value : resolution(value)
+ // forward all pending observers.
+ while (pending.length) result.then.apply(result, pending.shift())
+ // mark promise as resolved.
+ pending = null
+ }
+ },
+ reject: function reject(reason) {
+ /**
+ Rejects associated `promise` with a given `reason`, unless it's already
+ resolved / rejected.
+ **/
+ deferred.resolve(rejection(reason))
+ }
+ }
+
+ return deferred
+}
+exports.defer = defer
+
+function resolve(value, prototype) {
+ /**
+ Returns a promise resolved to a given `value`. Optionally second `prototype`
+ arguments my be provided to be used as a prototype for a returned promise.
+ **/
+ var deferred = defer(prototype)
+ deferred.resolve(value)
+ return deferred.promise
+}
+exports.resolve = resolve
+
+function reject(reason, prototype) {
+ /**
+ Returns a promise that is rejected with a given `reason`. Optionally second
+ `prototype` arguments my be provided to be used as a prototype for a returned
+ promise.
+ **/
+ var deferred = defer(prototype)
+ deferred.reject(reason)
+ return deferred.promise
+}
+exports.reject = reject
+
+var promised = (function() {
+ // Note: Define shortcuts and utility functions here in order to avoid
+ // slower property accesses and unnecessary closure creations on each
+ // call of this popular function.
+
+ var call = Function.call
+ var concat = Array.prototype.concat
+
+ // Utility function that does following:
+ // execute([ f, self, args...]) => f.apply(self, args)
+ function execute(args) { return call.apply(call, args) }
+
+ // Utility function that takes promise of `a` array and maybe promise `b`
+ // as arguments and returns promise for `a.concat(b)`.
+ function promisedConcat(promises, unknown) {
+ return promises.then(function(values) {
+ return resolve(unknown).then(function(value) {
+ return values.concat(value)
+ })
+ })
+ }
+
+ return function promised(f, prototype) {
+ /**
+ Returns a wrapped `f`, which when called returns a promise that resolves to
+ `f(...)` passing all the given arguments to it, which by the way may be
+ promises. Optionally second `prototype` argument may be provided to be used
+ a prototype for a returned promise.
+
+ ## Example
+
+ var promise = promised(Array)(1, promise(2), promise(3))
+ promise.then(console.log) // => [ 1, 2, 3 ]
+ **/
+
+ return function promised() {
+ // create array of [ f, this, args... ]
+ return concat.apply([ f, this ], arguments).
+ // reduce it via `promisedConcat` to get promised array of fulfillments
+ reduce(promisedConcat, resolve([], prototype)).
+ // finally map that to promise of `f.apply(this, args...)`
+ then(execute)
+ }
+ }
+})()
+exports.promised = promised
+
+})
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/querystring.js b/tools/addon-sdk-1.7/packages/api-utils/lib/querystring.js
new file mode 100644
index 0000000..9e29515
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/querystring.js
@@ -0,0 +1,117 @@
+/* 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 unescape = decodeURIComponent;
+exports.unescape = unescape;
+
+// encodes a string safely for application/x-www-form-urlencoded
+// adheres to RFC 3986
+// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURIComponent
+function escape(query) {
+ return encodeURIComponent(query).
+ replace(/%20/g, '+').
+ replace(/!/g, '%21').
+ replace(/'/g, '%27').
+ replace(/\(/g, '%28').
+ replace(/\)/g, '%29').
+ replace(/\*/g, '%2A');
+}
+exports.escape = escape;
+
+// Converts an object of unordered key-vals to a string that can be passed
+// as part of a request
+function stringify(options, separator, assigner) {
+ separator = separator || '&';
+ assigner = assigner || '=';
+ // Explicitly return null if we have null, and empty string, or empty object.
+ if (!options)
+ return '';
+
+ // If content is already a string, just return it as is.
+ if (typeof(options) == 'string')
+ return options;
+
+ // At this point we have a k:v object. Iterate over it and encode each value.
+ // Arrays and nested objects will get encoded as needed. For example...
+ //
+ // { foo: [1, 2, { omg: 'bbq', 'all your base!': 'are belong to us' }], bar: 'baz' }
+ //
+ // will be encoded as
+ //
+ // foo[0]=1&foo[1]=2&foo[2][omg]=bbq&foo[2][all+your+base!]=are+belong+to+us&bar=baz
+ //
+ // Keys (including '[' and ']') and values will be encoded with
+ // `escape` before returning.
+ //
+ // Execution was inspired by jQuery, but some details have changed and numeric
+ // array keys are included (whereas they are not in jQuery).
+
+ let encodedContent = [];
+ function add(key, val) {
+ encodedContent.push(escape(key) + assigner + escape(val));
+ }
+
+ function make(key, value) {
+ if (value && typeof(value) === 'object')
+ Object.keys(value).forEach(function(name) {
+ make(key + '[' + name + ']', value[name]);
+ });
+ else
+ add(key, value);
+ }
+
+ Object.keys(options).forEach(function(name) { make(name, options[name]); });
+ return encodedContent.join(separator);
+
+ //XXXzpao In theory, we can just use a FormData object on 1.9.3, but I had
+ // trouble getting that working. It would also be nice to stay
+ // backwards-compat as long as possible. Keeping this in for now...
+ // let formData = Cc['@mozilla.org/files/formdata;1'].
+ // createInstance(Ci.nsIDOMFormData);
+ // for ([k, v] in Iterator(content)) {
+ // formData.append(k, v);
+ // }
+ // return formData;
+}
+exports.stringify = stringify;
+
+// Exporting aliases that nodejs implements just for the sake of
+// interoperability.
+exports.encode = stringify;
+exports.serialize = stringify;
+
+// Note: That `stringify` and `parse` aren't bijective as we use `stringify`
+// as it was implement in request module, but implement `parse` to match nodejs
+// behavior.
+// TODO: Make `stringify` implement API as in nodejs and figure out backwards
+// compatibility.
+function parse(query, separator, assigner) {
+ separator = separator || '&';
+ assigner = assigner || '=';
+ let result = {};
+
+ if (typeof query !== 'string' || query.length === 0)
+ return result;
+
+ query.split(separator).forEach(function(chunk) {
+ let pair = chunk.split(assigner);
+ let key = unescape(pair[0]);
+ let value = unescape(pair.slice(1).join(assigner));
+
+ if (!(key in result))
+ result[key] = value;
+ else if (Array.isArray(result[key]))
+ result[key].push(value);
+ else
+ result[key] = [result[key], value];
+ });
+
+ return result;
+};
+exports.parse = parse;
+// Exporting aliases that nodejs implements just for the sake of
+// interoperability.
+exports.decode = parse;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/runtime.js b/tools/addon-sdk-1.7/packages/api-utils/lib/runtime.js
new file mode 100644
index 0000000..845d146
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/runtime.js
@@ -0,0 +1,15 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 } = require("chrome");
+const runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
+
+exports.inSafeMode = runtime.inSafeMode;
+exports.OS = runtime.OS;
+exports.processType = runtime.processType;
+exports.widgetToolkit = runtime.widgetToolkit;
+exports.XPCOMABI = runtime.XPCOMABI;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/sandbox.js b/tools/addon-sdk-1.7/packages/api-utils/lib/sandbox.js
new file mode 100644
index 0000000..6130637
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/sandbox.js
@@ -0,0 +1,46 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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, CC, Cu } = require('chrome');
+const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
+const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1'].
+ getService(Ci.mozIJSSubScriptLoader);
+
+/**
+ * Make a new sandbox that inherits given `source`'s principals. Source can be
+ * URI string, DOMWindow or `null` for system principals.
+ */
+function sandbox(target, options) {
+ return Cu.Sandbox(target || systemPrincipal, options || {});
+}
+exports.sandbox = sandbox
+
+/**
+ * Evaluates given `source` in a given `sandbox` and returns result.
+ */
+function evaluate(sandbox, code, uri, line, version) {
+ return Cu.evalInSandbox(code, sandbox, version || '1.8', uri || '', line || 1);
+}
+exports.evaluate = evaluate;
+
+/**
+ * Evaluates code under the given `uri` in the given `sandbox`.
+ *
+ * @param {String} uri
+ * The URL pointing to the script to load.
+ * It must be a local chrome:, resource:, file: or data: URL.
+ */
+function load(sandbox, uri) {
+ if (uri.indexOf('data:') === 0) {
+ let source = uri.substr(uri.indexOf(',') + 1);
+
+ return evaluate(sandbox, decodeURIComponent(source), '1.8', uri, 0);
+ } else {
+ return scriptLoader.loadSubScript(uri, sandbox, 'UTF-8');
+ }
+}
+exports.load = load; \ No newline at end of file
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/self!.js b/tools/addon-sdk-1.7/packages/api-utils/lib/self!.js
new file mode 100644
index 0000000..fd69f9a
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/self!.js
@@ -0,0 +1,48 @@
+/* vim:st=2:sts=2:sw=2:
+ * 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 } = require('chrome');
+const { jetpackID, name, manifest, metadata, uriPrefix } = require('@packaging');
+
+const XMLHttpRequest = CC('@mozilla.org/xmlextras/xmlhttprequest;1',
+ 'nsIXMLHttpRequest');
+
+// Utility function that synchronously reads local resource from the given
+// `uri` and returns content string.
+function readURI(uri) {
+ let request = XMLHttpRequest();
+ request.open('GET', uri, false);
+ request.overrideMimeType('text/plain');
+ request.send();
+ return request.responseText;
+}
+
+// Some XPCOM APIs require valid URIs as an argument for certain operations (see
+// `nsILoginManager` for example). This property represents add-on associated
+// unique URI string that can be used for that.
+const uri = 'addon:' + jetpackID
+
+function url(root, path) root + (path || "")
+function read(root, path) readURI(url(root, path))
+
+exports.create = function create(base) {
+ let moduleData = manifest[base] && manifest[base].requirements['self'];
+ let root = uriPrefix + moduleData.dataURIPrefix;
+ return Object.freeze({
+ id: 'self',
+ exports: Object.freeze({
+ id: jetpackID,
+ uri: uri,
+ name: name,
+ version: metadata[name].version,
+ data: {
+ url: url.bind(null, root),
+ load: read.bind(null, root)
+ }
+ })
+ });
+};
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/system.js b/tools/addon-sdk-1.7/packages/api-utils/lib/system.js
new file mode 100644
index 0000000..3986645
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/system.js
@@ -0,0 +1,120 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* 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, CC } = require('chrome');
+const options = require('@packaging');
+const file = require('./file');
+const runtime = require("./runtime.js");
+
+const appStartup = Cc['@mozilla.org/toolkit/app-startup;1'].
+ getService(Ci.nsIAppStartup);
+const appInfo = Cc["@mozilla.org/xre/app-info;1"].
+ getService(Ci.nsIXULAppInfo);
+const directoryService = Cc['@mozilla.org/file/directory_service;1'].
+ getService(Ci.nsIProperties);
+
+
+const { eAttemptQuit: E_ATTEMPT, eForceQuit: E_FORCE } = appStartup;
+
+/**
+ * Parsed JSON object that was passed via `cfx --static-args "{ foo: 'bar' }"`
+ */
+exports.staticArgs = options.staticArgs;
+
+/**
+ * Environment variables. Environment variables are non-enumerable properties
+ * of this object (key is name and value is value).
+ */
+exports.env = require('./environment').env;
+
+/**
+ * Ends the process with the specified `code`. If omitted, exit uses the
+ * 'success' code 0. To exit with failure use `1`.
+ * TODO: Improve platform to actually quit with an exit code.
+ */
+exports.exit = function exit(code) {
+ // This is used by 'cfx' to find out exit code.
+ if ('resultFile' in options) {
+ let stream = file.open(options.resultFile, 'w');
+ stream.write(code ? 'FAIL' : 'OK');
+ stream.close();
+ }
+
+ appStartup.quit(code ? E_ATTEMPT : E_FORCE);
+};
+
+/**
+ * Returns a path of the system's or application's special directory / file
+ * associated with a given `id`. For list of possible `id`s please see:
+ * https://developer.mozilla.org/en/Code_snippets/File_I%2F%2FO#Getting_special_files
+ * http://mxr.mozilla.org/mozilla-central/source/xpcom/io/nsAppDirectoryServiceDefs.h
+ * @example
+ *
+ * // get firefox profile path
+ * let profilePath = require('system').pathFor('ProfD');
+ * // get OS temp files directory (/tmp)
+ * let temps = require('system').pathFor('TmpD');
+ * // get OS desktop path for an active user (~/Desktop on linux
+ * // or C:\Documents and Settings\username\Desktop on windows).
+ * let desktopPath = require('system').pathFor('Desk');
+ */
+exports.pathFor = function pathFor(id) {
+ return directoryService.get(id, Ci.nsIFile).path;
+};
+
+/**
+ * What platform you're running on (all lower case string).
+ * For possible values see:
+ * https://developer.mozilla.org/en/OS_TARGET
+ */
+exports.platform = runtime.OS.toLowerCase();
+
+/**
+ * What processor architecture you're running on:
+ * `'arm', 'ia32', or 'x64'`.
+ */
+exports.architecture = runtime.XPCOMABI.split('_')[0];
+
+/**
+ * What compiler used for build:
+ * `'msvc', 'n32', 'gcc2', 'gcc3', 'sunc', 'ibmc'...`
+ */
+exports.compiler = runtime.XPCOMABI.split('_')[1];
+
+/**
+ * The application's build ID/date, for example "2004051604".
+ */
+exports.build = appInfo.appBuildID;
+
+/**
+ * The XUL application's UUID.
+ * This has traditionally been in the form
+ * `{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}` but for some applications it may
+ * be: "appname@vendor.tld".
+ */
+exports.id = appInfo.ID;
+
+/**
+ * The name of the application.
+ */
+exports.name = appInfo.name;
+
+/**
+ * The XUL application's version, for example "0.8.0+" or "3.7a1pre".
+ */
+exports.version = appInfo.version;
+
+/**
+ * XULRunner version.
+ */
+exports.platformVersion = appInfo.platformVersion;
+
+
+/**
+ * The name of the application vendor, for example "Mozilla".
+ */
+exports.vendor = appInfo.vendor;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/tab-browser.js b/tools/addon-sdk-1.7/packages/api-utils/lib/tab-browser.js
new file mode 100644
index 0000000..13d365a
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/tab-browser.js
@@ -0,0 +1,727 @@
+/* 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} = require("chrome");
+var NetUtil = {};
+Cu.import("resource://gre/modules/NetUtil.jsm", NetUtil);
+NetUtil = NetUtil.NetUtil;
+const errors = require("./errors");
+const windowUtils = require("./window-utils");
+const apiUtils = require("./api-utils");
+const collection = require("./collection");
+
+// TODO: The hard-coding of app-specific info here isn't very nice;
+// ideally such app-specific info should be more decoupled, and the
+// module should be extensible, allowing for support of new apps at
+// runtime, perhaps by inspecting supported packages (e.g. via
+// dynamically-named modules or package-defined extension points).
+
+if (!require("./xul-app").is("Firefox")) {
+ throw new Error([
+ "The tab-browser module currently supports only Firefox. In the future ",
+ "it will support other applications. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=560716 for more information."
+ ].join(""));
+}
+
+function onBrowserLoad(callback, event) {
+ if (event.target && event.target.defaultView == this) {
+ this.removeEventListener("load", onBrowserLoad, true);
+ try {
+ require("timer").setTimeout(function () {
+ callback(event);
+ }, 10);
+ } catch (e) { console.exception(e); }
+ }
+}
+
+// Utility function to open a new browser window.
+function openBrowserWindow(callback, url) {
+ let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher);
+ let urlString = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ urlString.data = url;
+ let window = ww.openWindow(null, "chrome://browser/content/browser.xul",
+ "_blank", "chrome,all,dialog=no", urlString);
+ if (callback)
+ window.addEventListener("load", onBrowserLoad.bind(window, callback), true);
+
+ return window;
+}
+
+// Open a URL in a new tab
+exports.addTab = function addTab(url, options) {
+ if (!options)
+ options = {};
+ options.url = url;
+
+ options = apiUtils.validateOptions(options, {
+ // TODO: take URL object instead of string (bug 564524)
+ url: {
+ is: ["string"],
+ ok: function (v) !!v,
+ msg: "The url parameter must have be a non-empty string."
+ },
+ inNewWindow: {
+ is: ["undefined", "null", "boolean"]
+ },
+ inBackground: {
+ is: ["undefined", "null", "boolean"]
+ },
+ onLoad: {
+ is: ["undefined", "null", "function"]
+ },
+ isPinned: {
+ is: ["undefined", "boolean"]
+ }
+ });
+
+ var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator);
+ var win = wm.getMostRecentWindow("navigator:browser");
+ if (!win || options.inNewWindow) {
+ openBrowserWindow(function(e) {
+ if(options.isPinned) {
+ //get the active tab in the recently created window
+ let mainWindow = e.target.defaultView;
+ mainWindow.gBrowser.pinTab(mainWindow.gBrowser.selectedTab);
+ }
+ require("./errors").catchAndLog(function(e) options.onLoad(e))(e);
+ }, options.url);
+ } else {
+ let tab = win.gBrowser.addTab(options.url);
+ if (!options.inBackground)
+ win.gBrowser.selectedTab = tab;
+ if (options.onLoad) {
+ let tabBrowser = win.gBrowser.getBrowserForTab(tab);
+ tabBrowser.addEventListener("load", function onLoad(e) {
+ if (e.target.defaultView.content.location == "about:blank")
+ return;
+ // remove event handler from addTab - don't want notified
+ // for subsequent loads in same tab.
+ tabBrowser.removeEventListener("load", onLoad, true);
+ require("./errors").catchAndLog(function(e) options.onLoad(e))(e);
+ }, true);
+ }
+ }
+}
+
+// Iterate over a window's tabbrowsers
+function tabBrowserIterator(window) {
+ var browsers = window.document.querySelectorAll("tabbrowser");
+ for (var i = 0; i < browsers.length; i++)
+ yield browsers[i];
+}
+
+// Iterate over a tabbrowser's tabs
+function tabIterator(tabbrowser) {
+ var tabs = tabbrowser.tabContainer;
+ for (var i = 0; i < tabs.children.length; i++) {
+ yield tabs.children[i];
+ }
+}
+
+// Tracker for all tabbrowsers across all windows,
+// or a single tabbrowser if the window is given.
+function Tracker(delegate, window) {
+ this._delegate = delegate;
+ this._browsers = [];
+ this._window = window;
+ this._windowTracker = windowUtils.WindowTracker(this);
+
+ require("./unload").ensure(this);
+}
+Tracker.prototype = {
+ __iterator__: function __iterator__() {
+ for (var i = 0; i < this._browsers.length; i++)
+ yield this._browsers[i];
+ },
+ get: function get(index) {
+ return this._browsers[index];
+ },
+ onTrack: function onTrack(window) {
+ if (this._window && window != this._window)
+ return;
+
+ for (let browser in tabBrowserIterator(window))
+ this._browsers.push(browser);
+ if (this._delegate)
+ for (let browser in tabBrowserIterator(window))
+ this._delegate.onTrack(browser);
+ },
+ onUntrack: function onUntrack(window) {
+ if (this._window && window != this._window)
+ return;
+
+ for (let browser in tabBrowserIterator(window)) {
+ let index = this._browsers.indexOf(browser);
+ if (index != -1)
+ this._browsers.splice(index, 1);
+ else
+ console.error("internal error: browser tab not found");
+ }
+ if (this._delegate)
+ for (let browser in tabBrowserIterator(window))
+ this._delegate.onUntrack(browser);
+ },
+ get length() {
+ return this._browsers.length;
+ },
+ unload: function unload() {
+ this._windowTracker.unload();
+ }
+};
+exports.Tracker = apiUtils.publicConstructor(Tracker);
+
+// Tracker for all tabs across all windows,
+// or a single window if it's given.
+function TabTracker(delegate, window) {
+ this._delegate = delegate;
+ this._tabs = [];
+ this._tracker = new Tracker(this, window);
+ require("./unload").ensure(this);
+}
+TabTracker.prototype = {
+ _TAB_EVENTS: ["TabOpen", "TabClose"],
+ _safeTrackTab: function safeTrackTab(tab) {
+ this._tabs.push(tab);
+ try {
+ this._delegate.onTrack(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ _safeUntrackTab: function safeUntrackTab(tab) {
+ var index = this._tabs.indexOf(tab);
+ if (index == -1)
+ console.error("internal error: tab not found");
+ this._tabs.splice(index, 1);
+ try {
+ this._delegate.onUntrack(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ handleEvent: function handleEvent(event) {
+ switch (event.type) {
+ case "TabOpen":
+ this._safeTrackTab(event.target);
+ break;
+ case "TabClose":
+ this._safeUntrackTab(event.target);
+ break;
+ default:
+ throw new Error("internal error: unknown event type: " +
+ event.type);
+ }
+ },
+ onTrack: function onTrack(tabbrowser) {
+ for (let tab in tabIterator(tabbrowser))
+ this._safeTrackTab(tab);
+ var self = this;
+ this._TAB_EVENTS.forEach(
+ function(eventName) {
+ tabbrowser.tabContainer.addEventListener(eventName, self, true);
+ });
+ },
+ onUntrack: function onUntrack(tabbrowser) {
+ for (let tab in tabIterator(tabbrowser))
+ this._safeUntrackTab(tab);
+ var self = this;
+ this._TAB_EVENTS.forEach(
+ function(eventName) {
+ tabbrowser.tabContainer.removeEventListener(eventName, self, true);
+ });
+ },
+ unload: function unload() {
+ this._tracker.unload();
+ }
+};
+exports.TabTracker = apiUtils.publicConstructor(TabTracker);
+
+exports.whenContentLoaded = function whenContentLoaded(callback) {
+ var cb = require("./errors").catchAndLog(function eventHandler(event) {
+ if (event.target && event.target.defaultView)
+ callback(event.target.defaultView);
+ });
+
+ var tracker = new Tracker({
+ onTrack: function(tabBrowser) {
+ tabBrowser.addEventListener("DOMContentLoaded", cb, false);
+ },
+ onUntrack: function(tabBrowser) {
+ tabBrowser.removeEventListener("DOMContentLoaded", cb, false);
+ }
+ });
+
+ return tracker;
+};
+
+exports.__defineGetter__("activeTab", function() {
+ const wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator);
+ let mainWindow = wm.getMostRecentWindow("navigator:browser");
+ return mainWindow.gBrowser.selectedTab;
+});
+
+/******************* TabModule *********************/
+
+// Supported tab events
+const events = [
+ "onActivate",
+ "onDeactivate",
+ "onOpen",
+ "onClose",
+ "onReady",
+ "onLoad",
+ "onPaint"
+];
+exports.tabEvents = events;
+
+/**
+ * TabModule
+ *
+ * Constructor for a module that implements the tabs API
+ */
+let TabModule = exports.TabModule = function TabModule(window) {
+ let self = this;
+ /**
+ * Tab
+ *
+ * Safe object representing a tab.
+ */
+ let tabConstructor = apiUtils.publicConstructor(function(element) {
+ if (!element)
+ throw new Error("no tab element.");
+ let win = element.ownerDocument.defaultView;
+ if (!win)
+ throw new Error("element has no window.");
+ if (window && win != window)
+ throw new Error("module's window and element's window don't match.");
+ let browser = win.gBrowser.getBrowserForTab(element);
+
+ this.__defineGetter__("title", function() browser.contentDocument.title);
+ this.__defineGetter__("location", function() browser.contentDocument.location);
+ this.__defineSetter__("location", function(val) browser.contentDocument.location = val);
+ this.__defineGetter__("contentWindow", function() browser.contentWindow);
+ this.__defineGetter__("contentDocument", function() browser.contentDocument);
+ this.__defineGetter__("favicon", function() {
+ let pageURI = NetUtil.newURI(browser.contentDocument.location);
+ let fs = Cc["@mozilla.org/browser/favicon-service;1"].
+ getService(Ci.nsIFaviconService);
+ let faviconURL;
+ try {
+ let faviconURI = fs.getFaviconForPage(pageURI);
+ faviconURL = fs.getFaviconDataAsDataURL(faviconURI);
+ } catch(ex) {
+ let data = getChromeURLContents("chrome://mozapps/skin/places/defaultFavicon.png");
+ let encoded = browser.contentWindow.btoa(data);
+ faviconURL = "data:image/png;base64," + encoded;
+ }
+ return faviconURL;
+ });
+ this.__defineGetter__("style", function() null); // TODO
+ this.__defineGetter__("index", function() win.gBrowser.getBrowserIndexForDocument(browser.contentDocument));
+ this.__defineGetter__("thumbnail", function() getThumbnailCanvasForTab(element, browser.contentWindow));
+
+ this.close = function() win.gBrowser.removeTab(element);
+ this.move = function(index) {
+ win.gBrowser.moveTabTo(element, index);
+ };
+
+ this.__defineGetter__("isPinned", function() element.pinned);
+ this.pin = function() win.gBrowser.pinTab(element);
+ this.unpin = function() win.gBrowser.unpinTab(element);
+
+ // Set up the event handlers
+ let tab = this;
+ events.filter(function(e) e != "onOpen").forEach(function(e) {
+ // create a collection for each event
+ collection.addCollectionProperty(tab, e);
+ // make tabs setter for each event, for adding via property assignment
+ tab.__defineSetter__(e, function(val) tab[e].add(val));
+ });
+
+ // listen for events, filtered on this tab
+ eventsTabDelegate.addTabDelegate(this);
+ });
+
+ /**
+ * tabs.activeTab
+ */
+ this.__defineGetter__("activeTab", function() {
+ try {
+ return window ? tabConstructor(window.gBrowser.selectedTab)
+ : tabConstructor(exports.activeTab);
+ }
+ catch (e) { }
+ return null;
+ });
+ this.__defineSetter__("activeTab", function(tab) {
+ let [tabElement, win] = getElementAndWindowForTab(tab, window);
+ if (tabElement) {
+ // set as active tab
+ win.gBrowser.selectedTab = tabElement;
+ // focus the window
+ win.focus();
+ }
+ });
+
+ this.open = function TM_open(options) {
+ open(options, tabConstructor, window);
+ }
+
+ // Set up the event handlers
+ events.forEach(function(eventHandler) {
+ // create a collection for each event
+ collection.addCollectionProperty(self, eventHandler);
+ // make tabs setter for each event, for adding via property assignment
+ self.__defineSetter__(eventHandler, function(val) self[eventHandler].add(val));
+ });
+
+ // Tracker that listens for tab events, and proxies
+ // them to registered event listeners.
+ let eventsTabDelegate = {
+ selectedTab: null,
+ tabs: [],
+ addTabDelegate: function TETT_addTabDelegate(tabObj) {
+ this.tabs.push(tabObj);
+ },
+ pushTabEvent: function TETT_pushTabEvent(event, tab) {
+ for (let callback in self[event]) {
+ require("./errors").catchAndLog(function(tab) {
+ callback(new tabConstructor(tab));
+ })(tab);
+ }
+
+ if (event != "onOpen") {
+ this.tabs.forEach(function(tabObj) {
+ if (tabObj[event].length) {
+ let [tabEl,] = getElementAndWindowForTab(tabObj, window);
+ if (tabEl == tab) {
+ for (let callback in tabObj[event])
+ require("./errors").catchAndLog(function() callback())();
+ }
+ }
+ // if being closed, remove the tab object from the cache
+ // of tabs to notify about events.
+ if (event == "onClose")
+ this.tabs.splice(this.tabs.indexOf(tabObj), 1);
+ }, this);
+ }
+ },
+ unload: function() {
+ this.selectedTab = null;
+ this.tabs.splice(0);
+ }
+ };
+ require("./unload").ensure(eventsTabDelegate);
+
+ let eventsTabTracker = new ModuleTabTracker({
+ onTrack: function TETT_onTrack(tab) {
+ eventsTabDelegate.pushTabEvent("onOpen", tab);
+ },
+ onUntrack: function TETT_onUntrack(tab) {
+ eventsTabDelegate.pushTabEvent("onClose", tab);
+ },
+ onSelect: function TETT_onSelect(tab) {
+ if (eventsTabDelegate.selectedTab)
+ eventsTabDelegate.pushTabEvent("onDeactivate", tab);
+
+ eventsTabDelegate.selectedTab = new tabConstructor(tab);
+
+ eventsTabDelegate.pushTabEvent("onActivate", tab);
+ },
+ onReady: function TETT_onReady(tab) {
+ eventsTabDelegate.pushTabEvent("onReady", tab);
+ },
+ onLoad: function TETT_onLoad(tab) {
+ eventsTabDelegate.pushTabEvent("onLoad", tab);
+ },
+ onPaint: function TETT_onPaint(tab) {
+ eventsTabDelegate.pushTabEvent("onPaint", tab);
+ }
+ }, window);
+ require("./unload").ensure(eventsTabTracker);
+
+ // Iterator for all tabs
+ this.__iterator__ = function tabsIterator() {
+ for (let i = 0; i < eventsTabTracker._tabs.length; i++)
+ yield tabConstructor(eventsTabTracker._tabs[i]);
+ }
+
+ this.__defineGetter__("length", function() eventsTabTracker._tabs.length);
+
+ // Cleanup when unloaded
+ this.unload = function TM_unload() {
+ // Unregister tabs event listeners
+ events.forEach(function(e) self[e] = []);
+ }
+ require("./unload").ensure(this);
+
+} // End of TabModule constructor
+
+/**
+ * tabs.open - open a URL in a new tab
+ */
+function open(options, tabConstructor, window) {
+ if (typeof options === "string")
+ options = { url: options };
+
+ options = apiUtils.validateOptions(options, {
+ url: {
+ is: ["string"]
+ },
+ inNewWindow: {
+ is: ["undefined", "boolean"]
+ },
+ inBackground: {
+ is: ["undefined", "boolean"]
+ },
+ isPinned: {
+ is: ["undefined", "boolean"]
+ },
+ onOpen: {
+ is: ["undefined", "function"]
+ }
+ });
+
+ if (window)
+ options.inNewWindow = false;
+
+ let win = window || require("./window-utils").activeBrowserWindow;
+
+ if (!win || options.inNewWindow)
+ openURLInNewWindow(options, tabConstructor);
+ else
+ openURLInNewTab(options, win, tabConstructor);
+}
+
+function openURLInNewWindow(options, tabConstructor) {
+ let addTabOptions = {
+ inNewWindow: true
+ };
+ if (options.onOpen) {
+ addTabOptions.onLoad = function(e) {
+ let win = e.target.defaultView;
+ let tabEl = win.gBrowser.tabContainer.childNodes[0];
+ let tabBrowser = win.gBrowser.getBrowserForTab(tabEl);
+ tabBrowser.addEventListener("load", function onLoad(e) {
+ tabBrowser.removeEventListener("load", onLoad, true);
+ let tab = tabConstructor(tabEl);
+ require("./errors").catchAndLog(function(e) options.onOpen(e))(tab);
+ }, true);
+ };
+ }
+ if (options.isPinned) {
+ addTabOptions.isPinned = true;
+ }
+ exports.addTab(options.url.toString(), addTabOptions);
+}
+
+function openURLInNewTab(options, window, tabConstructor) {
+ window.focus();
+ let tabEl = window.gBrowser.addTab(options.url.toString());
+ if (!options.inBackground)
+ window.gBrowser.selectedTab = tabEl;
+ if (options.isPinned)
+ window.gBrowser.pinTab(tabEl);
+ if (options.onOpen) {
+ let tabBrowser = window.gBrowser.getBrowserForTab(tabEl);
+ tabBrowser.addEventListener("load", function onLoad(e) {
+ // remove event handler from addTab - don't want to be notified
+ // for subsequent loads in same tab.
+ tabBrowser.removeEventListener("load", onLoad, true);
+ let tab = tabConstructor(tabEl);
+ require("./timer").setTimeout(function() {
+ require("./errors").catchAndLog(function(tab) options.onOpen(tab))(tab);
+ }, 10);
+ }, true);
+ }
+}
+
+function getElementAndWindowForTab(tabObj, window) {
+ // iterate over open windows, or use single window if provided
+ let windowIterator = window ? function() { yield window; }
+ : require("./window-utils").windowIterator;
+ for (let win in windowIterator()) {
+ if (win.gBrowser) {
+ // find the tab element at tab.index
+ let index = win.gBrowser.getBrowserIndexForDocument(tabObj.contentDocument);
+ if (index > -1)
+ return [win.gBrowser.tabContainer.getItemAtIndex(index), win];
+ }
+ }
+ return [null, null];
+}
+
+// Tracker for all tabs across all windows
+// This is tab-browser.TabTracker, but with
+// support for additional events added.
+function ModuleTabTracker(delegate, window) {
+ this._delegate = delegate;
+ this._tabs = [];
+ this._tracker = new Tracker(this, window);
+ require("./unload").ensure(this);
+}
+ModuleTabTracker.prototype = {
+ _TAB_EVENTS: ["TabOpen", "TabClose", "TabSelect", "DOMContentLoaded",
+ "load", "MozAfterPaint"],
+ _safeTrackTab: function safeTrackTab(tab) {
+ tab.addEventListener("load", this, false);
+ tab.linkedBrowser.addEventListener("MozAfterPaint", this, false);
+ this._tabs.push(tab);
+ try {
+ this._delegate.onTrack(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ _safeUntrackTab: function safeUntrackTab(tab) {
+ tab.removeEventListener("load", this, false);
+ tab.linkedBrowser.removeEventListener("MozAfterPaint", this, false);
+ var index = this._tabs.indexOf(tab);
+ if (index == -1)
+ throw new Error("internal error: tab not found");
+ this._tabs.splice(index, 1);
+ try {
+ this._delegate.onUntrack(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ _safeSelectTab: function safeSelectTab(tab) {
+ var index = this._tabs.indexOf(tab);
+ if (index == -1)
+ console.error("internal error: tab not found");
+ try {
+ if (this._delegate.onSelect)
+ this._delegate.onSelect(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ _safeDOMContentLoaded: function safeDOMContentLoaded(event) {
+ let tabBrowser = event.currentTarget;
+ let tabBrowserIndex = tabBrowser.getBrowserIndexForDocument(event.target);
+ // TODO: I'm seeing this when loading data url images
+ if (tabBrowserIndex == -1)
+ return;
+ let tab = tabBrowser.tabContainer.getItemAtIndex(tabBrowserIndex);
+ let index = this._tabs.indexOf(tab);
+ if (index == -1)
+ console.error("internal error: tab not found");
+ try {
+ if (this._delegate.onReady)
+ this._delegate.onReady(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ _safeLoad: function safeLoad(event) {
+ let tab = event.target;
+ let index = this._tabs.indexOf(tab);
+ if (index == -1)
+ console.error("internal error: tab not found");
+ try {
+ if (this._delegate.onLoad)
+ this._delegate.onLoad(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ _safeMozAfterPaint: function safeMozAfterPaint(event) {
+ let win = event.currentTarget.ownerDocument.defaultView;
+ let tabIndex = win.gBrowser.getBrowserIndexForDocument(event.target.document);
+ if (tabIndex == -1)
+ return;
+ let tab = win.gBrowser.tabContainer.getItemAtIndex(tabIndex);
+ let index = this._tabs.indexOf(tab);
+ if (index == -1)
+ console.error("internal error: tab not found");
+ try {
+ if (this._delegate.onPaint)
+ this._delegate.onPaint(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ handleEvent: function handleEvent(event) {
+ switch (event.type) {
+ case "TabOpen":
+ this._safeTrackTab(event.target);
+ break;
+ case "TabClose":
+ this._safeUntrackTab(event.target);
+ break;
+ case "TabSelect":
+ this._safeSelectTab(event.target);
+ break;
+ case "DOMContentLoaded":
+ this._safeDOMContentLoaded(event);
+ break;
+ case "load":
+ this._safeLoad(event);
+ break;
+ case "MozAfterPaint":
+ this._safeMozAfterPaint(event);
+ break;
+ default:
+ throw new Error("internal error: unknown event type: " +
+ event.type);
+ }
+ },
+ onTrack: function onTrack(tabbrowser) {
+ for (let tab in tabIterator(tabbrowser))
+ this._safeTrackTab(tab);
+ tabbrowser.tabContainer.addEventListener("TabOpen", this, false);
+ tabbrowser.tabContainer.addEventListener("TabClose", this, false);
+ tabbrowser.tabContainer.addEventListener("TabSelect", this, false);
+ tabbrowser.ownerDocument.defaultView.gBrowser.addEventListener("DOMContentLoaded", this, false);
+ },
+ onUntrack: function onUntrack(tabbrowser) {
+ for (let tab in tabIterator(tabbrowser))
+ this._safeUntrackTab(tab);
+ tabbrowser.tabContainer.removeEventListener("TabOpen", this, false);
+ tabbrowser.tabContainer.removeEventListener("TabClose", this, false);
+ tabbrowser.tabContainer.removeEventListener("TabSelect", this, false);
+ tabbrowser.ownerDocument.defaultView.gBrowser.removeEventListener("DOMContentLoaded", this, false);
+ },
+ unload: function unload() {
+ this._tracker.unload();
+ }
+};
+
+// Utility to get a thumbnail canvas from a tab object
+function getThumbnailCanvasForTab(tabEl, window) {
+ var thumbnail = window.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ thumbnail.mozOpaque = true;
+ window = tabEl.linkedBrowser.contentWindow;
+ thumbnail.width = Math.ceil(window.screen.availWidth / 5.75);
+ var aspectRatio = 0.5625; // 16:9
+ thumbnail.height = Math.round(thumbnail.width * aspectRatio);
+ var ctx = thumbnail.getContext("2d");
+ var snippetWidth = window.innerWidth * .6;
+ var scale = thumbnail.width / snippetWidth;
+ ctx.scale(scale, scale);
+ ctx.drawWindow(window, window.scrollX, window.scrollY, snippetWidth, snippetWidth * aspectRatio, "rgb(255,255,255)");
+ return thumbnail;
+}
+
+// Utility to return the contents of the target of a chrome URL
+function getChromeURLContents(chromeURL) {
+ let io = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ let channel = io.newChannel(chromeURL, null, null);
+ let input = channel.open();
+ let stream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ stream.setInputStream(input);
+ let str = stream.readBytes(input.available());
+ stream.close();
+ input.close();
+ return str;
+}
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/events.js b/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/events.js
new file mode 100644
index 0000000..efb8b45
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/events.js
@@ -0,0 +1,26 @@
+/* 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 ON_PREFIX = "on";
+const TAB_PREFIX = "Tab";
+
+const EVENTS = {
+ ready: "DOMContentLoaded",
+ open: "TabOpen",
+ close: "TabClose",
+ activate: "TabSelect",
+ deactivate: null,
+ pinned: "TabPinned",
+ unpinned: "TabUnpinned"
+}
+exports.EVENTS = EVENTS;
+
+Object.keys(EVENTS).forEach(function(name) {
+ EVENTS[name] = {
+ name: name,
+ listener: ON_PREFIX + name.charAt(0).toUpperCase() + name.substr(1),
+ dom: EVENTS[name]
+ }
+});
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/observer.js b/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/observer.js
new file mode 100644
index 0000000..e3854cb
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/observer.js
@@ -0,0 +1,93 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { EventEmitterTrait: EventEmitter } = require("../events");
+const { DOMEventAssembler } = require("../events/assembler");
+const { Trait } = require("../light-traits");
+const { getActiveTab, getTabs, getTabContainers } = require("./utils");
+const { browserWindowIterator, isBrowser } = require("../window-utils");
+const { observer: windowObserver } = require("../windows/observer");
+
+const EVENTS = {
+ "TabOpen": "open",
+ "TabClose": "close",
+ "TabSelect": "select",
+ "TabMove": "move",
+ "TabPinned": "pinned",
+ "TabUnpinned": "unpinned"
+};
+
+
+// Event emitter objects used to register listeners and emit events on them
+// when they occur.
+const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
+ /**
+ * Method is implemented by `EventEmitter` and is used just for emitting
+ * events on registered listeners.
+ */
+ _emit: Trait.required,
+ /**
+ * Events that are supported and emitted by the module.
+ */
+ supportedEventsTypes: Object.keys(EVENTS),
+ /**
+ * Function handles all the supported events on all the windows that are
+ * observed. Method is used to proxy events to the listeners registered on
+ * this event emitter.
+ * @param {Event} event
+ * Keyboard event being emitted.
+ */
+ handleEvent: function handleEvent(event) {
+ this._emit(EVENTS[event.type], event.target, event);
+ }
+});
+
+// Currently gecko does not dispatches any event on the previously selected
+// tab before / after "TabSelect" is dispatched. In order to work around this
+// limitation we keep track of selected tab and emit "deactivate" event with
+// that before emitting "activate" on selected tab.
+var selectedTab = null;
+function onTabSelect(tab) {
+ if (selectedTab !== tab) {
+ if (selectedTab) observer._emit("deactivate", selectedTab);
+ if (tab) observer._emit("activate", selectedTab = tab);
+ }
+};
+observer.on("select", onTabSelect);
+
+// We also observe opening / closing windows in order to add / remove it's
+// containers to the observed list.
+function onWindowOpen(chromeWindow) {
+ if (!isBrowser(chromeWindow)) return; // Ignore if it's not a browser window.
+ getTabContainers(chromeWindow).forEach(function (container) {
+ observer.observe(container);
+ });
+}
+windowObserver.on("open", onWindowOpen);
+
+function onWindowClose(chromeWindow) {
+ if (!isBrowser(chromeWindow)) return; // Ignore if it's not a browser window.
+ getTabContainers(chromeWindow).forEach(function (container) {
+ observer.ignore(container);
+ });
+}
+windowObserver.on("close", onWindowClose);
+
+
+// Currently gecko does not dispatches "TabSelect" events when different
+// window gets activated. To work around this limitation we emulate "select"
+// event for this case.
+windowObserver.on("activate", function onWindowActivate(chromeWindow) {
+ if (!isBrowser(chromeWindow)) return; // Ignore if it's not a browser window.
+ observer._emit("select", getActiveTab(chromeWindow));
+});
+
+// We should synchronize state, since probably we already have at least one
+// window open.
+for each (let window in browserWindowIterator()) onWindowOpen(window);
+
+exports.observer = observer;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/tab.js b/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/tab.js
new file mode 100644
index 0000000..246834f
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/tab.js
@@ -0,0 +1,264 @@
+/* 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 { Ci } = require('chrome');
+const { Trait } = require("../traits");
+const { EventEmitter } = require("../events");
+const { validateOptions } = require("../api-utils");
+const { defer } = require("../functional");
+const { EVENTS } = require("./events");
+const { getThumbnailURIForWindow } = require("../utils/thumbnail");
+const { getFaviconURIForLocation } = require("../utils/data");
+
+
+
+// Array of the inner instances of all the wrapped tabs.
+const TABS = [];
+
+/**
+ * Trait used to create tab wrappers.
+ */
+const TabTrait = Trait.compose(EventEmitter, {
+ on: Trait.required,
+ _emit: Trait.required,
+ /**
+ * Tab DOM element that is being wrapped.
+ */
+ _tab: null,
+ /**
+ * Window wrapper whose tab this object represents.
+ */
+ window: null,
+ constructor: function Tab(options) {
+ this._onReady = this._onReady.bind(this);
+ this._tab = options.tab;
+ let window = this.window = options.window;
+ // Setting event listener if was passed.
+ for each (let type in EVENTS) {
+ let listener = options[type.listener];
+ if (listener)
+ this.on(type.name, options[type.listener]);
+ if ('ready' != type.name) // window spreads this event.
+ window.tabs.on(type.name, this._onEvent.bind(this, type.name));
+ }
+
+ this.on(EVENTS.close.name, this.destroy.bind(this));
+ this._browser.addEventListener(EVENTS.ready.dom, this._onReady, true);
+
+ if (options.isPinned)
+ this.pin();
+
+ // Since we will have to identify tabs by a DOM elements facade function
+ // is used as constructor that collects all the instances and makes sure
+ // that they more then one wrapper is not created per tab.
+ return this;
+ },
+ destroy: function destroy() {
+ this._removeAllListeners();
+ this._browser.removeEventListener(EVENTS.ready.dom, this._onReady,
+ true);
+ },
+
+ /**
+ * Internal listener that emits public event 'ready' when the page of this
+ * tab is loaded.
+ */
+ _onReady: function _onReady(event) {
+ // IFrames events will bubble so we need to ignore those.
+ if (event.target == this._contentDocument)
+ this._emit(EVENTS.ready.name, this._public);
+ },
+ /**
+ * Internal tab event router. Window will emit tab related events for all it's
+ * tabs, this listener will propagate all the events for this tab to it's
+ * listeners.
+ */
+ _onEvent: function _onEvent(type, tab) {
+ if (tab == this._public)
+ this._emit(type, tab);
+ },
+ /**
+ * Browser DOM element where page of this tab is currently loaded.
+ */
+ get _browser() this._window.gBrowser.getBrowserForTab(this._tab),
+ /**
+ * Window DOM element containing this tab.
+ */
+ get _window() this._tab.ownerDocument.defaultView,
+ /**
+ * Document object of the page that is currently loaded in this tab.
+ */
+ get _contentDocument() this._browser.contentDocument,
+ /**
+ * Window object of the page that is currently loaded in this tab.
+ */
+ get _contentWindow() this._browser.contentWindow,
+
+ /**
+ * The title of the page currently loaded in the tab.
+ * Changing this property changes an actual title.
+ * @type {String}
+ */
+ get title() this._contentDocument.title,
+ set title(value) this._contentDocument.title = String(value),
+ /**
+ * Location of the page currently loaded in this tab.
+ * Changing this property will loads page under under the specified location.
+ * @type {String}
+ */
+ get url() String(this._browser.currentURI.spec),
+ set url(value) this._changeLocation(String(value)),
+ // "TabOpen" event is fired when it's still "about:blank" is loaded in the
+ // changing `location` property of the `contentDocument` has no effect since
+ // seems to be either ignored or overridden by internal listener, there for
+ // location change is enqueued for the next turn of event loop.
+ _changeLocation: defer(function(url) this._browser.loadURI(url)),
+ /**
+ * URI of the favicon for the page currently loaded in this tab.
+ * @type {String}
+ */
+ get favicon() getFaviconURIForLocation(this.url),
+ /**
+ * The CSS style for the tab
+ */
+ get style() null, // TODO
+ /**
+ * The index of the tab relative to other tabs in the application window.
+ * Changing this property will change order of the actual position of the tab.
+ * @type {Number}
+ */
+ get index()
+ this._window.gBrowser.getBrowserIndexForDocument(this._contentDocument),
+ set index(value) this._window.gBrowser.moveTabTo(this._tab, value),
+ /**
+ * Thumbnail data URI of the page currently loaded in this tab.
+ * @type {String}
+ */
+ getThumbnail: function getThumbnail()
+ getThumbnailURIForWindow(this._contentWindow),
+ /**
+ * Whether or not tab is pinned (Is an app-tab).
+ * @type {Boolean}
+ */
+ get isPinned() this._tab.pinned,
+ pin: function pin() {
+ this._window.gBrowser.pinTab(this._tab);
+ },
+ unpin: function unpin() {
+ this._window.gBrowser.unpinTab(this._tab);
+ },
+
+ /**
+ * Create a worker for this tab, first argument is options given to Worker.
+ * @type {Worker}
+ */
+ attach: function attach(options) {
+ let { Worker } = require("../content/worker");
+ options.window = this._contentWindow;
+ let worker = Worker(options);
+ worker.once("detach", function detach() {
+ worker.destroy();
+ });
+ return worker;
+ },
+
+ /**
+ * Make this tab active.
+ * Please note: That this function is called synchronous since in E10S that
+ * will be the case. Besides this function is called from a constructor where
+ * we would like to return instance before firing a 'TabActivated' event.
+ */
+ activate: defer(function activate() {
+ if (this._window) // Ignore if window is closed by the time this is invoked.
+ this._window.gBrowser.selectedTab = this._tab;
+ }),
+ /**
+ * Close the tab
+ */
+ close: function close(callback) {
+ if (callback)
+ this.once(EVENTS.close.name, callback);
+ this._window.gBrowser.removeTab(this._tab);
+ },
+ /**
+ * Reload the tab
+ */
+ reload: function reload() {
+ this._window.gBrowser.reloadTab(this._tab);
+ }
+});
+
+function Tab(options) {
+ let chromeTab = options.tab;
+ for each (let tab in TABS) {
+ if (chromeTab == tab._tab)
+ return tab._public;
+ }
+ let tab = TabTrait(options);
+ TABS.push(tab);
+ return tab._public;
+}
+Tab.prototype = TabTrait.prototype;
+exports.Tab = Tab;
+
+function Options(options) {
+ if ("string" === typeof options)
+ options = { url: options };
+
+ return validateOptions(options, {
+ url: { is: ["string"] },
+ inBackground: { is: ["undefined", "boolean"] },
+ isPinned: { is: ["undefined", "boolean"] },
+ onOpen: { is: ["undefined", "function"] },
+ onClose: { is: ["undefined", "function"] },
+ onReady: { is: ["undefined", "function"] },
+ onActivate: { is: ["undefined", "function"] },
+ onDeactivate: { is: ["undefined", "function"] }
+ });
+}
+exports.Options = Options;
+
+
+exports.getTabForWindow = function (win) {
+ // Get browser window
+ let topWindow = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ if (!topWindow.gBrowser) return null;
+
+ // Get top window object, in case we are in a content iframe
+ let topContentWindow;
+ try {
+ topContentWindow = win.top;
+ } catch(e) {
+ // It may throw if win is not a valid content window
+ return null;
+ }
+
+ function getWindowID(obj) {
+ return obj.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .currentInnerWindowID;
+ }
+
+ // Search for related Tab
+ let topWindowId = getWindowID(topContentWindow);
+ for (let i = 0; i < topWindow.gBrowser.browsers.length; i++) {
+ let w = topWindow.gBrowser.browsers[i].contentWindow;
+ if (getWindowID(w) == topWindowId) {
+ return Tab({
+ // TODO: api-utils should not depend on addon-kit!
+ window: require("addon-kit/windows").BrowserWindow({ window: topWindow }),
+ tab: topWindow.gBrowser.tabs[i]
+ });
+ }
+ }
+
+ // We were unable to find the related tab!
+ return null;
+}
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/utils.js b/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/utils.js
new file mode 100644
index 0000000..1c411dc
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/utils.js
@@ -0,0 +1,59 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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";
+
+function getTabContainer(tabBrowser) {
+ return tabBrowser.tabContainer;
+}
+exports.getTabContainer = getTabContainer;
+
+function getTabBrowsers(window) {
+ return Array.slice(window.document.getElementsByTagName("tabbrowser"));
+}
+exports.getTabBrowsers = getTabBrowsers;
+
+function getTabContainers(window) {
+ return getTabBrowsers(window).map(getTabContainer);
+}
+exports.getTabContainers = getTabContainers;
+
+function getTabs(window) {
+ return getTabContainers(window).reduce(function (tabs, container) {
+ tabs.push.apply(tabs, container.children);
+ return tabs;
+ }, []);
+}
+exports.getTabs = getTabs;
+
+function getActiveTab(window) {
+ return window.gBrowser.selectedTab;
+}
+exports.getActiveTab = getActiveTab;
+
+function getOwnerWindow(tab) {
+ return tab.ownerDocument.defaultView;
+}
+exports.getOwnerWindow = getOwnerWindow;
+
+function openTab(window, url) {
+ return window.gBrowser.addTab(url);
+}
+exports.openTab = openTab;
+
+function isTabOpen(tab) {
+ return !!tab.linkedBrowser;
+}
+exports.isTabOpen = isTabOpen;
+
+function closeTab(tab) {
+ return getOwnerWindow(tab).gBrowser.removeTab(tab);
+}
+exports.closeTab = closeTab;
+
+function activateTab(tab) {
+ getOwnerWindow(tab).gBrowser.selectedTab = tab;
+}
+exports.activateTab = activateTab;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/test.js b/tools/addon-sdk-1.7/packages/api-utils/lib/test.js
new file mode 100644
index 0000000..e69a2f4
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/test.js
@@ -0,0 +1,108 @@
+/* vim:ts=2:sts=2:sw=2:
+ * 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 BaseAssert = require("./test/assert").Assert;
+const { isFunction, isObject } = require("./type");
+
+function extend(target) {
+ let descriptor = {}
+ Array.slice(arguments, 1).forEach(function(source) {
+ Object.getOwnPropertyNames(source).forEach(function onEach(name) {
+ descriptor[name] = Object.getOwnPropertyDescriptor(source, name);
+ });
+ });
+ return Object.create(target, descriptor);
+}
+
+/**
+ * Function takes test `suite` object in CommonJS format and defines all of the
+ * tests from that suite and nested suites in a jetpack format on a given
+ * `target` object. Optionally third argument `prefix` can be passed to prefix
+ * all the test names.
+ */
+function defineTestSuite(target, suite, prefix) {
+ prefix = prefix || "";
+ // If suite defines `Assert` that's what `assert` object have to be created
+ // from and passed to a test function (This allows custom assertion functions)
+ // See for details: http://wiki.commonjs.org/wiki/Unit_Testing/1.1
+ let Assert = suite.Assert || BaseAssert;
+ // Going through each item in the test suite and wrapping it into a
+ // Jetpack test format.
+ Object.keys(suite).forEach(function(key) {
+ // If name starts with test then it's a test function or suite.
+ if (key.indexOf("test") === 0) {
+ let test = suite[key];
+
+ // For each test function so we create a wrapper test function in a
+ // jetpack format and copy that to a `target` exports.
+ if (isFunction(test)) {
+
+ // Since names of the test may match across suites we use full object
+ // path as a name to avoid overriding same function.
+ target[prefix + key] = function(options) {
+
+ // Creating `assert` functions for this test.
+ let assert = Assert(options);
+
+ // If CommonJS test function expects more than one argument
+ // it means that test is async and second argument is a callback
+ // to notify that test is finished.
+ if (1 < test.length) {
+
+ // Letting test runner know that test is executed async and
+ // creating a callback function that CommonJS tests will call
+ // once it's done.
+ options.waitUntilDone();
+ test(assert, function() {
+ options.done();
+ });
+ }
+
+ // Otherwise CommonJS test is synchronous so we call it only with
+ // one argument.
+ else {
+ test(assert);
+ }
+ }
+ }
+
+ // If it's an object then it's a test suite containing test function
+ // and / or nested test suites. In that case we just extend prefix used
+ // and call this function to copy and wrap tests from nested suite.
+ else if (isObject(test)) {
+ // We need to clone `tests` instead of modifying it, since it's very
+ // likely that it is frozen (usually test suites imported modules).
+ test = extend(Object.prototype, test, {
+ Assert: test.Assert || Assert
+ });
+ defineTestSuite(target, test, prefix + key + ".");
+ }
+ }
+ });
+}
+
+/**
+ * This function is a CommonJS test runner function, but since Jetpack test
+ * runner and test format is different from CommonJS this function shims given
+ * `exports` with all its tests into a Jetpack test format so that the built-in
+ * test runner will be able to run CommonJS test without manual changes.
+ */
+exports.run = function run(exports) {
+
+ // We can't leave old properties on exports since those are test in a CommonJS
+ // format that why we move everything to a new `suite` object.
+ let suite = {};
+ Object.keys(exports).forEach(function(key) {
+ suite[key] = exports[key];
+ delete exports[key];
+ });
+
+ // Now we wrap all the CommonJS tests to a Jetpack format and define
+ // those to a given `exports` object since that where jetpack test runner
+ // will look for them.
+ defineTestSuite(exports, suite);
+};
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/test/assert.js b/tools/addon-sdk-1.7/packages/api-utils/lib/test/assert.js
new file mode 100644
index 0000000..fa40807
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/test/assert.js
@@ -0,0 +1,331 @@
+/* vim:ts=2:sts=2:sw=2:
+ * 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 { isFunction, isNull, isObject, isString, isRegExp, isArray, isDate,
+ isPrimitive, isUndefined, instanceOf, source } = require("../type");
+
+/**
+ * The `AssertionError` is defined in assert.
+ * @extends Error
+ * @example
+ * new assert.AssertionError({
+ * message: message,
+ * actual: actual,
+ * expected: expected
+ * })
+ */
+function AssertionError(options) {
+ let assertionError = Object.create(AssertionError.prototype);
+
+ if (isString(options))
+ options = { message: options };
+ if ("actual" in options)
+ assertionError.actual = options.actual;
+ if ("expected" in options)
+ assertionError.expected = options.expected;
+ if ("operator" in options)
+ assertionError.operator = options.operator;
+
+ assertionError.message = options.message;
+ assertionError.stack = new Error().stack;
+ return assertionError;
+}
+AssertionError.prototype = Object.create(Error.prototype, {
+ constructor: { value: AssertionError },
+ name: { value: "AssertionError", enumerable: true },
+ toString: { value: function toString() {
+ let value;
+ if (this.message) {
+ value = this.name + " : " + this.message;
+ }
+ else {
+ value = [
+ this.name + " : ",
+ source(this.expected),
+ this.operator,
+ source(this.actual)
+ ].join(" ");
+ }
+ return value;
+ }}
+});
+exports.AssertionError = AssertionError;
+
+function Assert(logger) {
+ return Object.create(Assert.prototype, { _log: { value: logger }});
+}
+Assert.prototype = {
+ fail: function fail(e) {
+ this._log.fail(e.message);
+ },
+ pass: function pass(message) {
+ this._log.pass(message);
+ },
+ error: function error(e) {
+ this._log.exception(e);
+ },
+ ok: function ok(value, message) {
+ if (!!!value) {
+ this.fail({
+ actual: value,
+ expected: true,
+ message: message,
+ operator: "=="
+ });
+ }
+ else {
+ this.pass(message);
+ }
+ },
+
+ /**
+ * The equality assertion tests shallow, coercive equality with `==`.
+ * @example
+ * assert.equal(1, 1, "one is one");
+ */
+ equal: function equal(actual, expected, message) {
+ if (actual == expected) {
+ this.pass(message);
+ }
+ else {
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "=="
+ });
+ }
+ },
+
+ /**
+ * The non-equality assertion tests for whether two objects are not equal
+ * with `!=`.
+ * @example
+ * assert.notEqual(1, 2, "one is not two");
+ */
+ notEqual: function notEqual(actual, expected, message) {
+ if (actual != expected) {
+ this.pass(message);
+ }
+ else {
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "!=",
+ });
+ }
+ },
+
+ /**
+ * The equivalence assertion tests a deep (with `===`) equality relation.
+ * @example
+ * assert.deepEqual({ a: "foo" }, { a: "foo" }, "equivalent objects")
+ */
+ deepEqual: function deepEqual(actual, expected, message) {
+ if (isDeepEqual(actual, expected)) {
+ this.pass(message);
+ }
+ else {
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "deepEqual"
+ });
+ }
+ },
+
+ /**
+ * The non-equivalence assertion tests for any deep (with `===`) inequality.
+ * @example
+ * assert.notDeepEqual({ a: "foo" }, Object.create({ a: "foo" }),
+ * "object's inherit from different prototypes");
+ */
+ notDeepEqual: function notDeepEqual(actual, expected, message) {
+ if (!isDeepEqual(actual, expected)) {
+ this.pass(message);
+ }
+ else {
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "notDeepEqual"
+ });
+ }
+ },
+
+ /**
+ * The strict equality assertion tests strict equality, as determined by
+ * `===`.
+ * @example
+ * assert.strictEqual(null, null, "`null` is `null`")
+ */
+ strictEqual: function strictEqual(actual, expected, message) {
+ if (actual === expected) {
+ this.pass(message);
+ }
+ else {
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "==="
+ });
+ }
+ },
+
+ /**
+ * The strict non-equality assertion tests for strict inequality, as
+ * determined by `!==`.
+ * @example
+ * assert.notStrictEqual(null, undefined, "`null` is not `undefined`");
+ */
+ notStrictEqual: function notStrictEqual(actual, expected, message) {
+ if (actual !== expected) {
+ this.pass(message);
+ }
+ else {
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "!=="
+ })
+ }
+ },
+
+ /**
+ * The assertion whether or not given `block` throws an exception. If optional
+ * `Error` argument is provided and it's type of function thrown error is
+ * asserted to be an instance of it, if type of `Error` is string then message
+ * of throw exception is asserted to contain it.
+ * @param {Function} block
+ * Function that is expected to throw.
+ * @param {Error|RegExp} [Error]
+ * Error constructor that is expected to be thrown or a string that
+ * must be contained by a message of the thrown exception, or a RegExp
+ * matching a message of the thrown exception.
+ * @param {String} message
+ * Description message
+ *
+ * @examples
+ *
+ * assert.throws(function block() {
+ * doSomething(4)
+ * }, "Object is expected", "Incorrect argument is passed");
+ *
+ * assert.throws(function block() {
+ * Object.create(5)
+ * }, TypeError, "TypeError is thrown");
+ */
+ throws: function throws(block, Error, message) {
+ let threw = false;
+ let exception = null;
+
+ // If third argument is not provided and second argument is a string it
+ // means that optional `Error` argument was not passed, so we shift
+ // arguments.
+ if (isString(Error) && isUndefined(message)) {
+ message = Error;
+ Error = undefined;
+ }
+
+ // Executing given `block`.
+ try {
+ block();
+ }
+ catch (e) {
+ threw = true;
+ exception = e;
+ }
+
+ // If exception was thrown and `Error` argument was not passed assert is
+ // passed.
+ if (threw && (isUndefined(Error) ||
+ // If passed `Error` is RegExp using it's test method to
+ // assert thrown exception message.
+ (isRegExp(Error) && Error.test(exception.message)) ||
+ // If passed `Error` is a constructor function testing if
+ // thrown exception is an instance of it.
+ (isFunction(Error) && instanceOf(exception, Error))))
+ {
+ this.pass(message);
+ }
+
+ // Otherwise we report assertion failure.
+ else {
+ let failure = {
+ message: message,
+ operator: "throws"
+ };
+
+ if (exception)
+ failure.actual = exception;
+
+ if (Error)
+ failure.expected = Error;
+
+ this.fail(failure);
+ }
+ }
+};
+exports.Assert = Assert;
+
+function isDeepEqual(actual, expected) {
+
+ // 7.1. All identical values are equivalent, as determined by ===.
+ if (actual === expected) {
+ return true;
+ }
+
+ // 7.2. If the expected value is a Date object, the actual value is
+ // equivalent if it is also a Date object that refers to the same time.
+ else if (isDate(actual) && isDate(expected)) {
+ return actual.getTime() === expected.getTime();
+ }
+
+ // XXX specification bug: this should be specified
+ else if (isPrimitive(actual) || isPrimitive(expected)) {
+ return expected === actual;
+ }
+
+ // 7.3. Other pairs that do not both pass typeof value == "object",
+ // equivalence is determined by ==.
+ else if (!isObject(actual) && !isObject(expected)) {
+ return actual == expected;
+ }
+
+ // 7.4. For all other Object pairs, including Array objects, equivalence is
+ // determined by having the same number of owned properties (as verified
+ // with Object.prototype.hasOwnProperty.call), the same set of keys
+ // (although not necessarily the same order), equivalent values for every
+ // corresponding key, and an identical "prototype" property. Note: this
+ // accounts for both named and indexed properties on Arrays.
+ else {
+ return actual.prototype === expected.prototype &&
+ isEquivalent(actual, expected);
+ }
+}
+
+function isEquivalent(a, b, stack) {
+ let aKeys = Object.keys(a);
+ let bKeys = Object.keys(b);
+
+ return aKeys.length === bKeys.length &&
+ isArrayEquivalent(aKeys.sort(), bKeys.sort()) &&
+ aKeys.every(function(key) {
+ return isDeepEqual(a[key], b[key], stack)
+ });
+}
+
+function isArrayEquivalent(a, b, stack) {
+ return isArray(a) && isArray(b) &&
+ a.every(function(value, index) {
+ return isDeepEqual(value, b[index]);
+ });
+}
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/text-streams.js b/tools/addon-sdk-1.7/packages/api-utils/lib/text-streams.js
new file mode 100644
index 0000000..96217eb
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/text-streams.js
@@ -0,0 +1,240 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * 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,components} = require("chrome");
+var NetUtil = {};
+Cu.import("resource://gre/modules/NetUtil.jsm", NetUtil);
+NetUtil = NetUtil.NetUtil;
+
+// NetUtil.asyncCopy() uses this buffer length, and since we call it, for best
+// performance we use it, too.
+const BUFFER_BYTE_LEN = 0x8000;
+const PR_UINT32_MAX = 0xffffffff;
+const DEFAULT_CHARSET = "UTF-8";
+
+exports.TextReader = TextReader;
+exports.TextWriter = TextWriter;
+
+/**
+ * An input stream that reads text from a backing stream using a given text
+ * encoding.
+ *
+ * @param inputStream
+ * The stream is backed by this nsIInputStream. It must already be
+ * opened.
+ * @param charset
+ * Text in inputStream is expected to be in this character encoding. If
+ * not given, "UTF-8" is assumed. See nsICharsetConverterManager.idl for
+ * documentation on how to determine other valid values for this.
+ */
+function TextReader(inputStream, charset) {
+ const self = this;
+ charset = checkCharset(charset);
+
+ let stream = Cc["@mozilla.org/intl/converter-input-stream;1"].
+ createInstance(Ci.nsIConverterInputStream);
+ stream.init(inputStream, charset, BUFFER_BYTE_LEN,
+ Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
+
+ let manager = new StreamManager(this, stream);
+
+ /**
+ * Reads a string from the stream. If the stream is closed, an exception is
+ * thrown.
+ *
+ * @param numChars
+ * The number of characters to read. If not given, the remainder of
+ * the stream is read.
+ * @return The string read. If the stream is already at EOS, returns the
+ * empty string.
+ */
+ this.read = function TextReader_read(numChars) {
+ manager.ensureOpened();
+
+ let readAll = false;
+ if (typeof(numChars) === "number")
+ numChars = Math.max(numChars, 0);
+ else
+ readAll = true;
+
+ let str = "";
+ let totalRead = 0;
+ let chunkRead = 1;
+
+ // Read in numChars or until EOS, whichever comes first. Note that the
+ // units here are characters, not bytes.
+ while (true) {
+ let chunk = {};
+ let toRead = readAll ?
+ PR_UINT32_MAX :
+ Math.min(numChars - totalRead, PR_UINT32_MAX);
+ if (toRead <= 0 || chunkRead <= 0)
+ break;
+
+ // The converter stream reads in at most BUFFER_BYTE_LEN bytes in a call
+ // to readString, enough to fill its byte buffer. chunkRead will be the
+ // number of characters encoded by the bytes in that buffer.
+ chunkRead = stream.readString(toRead, chunk);
+ str += chunk.value;
+ totalRead += chunkRead;
+ }
+
+ return str;
+ };
+}
+
+/**
+ * A buffered output stream that writes text to a backing stream using a given
+ * text encoding.
+ *
+ * @param outputStream
+ * The stream is backed by this nsIOutputStream. It must already be
+ * opened.
+ * @param charset
+ * Text will be written to outputStream using this character encoding.
+ * If not given, "UTF-8" is assumed. See nsICharsetConverterManager.idl
+ * for documentation on how to determine other valid values for this.
+ */
+function TextWriter(outputStream, charset) {
+ const self = this;
+ charset = checkCharset(charset);
+
+ let stream = outputStream;
+
+ // Buffer outputStream if it's not already.
+ let ioUtils = Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil);
+ if (!ioUtils.outputStreamIsBuffered(outputStream)) {
+ stream = Cc["@mozilla.org/network/buffered-output-stream;1"].
+ createInstance(Ci.nsIBufferedOutputStream);
+ stream.init(outputStream, BUFFER_BYTE_LEN);
+ }
+
+ // I'd like to use nsIConverterOutputStream. But NetUtil.asyncCopy(), which
+ // we use below in writeAsync(), naturally expects its sink to be an instance
+ // of nsIOutputStream, which nsIConverterOutputStream's only implementation is
+ // not. So we use uconv and manually convert all strings before writing to
+ // outputStream.
+ let uconv = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Ci.nsIScriptableUnicodeConverter);
+ uconv.charset = charset;
+
+ let manager = new StreamManager(this, stream);
+
+ /**
+ * Flushes the backing stream's buffer.
+ */
+ this.flush = function TextWriter_flush() {
+ manager.ensureOpened();
+ stream.flush();
+ };
+
+ /**
+ * Writes a string to the stream. If the stream is closed, an exception is
+ * thrown.
+ *
+ * @param str
+ * The string to write.
+ */
+ this.write = function TextWriter_write(str) {
+ manager.ensureOpened();
+ let istream = uconv.convertToInputStream(str);
+ let len = istream.available();
+ while (len > 0) {
+ stream.writeFrom(istream, len);
+ len = istream.available();
+ }
+ istream.close();
+ };
+
+ /**
+ * Writes a string on a background thread. After the write completes, the
+ * backing stream's buffer is flushed, and both the stream and the backing
+ * stream are closed, also on the background thread. If the stream is already
+ * closed, an exception is thrown immediately.
+ *
+ * @param str
+ * The string to write.
+ * @param callback
+ * An optional function. If given, it's called as callback(error) when
+ * the write completes. error is an Error object or undefined if there
+ * was no error. Inside callback, |this| is the stream object.
+ */
+ this.writeAsync = function TextWriter_writeAsync(str, callback) {
+ manager.ensureOpened();
+ let istream = uconv.convertToInputStream(str);
+ NetUtil.asyncCopy(istream, stream, function (result) {
+ let err = components.isSuccessCode(result) ? undefined :
+ new Error("An error occured while writing to the stream: " + result);
+ if (err)
+ console.error(err);
+
+ // asyncCopy() closes its output (and input) stream.
+ manager.opened = false;
+
+ if (typeof(callback) === "function") {
+ try {
+ callback.call(self, err);
+ }
+ catch (exc) {
+ console.exception(exc);
+ }
+ }
+ });
+ };
+}
+
+// This manages the lifetime of stream, a TextReader or TextWriter. It defines
+// closed and close() on stream and registers an unload listener that closes
+// rawStream if it's still opened. It also provides ensureOpened(), which
+// throws an exception if the stream is closed.
+function StreamManager(stream, rawStream) {
+ const self = this;
+ this.rawStream = rawStream;
+ this.opened = true;
+
+ /**
+ * True iff the stream is closed.
+ */
+ stream.__defineGetter__("closed", function stream_closed() {
+ return !self.opened;
+ });
+
+ /**
+ * Closes both the stream and its backing stream. If the stream is already
+ * closed, an exception is thrown. For TextWriters, this first flushes the
+ * backing stream's buffer.
+ */
+ stream.close = function stream_close() {
+ self.ensureOpened();
+ self.unload();
+ };
+
+ require("./unload").ensure(this);
+}
+
+StreamManager.prototype = {
+ ensureOpened: function StreamManager_ensureOpened() {
+ if (!this.opened)
+ throw new Error("The stream is closed and cannot be used.");
+ },
+ unload: function StreamManager_unload() {
+ // TextWriter.writeAsync() causes rawStream to close and therefore sets
+ // opened to false, so check that we're still opened.
+ if (this.opened) {
+ // Calling close() on both an nsIUnicharInputStream and
+ // nsIBufferedOutputStream closes their backing streams. It also forces
+ // nsIOutputStreams to flush first.
+ this.rawStream.close();
+ this.opened = false;
+ }
+ }
+};
+
+function checkCharset(charset) {
+ return typeof(charset) === "string" ? charset : DEFAULT_CHARSET;
+}
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/timer.js b/tools/addon-sdk-1.7/packages/api-utils/lib/timer.js
new file mode 100644
index 0000000..55654a8
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/timer.js
@@ -0,0 +1,78 @@
+/* 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 } = require("chrome");
+const { Unknown } = require("./xpcom");
+const { when: unload } = require("./unload");
+
+const Timer = CC('@mozilla.org/timer;1', 'nsITimer');
+
+// Registry for all timers.
+const timers = (function(map, id) {
+ return Object.defineProperties(function registry(key, fallback) {
+ return key in map ? map[key] : fallback;
+ }, {
+ register: { value: function(value) (map[++id] = value, id) },
+ unregister: { value: function(key) delete map[key] },
+ forEach: { value: function(callback) Object.keys(map).forEach(callback) }
+ });
+})(Object.create(null), 0);
+
+const TimerCallback = Unknown.extend({
+ interfaces: [ 'nsITimerCallback' ],
+ initialize: function initialize(id, callback, rest) {
+ this.id = id;
+ this.callback = callback;
+ this.arguments = rest;
+ }
+});
+
+const TimeoutCallback = TimerCallback.extend({
+ type: 0, // nsITimer.TYPE_ONE_SHOT
+ notify: function notify() {
+ try {
+ timers.unregister(this.id);
+ this.callback.apply(null, this.arguments);
+ }
+ catch (error) {
+ console.exception(error);
+ }
+ }
+});
+
+const IntervalCallback = TimerCallback.extend({
+ type: 1, // nsITimer.TYPE_REPEATING_SLACK
+ notify: function notify() {
+ try {
+ this.callback.apply(null, this.arguments);
+ }
+ catch (error) {
+ console.exception(error);
+ }
+ }
+});
+
+function setTimer(TimerCallback, listener, delay) {
+ let timer = Timer();
+ let id = timers.register(timer);
+ let callback = TimerCallback.new(id, listener, Array.slice(arguments, 3));
+ timer.initWithCallback(callback, delay || 0, TimerCallback.type);
+ return id;
+}
+
+function unsetTimer(id) {
+ let timer = timers(id);
+ timers.unregister(id);
+ if (timer)
+ timer.cancel();
+}
+
+exports.setTimeout = setTimer.bind(null, TimeoutCallback);
+exports.setInterval = setTimer.bind(null, IntervalCallback);
+exports.clearTimeout = unsetTimer;
+exports.clearInterval = unsetTimer;
+
+unload(function() { timers.forEach(unsetTimer); });
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/traceback.js b/tools/addon-sdk-1.7/packages/api-utils/lib/traceback.js
new file mode 100644
index 0000000..c55fd9d
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/traceback.js
@@ -0,0 +1,123 @@
+/* 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,components} = require("chrome");
+
+// Undo the auto-parentification of URLs done in bug 418356.
+function deParentifyURL(url) {
+ return url ? url.split(" -> ").slice(-1)[0] : url;
+}
+
+// TODO: We might want to move this function to url or some similar
+// module.
+function getLocalFile(path) {
+ var ios = Cc['@mozilla.org/network/io-service;1']
+ .getService(Ci.nsIIOService);
+ var channel = ios.newChannel(path, null, null);
+ var iStream = channel.open();
+ var siStream = Cc['@mozilla.org/scriptableinputstream;1']
+ .createInstance(Ci.nsIScriptableInputStream);
+ siStream.init(iStream);
+ var data = new String();
+ data += siStream.read(-1);
+ siStream.close();
+ iStream.close();
+ return data;
+}
+
+function safeGetFileLine(path, line) {
+ try {
+ var scheme = require("./url").URL(path).scheme;
+ // TODO: There should be an easier, more accurate way to figure out
+ // what's the case here.
+ if (!(scheme == "http" || scheme == "https"))
+ return getLocalFile(path).split("\n")[line - 1];
+ } catch (e) {}
+ return null;
+}
+
+function errorStackToJSON(stack) {
+ var lines = stack.split("\n");
+
+ var frames = [];
+ lines.forEach(
+ function(line) {
+ if (!line)
+ return;
+ var atIndex = line.indexOf("@");
+ var colonIndex = line.lastIndexOf(":");
+ var filename = deParentifyURL(line.slice(atIndex + 1, colonIndex));
+ var lineNo = parseInt(line.slice(colonIndex + 1));
+ var funcSig = line.slice(0, atIndex);
+ var funcName = funcSig.slice(0, funcSig.indexOf("("));
+ frames.unshift({filename: filename,
+ funcName: funcName,
+ lineNo: lineNo});
+ });
+
+ return frames;
+};
+
+function nsIStackFramesToJSON(frame) {
+ var stack = [];
+
+ while (frame) {
+ if (frame.filename) {
+ var filename = deParentifyURL(frame.filename);
+ stack.splice(0, 0, {filename: filename,
+ lineNo: frame.lineNumber,
+ funcName: frame.name});
+ }
+ frame = frame.caller;
+ }
+
+ return stack;
+};
+
+var fromException = exports.fromException = function fromException(e) {
+ if (e instanceof Ci.nsIException)
+ return nsIStackFramesToJSON(e.location);
+ if (e.stack && e.stack.length)
+ return errorStackToJSON(e.stack);
+ if (e.fileName && typeof(e.lineNumber == "number"))
+ return [{filename: deParentifyURL(e.fileName),
+ lineNo: e.lineNumber,
+ funcName: null}];
+ return [];
+};
+
+var get = exports.get = function get() {
+ return nsIStackFramesToJSON(components.stack.caller);
+};
+
+var format = exports.format = function format(tbOrException) {
+ if (tbOrException === undefined) {
+ tbOrException = get();
+ tbOrException.splice(-1, 1);
+ }
+
+ var tb;
+ if (typeof(tbOrException) == "object" &&
+ tbOrException.constructor.name == "Array")
+ tb = tbOrException;
+ else
+ tb = fromException(tbOrException);
+
+ var lines = ["Traceback (most recent call last):"];
+
+ tb.forEach(
+ function(frame) {
+ if (!(frame.filename || frame.lineNo || frame.funcName))
+ return;
+ lines.push(' File "' + frame.filename + '", line ' +
+ frame.lineNo + ', in ' + frame.funcName);
+ var sourceLine = safeGetFileLine(frame.filename, frame.lineNo);
+ if (sourceLine)
+ lines.push(' ' + sourceLine.trim());
+ });
+
+ return lines.join("\n");
+};
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/traits.js b/tools/addon-sdk-1.7/packages/api-utils/lib/traits.js
new file mode 100644
index 0000000..7c1c4ec
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/traits.js
@@ -0,0 +1,183 @@
+/* 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 {
+ compose: _compose,
+ override: _override,
+ resolve: _resolve,
+ trait: _trait,
+ //create: _create,
+ required,
+} = require('./traits/core');
+
+const defineProperties = Object.defineProperties,
+ freeze = Object.freeze,
+ create = Object.create;
+
+/**
+ * Work around bug 608959 by defining the _create function here instead of
+ * importing it from traits/core. For docs on this function, see the create
+ * function in that module.
+ *
+ * FIXME: remove this workaround in favor of importing the function once that
+ * bug has been fixed.
+ */
+function _create(proto, trait) {
+ let properties = {},
+ keys = Object.getOwnPropertyNames(trait);
+ for each(let key in keys) {
+ let descriptor = trait[key];
+ if (descriptor.required &&
+ !Object.prototype.hasOwnProperty.call(proto, key))
+ throw new Error('Missing required property: ' + key);
+ else if (descriptor.conflict)
+ throw new Error('Remaining conflicting property: ' + key);
+ else
+ properties[key] = descriptor;
+ }
+ return Object.create(proto, properties);
+}
+
+/**
+ * Placeholder for `Trait.prototype`
+ */
+let TraitProto = Object.prototype;
+
+function Get(key) this[key]
+function Set(key, value) this[key] = value
+
+/**
+ * Creates anonymous trait descriptor from the passed argument, unless argument
+ * is a trait constructor. In later case trait's already existing properties
+ * descriptor is returned.
+ * This is module's internal function and is used as a gateway to a trait's
+ * internal properties descriptor.
+ * @param {Function} $
+ * Composed trait's constructor.
+ * @returns {Object}
+ * Private trait property of the composition.
+ */
+function TraitDescriptor(object)
+ (
+ 'function' == typeof object &&
+ (object.prototype == TraitProto || object.prototype instanceof Trait)
+ ) ? object._trait(TraitDescriptor) : _trait(object)
+
+function Public(instance, trait) {
+ let result = {},
+ keys = Object.getOwnPropertyNames(trait);
+ for each (let key in keys) {
+ if ('_' === key.charAt(0) && '__iterator__' !== key )
+ continue;
+ let property = trait[key],
+ descriptor = {
+ configurable: property.configurable,
+ enumerable: property.enumerable
+ };
+ if (property.get)
+ descriptor.get = property.get.bind(instance);
+ if (property.set)
+ descriptor.set = property.set.bind(instance);
+ if ('value' in property) {
+ let value = property.value;
+ if ('function' === typeof value) {
+ descriptor.value = property.value.bind(instance);
+ descriptor.writable = property.writable;
+ } else {
+ descriptor.get = Get.bind(instance, key);
+ descriptor.set = Set.bind(instance, key);
+ }
+ }
+ result[key] = descriptor;
+ }
+ return result;
+}
+
+/**
+ * This is private function that composes new trait with privates.
+ */
+function Composition(trait) {
+ function Trait() {
+ let self = _create({}, trait);
+ self._public = create(Trait.prototype, Public(self, trait));
+ delete self._public.constructor;
+ if (Object === self.constructor)
+ self.constructor = Trait;
+ else
+ return self.constructor.apply(self, arguments) || self._public;
+ return self._public;
+ }
+ defineProperties(Trait, {
+ prototype: { value: freeze(create(TraitProto, {
+ constructor: { value: constructor, writable: true }
+ }))}, // writable is `true` to avoid getters in custom ES5
+ displayName: { value: (trait.constructor || constructor).name },
+ compose: { value: compose, enumerable: true },
+ override: { value: override, enumerable: true },
+ resolve: { value: resolve, enumerable: true },
+ required: { value: required, enumerable: true },
+ _trait: { value: function _trait(caller)
+ caller === TraitDescriptor ? trait : undefined
+ }
+ });
+ return freeze(Trait);
+}
+
+/**
+ * Composes new trait out of itself and traits / property maps passed as an
+ * arguments. If two or more traits / property maps have properties with the
+ * same name, the new trait will contain a "conflict" property for that name.
+ * This is a commutative and associative operation, and the order of its
+ * arguments is not significant.
+ * @params {Object|Function}
+ * List of Traits or property maps to create traits from.
+ * @returns {Function}
+ * New trait containing the combined properties of all the traits.
+ */
+function compose() {
+ let traits = Array.slice(arguments, 0);
+ traits.push(this);
+ return Composition(_compose.apply(null, traits.map(TraitDescriptor)));
+}
+
+/**
+ * Composes a new trait with all of the combined properties of `this` and the
+ * argument traits. In contrast to `compose`, `override` immediately resolves
+ * all conflicts resulting from this composition by overriding the properties of
+ * later traits. Trait priority is from left to right. I.e. the properties of
+ * the leftmost trait are never overridden.
+ * @params {Object} trait
+ * @returns {Object}
+ */
+function override() {
+ let traits = Array.slice(arguments, 0);
+ traits.push(this);
+ return Composition(_override.apply(null, traits.map(TraitDescriptor)));
+}
+
+/**
+ * Composes new resolved trait, with all the same properties as this
+ * trait, except that all properties whose name is an own property of
+ * `resolutions` will be renamed to `resolutions[name]`. If it is
+ * `resolutions[name]` is `null` value is changed into a required property
+ * descriptor.
+ */
+function resolve(resolutions)
+ Composition(_resolve(resolutions, TraitDescriptor(this)))
+
+/**
+ * Base Trait, that all the traits are composed of.
+ */
+const Trait = Composition({
+ /**
+ * Internal property holding public API of this instance.
+ */
+ _public: { value: null, configurable: true, writable: true },
+ toString: { value: function() '[object ' + this.constructor.name + ']' }
+});
+TraitProto = Trait.prototype;
+exports.Trait = Trait;
+
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/traits/core.js b/tools/addon-sdk-1.7/packages/api-utils/lib/traits/core.js
new file mode 100644
index 0000000..6e802c7
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/traits/core.js
@@ -0,0 +1,317 @@
+/* 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";
+// Design inspired by: http://www.traitsjs.org/
+
+// shortcuts
+const getOwnPropertyNames = Object.getOwnPropertyNames,
+ getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor,
+ hasOwn = Object.prototype.hasOwnProperty,
+ _create = Object.create;
+
+function doPropertiesMatch(object1, object2, name) {
+ // If `object1` has property with the given `name`
+ return name in object1 ?
+ // then `object2` should have it with the same value.
+ name in object2 && object1[name] === object2[name] :
+ // otherwise `object2` should not have property with the given `name`.
+ !(name in object2);
+}
+
+/**
+ * Compares two trait custom property descriptors if they are the same. If
+ * both are `conflict` or all the properties of descriptor are equal returned
+ * value will be `true`, otherwise it will be `false`.
+ * @param {Object} desc1
+ * @param {Object} desc2
+ */
+function areSame(desc1, desc2) {
+ return ('conflict' in desc1 && desc1.conflict &&
+ 'conflict' in desc2 && desc2.conflict) ||
+ (doPropertiesMatch(desc1, desc2, 'get') &&
+ doPropertiesMatch(desc1, desc2, 'set') &&
+ doPropertiesMatch(desc1, desc2, 'value') &&
+ doPropertiesMatch(desc1, desc2, 'enumerable') &&
+ doPropertiesMatch(desc1, desc2, 'required') &&
+ doPropertiesMatch(desc1, desc2, 'conflict'));
+}
+
+/**
+ * Converts array to an object whose own property names represent
+ * values of array.
+ * @param {String[]} names
+ * @returns {Object}
+ * @example
+ * Map(['foo', ...]) => { foo: true, ...}
+ */
+function Map(names) {
+ let map = {};
+ for each (let name in names)
+ map[name] = true;
+ return map;
+}
+
+
+const ERR_CONFLICT = 'Remaining conflicting property: ',
+ ERR_REQUIRED = 'Missing required property: ';
+/**
+ * Constant singleton, representing placeholder for required properties.
+ * @type {Object}
+ */
+const required = { toString: function()'<Trait.required>' };
+exports.required = required;
+
+/**
+ * Generates custom **required** property descriptor. Descriptor contains
+ * non-standard property `required` that is equal to `true`.
+ * @param {String} name
+ * property name to generate descriptor for.
+ * @returns {Object}
+ * custom property descriptor
+ */
+function Required(name) {
+ function required() { throw new Error(ERR_REQUIRED + name) }
+ return {
+ get: required,
+ set: required,
+ required: true
+ };
+}
+
+/**
+ * Generates custom **conflicting** property descriptor. Descriptor contains
+ * non-standard property `conflict` that is equal to `true`.
+ * @param {String} name
+ * property name to generate descriptor for.
+ * @returns {Object}
+ * custom property descriptor
+ */
+function Conflict(name) {
+ function conflict() { throw new Error(ERR_CONFLICT + name) }
+ return {
+ get: conflict,
+ set: conflict,
+ conflict: true
+ };
+}
+
+/**
+ * Function generates custom properties descriptor of the `object`s own
+ * properties. All the inherited properties are going to be ignored.
+ * Properties with values matching `required` singleton will be marked as
+ * 'required' properties.
+ * @param {Object} object
+ * Set of properties to generate trait from.
+ * @returns {Object}
+ * Properties descriptor of all of the `object`'s own properties.
+ */
+function trait(properties) {
+ let result = {},
+ keys = getOwnPropertyNames(properties);
+ for each (let key in keys) {
+ let descriptor = getOwnPropertyDescriptor(properties, key);
+ result[key] = (required === descriptor.value) ? Required(key) : descriptor;
+ }
+ return result;
+}
+exports.Trait = exports.trait = trait;
+
+/**
+ * Composes new trait. If two or more traits have own properties with the
+ * same name, the new trait will contain a 'conflict' property for that name.
+ * 'compose' is a commutative and associative operation, and the order of its
+ * arguments is not significant.
+ *
+ * @params {Object} trait
+ * Takes traits as an arguments
+ * @returns {Object}
+ * New trait containing the combined own properties of all the traits.
+ * @example
+ * var newTrait = compose(trait_1, trait_2, ..., trait_N);
+ */
+function compose(trait1, trait2) {
+ let traits = Array.slice(arguments, 0),
+ result = {};
+ for each (let trait in traits) {
+ let keys = getOwnPropertyNames(trait);
+ for each (let key in keys) {
+ let descriptor = trait[key];
+ // if property already exists and it's not a requirement
+ if (hasOwn.call(result, key) && !result[key].required) {
+ if (descriptor.required)
+ continue;
+ if (!areSame(descriptor, result[key]))
+ result[key] = Conflict(key);
+ } else {
+ result[key] = descriptor;
+ }
+ }
+ }
+ return result;
+}
+exports.compose = compose;
+
+/**
+ * Composes new trait with the same own properties as the original trait,
+ * except that all property names appearing in the first argument are replaced
+ * by 'required' property descriptors.
+ * @param {String[]} keys
+ * Array of strings property names.
+ * @param {Object} trait
+ * A trait some properties of which should be excluded.
+ * @returns {Object}
+ * @example
+ * var newTrait = exclude(['name', ...], trait)
+ */
+function exclude(keys, trait) {
+ let exclusions = Map(keys),
+ result = {};
+
+ keys = getOwnPropertyNames(trait);
+
+ for each (let key in keys) {
+ if (!hasOwn.call(exclusions, key) || trait[key].required)
+ result[key] = trait[key];
+ else
+ result[key] = Required(key);
+ }
+ return result;
+}
+
+/**
+ * Composes a new trait with all of the combined properties of the argument
+ * traits. In contrast to `compose`, `override` immediately resolves all
+ * conflicts resulting from this composition by overriding the properties of
+ * later traits. Trait priority is from left to right. I.e. the properties of
+ * the leftmost trait are never overridden.
+ * @params {Object} trait
+ * @returns {Object}
+ * @examples
+ * // override is associative:
+ * override(t1,t2,t3)
+ * // is equivalent to
+ * override(t1, override(t2, t3))
+ * // or
+ * to override(override(t1, t2), t3)
+ *
+ * // override is not commutative:
+ * override(t1,t2)
+ * // is not equivalent to
+ * override(t2,t1)
+ */
+function override() {
+ let traits = Array.slice(arguments, 0),
+ result = {};
+ for each (let trait in traits) {
+ let keys = getOwnPropertyNames(trait);
+ for each(let key in keys) {
+ let descriptor = trait[key];
+ if (!hasOwn.call(result, key) || result[key].required)
+ result[key] = descriptor;
+ }
+ }
+ return result;
+}
+exports.override = override;
+
+/**
+ * Composes a new trait with the same properties as the original trait, except
+ * that all properties whose name is an own property of map will be renamed to
+ * map[name], and a 'required' property for name will be added instead.
+ * @param {Object} map
+ * An object whose own properties serve as a mapping from old names to new
+ * names.
+ * @param {Object} trait
+ * A trait object
+ * @returns {Object}
+ * @example
+ * var newTrait = rename(map, trait);
+ */
+function rename(map, trait) {
+ let result = {},
+ keys = getOwnPropertyNames(trait);
+ for each(let key in keys) {
+ // must be renamed & it's not requirement
+ if (hasOwn.call(map, key) && !trait[key].required) {
+ let alias = map[key];
+ if (hasOwn.call(result, alias) && !result[alias].required)
+ result[alias] = Conflict(alias);
+ else
+ result[alias] = trait[key];
+ if (!hasOwn.call(result, key))
+ result[key] = Required(key);
+ } else { // must not be renamed or its a requirement
+ // property is not in result trait yet
+ if (!hasOwn.call(result, key))
+ result[key] = trait[key];
+ // property is already in resulted trait & it's not requirement
+ else if (!trait[key].required)
+ result[key] = Conflict(key);
+ }
+ }
+ return result;
+}
+
+/**
+* Composes new resolved trait, with all the same properties as the original
+* trait, except that all properties whose name is an own property of
+* resolutions will be renamed to `resolutions[name]`. If it is
+* `resolutions[name]` is `null` value is changed into a required property
+* descriptor.
+* function can be implemented as `rename(map,exclude(exclusions, trait))`
+* where map is the subset of mappings from oldName to newName and exclusions
+* is an array of all the keys that map to `null`.
+* Note: it's important to **first** `exclude`, **then** `rename`, since
+* `exclude` and rename are not associative.
+* @param {Object} resolutions
+* An object whose own properties serve as a mapping from old names to new
+* names, or to `null` if the property should be excluded.
+* @param {Object} trait
+* A trait object
+* @returns {Object}
+* Resolved trait with the same own properties as the original trait.
+*/
+function resolve(resolutions, trait) {
+ let renames = {},
+ exclusions = [],
+ keys = getOwnPropertyNames(resolutions);
+ for each (let key in keys) { // pre-process renamed and excluded properties
+ if (resolutions[key]) // old name -> new name
+ renames[key] = resolutions[key];
+ else // name -> undefined
+ exclusions.push(key);
+ }
+ return rename(renames, exclude(exclusions, trait));
+}
+exports.resolve = resolve;
+
+/**
+ * `create` is like `Object.create`, except that it ensures that:
+ * - an exception is thrown if 'trait' still contains required properties
+ * - an exception is thrown if 'trait' still contains conflicting
+ * properties
+ * @param {Object}
+ * prototype of the completed object
+ * @param {Object} trait
+ * trait object to be turned into a complete object
+ * @returns {Object}
+ * An object with all of the properties described by the trait.
+ */
+function create(proto, trait) {
+ let properties = {},
+ keys = getOwnPropertyNames(trait);
+ for each(let key in keys) {
+ let descriptor = trait[key];
+ if (descriptor.required && !hasOwn.call(proto, key))
+ throw new Error(ERR_REQUIRED + key);
+ else if (descriptor.conflict)
+ throw new Error(ERR_CONFLICT + key);
+ else
+ properties[key] = descriptor;
+ }
+ return _create(proto, properties);
+}
+exports.create = create;
+
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/type.js b/tools/addon-sdk-1.7/packages/api-utils/lib/type.js
new file mode 100644
index 0000000..56df14e
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/type.js
@@ -0,0 +1,340 @@
+/* vim:ts=2:sts=2:sw=2:
+ * 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";
+
+/**
+ * Returns `true` if `value` is `undefined`.
+ * @examples
+ * var foo; isUndefined(foo); // true
+ * isUndefined(0); // false
+ */
+function isUndefined(value) {
+ return value === undefined;
+}
+exports.isUndefined = isUndefined;
+
+/**
+ * Returns `true` if value is `null`.
+ * @examples
+ * isNull(null); // true
+ * isNull(undefined); // false
+ */
+function isNull(value) {
+ return value === null;
+}
+exports.isNull = isNull;
+
+/**
+ * Returns `true` if value is a string.
+ * @examples
+ * isString("moe"); // true
+ */
+function isString(value) {
+ return typeof value === "string";
+}
+exports.isString = isString;
+
+/**
+ * Returns `true` if `value` is a number.
+ * @examples
+ * isNumber(8.4 * 5); // true
+ */
+function isNumber(value) {
+ return typeof value === "number";
+}
+exports.isNumber = isNumber;
+
+/**
+ * Returns `true` if `value` is a `RegExp`.
+ * @examples
+ * isRegExp(/moe/); // true
+ */
+function isRegExp(value) {
+ return isObject(value) && instanceOf(value, RegExp);
+}
+exports.isRegExp = isRegExp;
+
+/**
+ * Returns true if `value` is a `Date`.
+ * @examples
+ * isDate(new Date()); // true
+ */
+function isDate(value) {
+ return isObject(value) && instanceOf(value, Date);
+}
+exports.isDate = isDate;
+
+/**
+ * Returns true if object is a Function.
+ * @examples
+ * isFunction(function foo(){}) // true
+ */
+function isFunction(value) {
+ return typeof value === "function";
+}
+exports.isFunction = isFunction;
+
+/**
+ * Returns `true` if `value` is an object (please note that `null` is considered
+ * to be an atom and not an object).
+ * @examples
+ * isObject({}) // true
+ * isObject(null) // false
+ */
+function isObject(value) {
+ return typeof value === "object" && value !== null;
+}
+exports.isObject = isObject;
+
+/**
+ * Returns true if `value` is an Array.
+ * @examples
+ * isArray([1, 2, 3]) // true
+ * isArray({ 0: 'foo', length: 1 }) // false
+ */
+var isArray = Array.isArray || function isArray(value) {
+ Object.prototype.toString.call(value) === "[object Array]";
+}
+exports.isArray = isArray;
+
+/**
+ * Returns `true` if `value` is an Arguments object.
+ * @examples
+ * (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;
+
+/**
+ * Returns true if it is a primitive `value`. (null, undefined, number,
+ * boolean, string)
+ * @examples
+ * isPrimitive(3) // true
+ * isPrimitive('foo') // true
+ * isPrimitive({ bar: 3 }) // false
+ */
+function isPrimitive(value) {
+ return !isFunction(value) && !isObject(value);
+}
+exports.isPrimitive = isPrimitive;
+
+/**
+ * Returns `true` if given `object` is flat (it is direct decedent of
+ * `Object.prototype` or `null`).
+ * @examples
+ * isFlat({}) // true
+ * isFlat(new Type()) // false
+ */
+function isFlat(object) {
+ return isObject(object) && (isNull(Object.getPrototypeOf(object)) ||
+ isNull(Object.getPrototypeOf(
+ Object.getPrototypeOf(object))));
+}
+exports.isFlat = isFlat;
+
+/**
+ * Returns `true` if object contains no values.
+ */
+function isEmpty(object) {
+ if (isObject(object)) {
+ for (var key in object)
+ return false;
+ return true;
+ }
+ return false;
+}
+exports.isEmpty = isEmpty;
+
+/**
+ * Returns `true` if `value` is an array / flat object containing only atomic
+ * values and other flat objects.
+ */
+function isJSON(value, visited) {
+ // Adding value to array of visited values.
+ (visited || (visited = [])).push(value);
+ // If `value` is an atom return `true` cause it's valid JSON.
+ return isPrimitive(value) ||
+ // If `value` is an array of JSON values that has not been visited
+ // yet.
+ (isArray(value) && value.every(function(element) {
+ return isJSON(element, visited);
+ })) ||
+ // If `value` is a plain object containing properties with a JSON
+ // values it's a valid JSON.
+ (isFlat(value) && Object.keys(value).every(function(key) {
+ var $ = Object.getOwnPropertyDescriptor(value, key);
+ // Check every proprety of a plain object to verify that
+ // it's neither getter nor setter, but a JSON value, that
+ // has not been visited yet.
+ return ((!isObject($.value) || !~visited.indexOf($.value)) &&
+ !('get' in $) && !('set' in $) &&
+ isJSON($.value, visited));
+ }));
+}
+exports.isJSON = function (value) {
+ return isJSON(value);
+};
+
+/**
+ * Returns if `value` is an instance of a given `Type`. This is exactly same as
+ * `value instanceof Type` with a difference that `Type` can be from a scope
+ * that has a different top level object. (Like in case where `Type` is a
+ * function from different iframe / jetpack module / sandbox).
+ */
+function instanceOf(value, Type) {
+ var isConstructorNameSame;
+ var isConstructorSourceSame;
+
+ // If `instanceof` returned `true` we know result right away.
+ var isInstanceOf = value instanceof Type;
+
+ // If `instanceof` returned `false` we do ducktype check since `Type` may be
+ // from a different sandbox. If a constructor of the `value` or a constructor
+ // of the value's prototype has same name and source we assume that it's an
+ // instance of the Type.
+ if (!isInstanceOf && value) {
+ isConstructorNameSame = value.constructor.name === Type.name;
+ isConstructorSourceSame = String(value.constructor) == String(Type);
+ isInstanceOf = (isConstructorNameSame && isConstructorSourceSame) ||
+ instanceOf(Object.getPrototypeOf(value), Type);
+ }
+ return isInstanceOf;
+}
+exports.instanceOf = instanceOf;
+
+/**
+ * Function returns textual representation of a value passed to it. Function
+ * takes additional `indent` argument that is used for indentation. Also
+ * optional `limit` argument may be passed to limit amount of detail returned.
+ * @param {Object} value
+ * @param {String} [indent=" "]
+ * @param {Number} [limit]
+ */
+function source(value, indent, limit, offset, visited) {
+ var result;
+ var names;
+ var nestingIndex;
+ var isCompact = !isUndefined(limit);
+
+ indent = indent || " ";
+ offset = (offset || "");
+ result = "";
+ visited = visited || [];
+
+ if (isUndefined(value)) {
+ result += "undefined";
+ }
+ else if (isNull(value)) {
+ result += "null";
+ }
+ else if (isString(value)) {
+ result += '"' + value + '"';
+ }
+ else if (isFunction(value)) {
+ value = String(value).split("\n");
+ if (isCompact && value.length > 2) {
+ value = value.splice(0, 2);
+ value.push("...}");
+ }
+ result += value.join("\n" + offset);
+ }
+ else if (isArray(value)) {
+ if ((nestingIndex = (visited.indexOf(value) + 1))) {
+ result = "#" + nestingIndex + "#";
+ }
+ else {
+ visited.push(value);
+
+ if (isCompact)
+ value = value.slice(0, limit);
+
+ result += "[\n";
+ result += value.map(function(value) {
+ return offset + indent + source(value, indent, limit, offset + indent,
+ visited);
+ }).join(",\n");
+ result += isCompact && value.length > limit ?
+ ",\n" + offset + "...]" : "\n" + offset + "]";
+ }
+ }
+ else if (isObject(value)) {
+ if ((nestingIndex = (visited.indexOf(value) + 1))) {
+ result = "#" + nestingIndex + "#"
+ }
+ else {
+ visited.push(value)
+
+ names = Object.keys(value);
+
+ result += "{ // " + value + "\n";
+ result += (isCompact ? names.slice(0, limit) : names).map(function(name) {
+ var _limit = isCompact ? limit - 1 : limit;
+ var descriptor = Object.getOwnPropertyDescriptor(value, name);
+ var result = offset + indent + "// ";
+ var accessor;
+ if (0 <= name.indexOf(" "))
+ name = '"' + name + '"';
+
+ if (descriptor.writable)
+ result += "writable ";
+ if (descriptor.configurable)
+ result += "configurable ";
+ if (descriptor.enumerable)
+ result += "enumerable ";
+
+ result += "\n";
+ if ("value" in descriptor) {
+ result += offset + indent + name + ": ";
+ result += source(descriptor.value, indent, _limit, indent + offset,
+ visited);
+ }
+ else {
+
+ if (descriptor.get) {
+ result += offset + indent + "get " + name + " ";
+ accessor = source(descriptor.get, indent, _limit, indent + offset,
+ visited);
+ result += accessor.substr(accessor.indexOf("{"));
+ }
+
+ if (descriptor.set) {
+ result += offset + indent + "set " + name + " ";
+ accessor = source(descriptor.set, indent, _limit, indent + offset,
+ visited);
+ result += accessor.substr(accessor.indexOf("{"));
+ }
+ }
+ return result;
+ }).join(",\n");
+
+ if (isCompact) {
+ if (names.length > limit && limit > 0) {
+ result += ",\n" + offset + indent + "//...";
+ }
+ }
+ else {
+ if (names.length)
+ result += ",";
+
+ result += "\n" + offset + indent + '"__proto__": ';
+ result += source(Object.getPrototypeOf(value), indent, 0,
+ offset + indent);
+ }
+
+ result += "\n" + offset + "}";
+ }
+ }
+ else {
+ result += String(value);
+ }
+ return result;
+}
+exports.source = function (value, indentation, limit) {
+ return source(value, indentation, limit);
+};
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/unit-test-finder.js b/tools/addon-sdk-1.7/packages/api-utils/lib/unit-test-finder.js
new file mode 100644
index 0000000..993749b
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/unit-test-finder.js
@@ -0,0 +1,76 @@
+/* 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 file = require("./file");
+const packaging = require('@packaging');
+const memory = require('api-utils/memory');
+const suites = packaging.allTestModules;
+
+
+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;
+};
+
+TestFinder.prototype = {
+ _makeTest: function _makeTest(suite, name, test) {
+ function runTest(runner) {
+ console.info("executing '" + suite + "." + name + "'");
+ test(runner);
+ }
+ return runTest;
+ },
+
+ findTests: function findTests(cb) {
+ var self = this;
+ var tests = [];
+ var filter;
+ // A filter string is {fileNameRegex}[:{testNameRegex}] - ie, a colon
+ // optionally separates a regex for the test fileName from a regex for the
+ // testName.
+ if (this.filter) {
+ var colonPos = this.filter.indexOf(':');
+ var filterFileRegex, filterNameRegex;
+ if (colonPos === -1) {
+ filterFileRegex = new RegExp(self.filter);
+ } else {
+ filterFileRegex = new RegExp(self.filter.substr(0, colonPos));
+ filterNameRegex = new RegExp(self.filter.substr(colonPos + 1));
+ }
+ // This function will first be called with just the filename; if
+ // it returns true the module will be loaded then the function
+ // called again with both the filename and the testname.
+ filter = function(filename, testname) {
+ return filterFileRegex.test(filename) &&
+ ((testname && filterNameRegex) ? filterNameRegex.test(testname)
+ : true);
+ };
+ } else
+ filter = function() {return true};
+
+ suites.forEach(
+ function(suite) {
+ var module = require(suite);
+ if (self.testInProcess)
+ for each (let name in Object.keys(module).sort()) {
+ if(NOT_TESTS.indexOf(name) === -1 && filter(suite, name)) {
+ tests.push({
+ setup: module.setup,
+ teardown: module.teardown,
+ testFunction: self._makeTest(suite, name, module[name]),
+ name: suite + "." + name
+ });
+ }
+ }
+ });
+
+ cb(tests);
+ }
+};
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/unit-test.js b/tools/addon-sdk-1.7/packages/api-utils/lib/unit-test.js
new file mode 100644
index 0000000..4018487
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/unit-test.js
@@ -0,0 +1,440 @@
+/* vim:st=2:sts=2:sw=2:
+ * 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 memory = require('api-utils/memory');
+var timer = require("./timer");
+
+exports.findAndRunTests = function findAndRunTests(options) {
+ var TestFinder = require("./unit-test-finder").TestFinder;
+ var finder = new TestFinder({
+ filter: options.filter,
+ testInProcess: options.testInProcess,
+ testOutOfProcess: options.testOutOfProcess
+ });
+ var runner = new TestRunner({fs: options.fs});
+ finder.findTests(
+ function (tests) {
+ runner.startMany({tests: tests,
+ stopOnError: options.stopOnError,
+ onDone: options.onDone});
+ });
+};
+
+var TestRunner = exports.TestRunner = function TestRunner(options) {
+ if (options) {
+ this.fs = options.fs;
+ }
+ this.console = (options && "console" in options) ? options.console : console;
+ memory.track(this);
+ this.passed = 0;
+ this.failed = 0;
+ this.testRunSummary = [];
+ this.expectFailNesting = 0;
+};
+
+TestRunner.prototype = {
+ toString: function toString() "[object TestRunner]",
+
+ DEFAULT_PAUSE_TIMEOUT: 10000,
+ PAUSE_DELAY: 500,
+
+ _logTestFailed: function _logTestFailed(why) {
+ this.test.errors[why]++;
+ if (!this.testFailureLogged) {
+ this.console.error("TEST FAILED: " + this.test.name + " (" + why + ")");
+ this.testFailureLogged = true;
+ }
+ },
+
+ pass: function pass(message) {
+ if(!this.expectFailure) {
+ this.console.info("pass:", message);
+ this.passed++;
+ this.test.passed++;
+ }
+ else {
+ this.expectFailure = false;
+ this.fail('Failure Expected: ' + message);
+ }
+ },
+
+ fail: function fail(message) {
+ if(!this.expectFailure) {
+ this._logTestFailed("failure");
+ this.console.error("fail:", message);
+ this.console.trace();
+ this.failed++;
+ this.test.failed++;
+ }
+ else {
+ this.expectFailure = false;
+ this.pass(message);
+ }
+ },
+
+ expectFail: function(callback) {
+ this.expectFailure = true;
+ callback();
+ this.expectFailure = false;
+ },
+
+ exception: function exception(e) {
+ this._logTestFailed("exception");
+ this.console.exception(e);
+ this.failed++;
+ this.test.failed++;
+ },
+
+ assertMatches: function assertMatches(string, regexp, message) {
+ if (regexp.test(string)) {
+ if (!message)
+ message = uneval(string) + " matches " + uneval(regexp);
+ this.pass(message);
+ } else {
+ var no = uneval(string) + " doesn't match " + uneval(regexp);
+ if (!message)
+ message = no;
+ else
+ message = message + " (" + no + ")";
+ this.fail(message);
+ }
+ },
+
+ assertRaises: function assertRaises(func, predicate, message) {
+ try {
+ func();
+ if (message)
+ this.fail(message + " (no exception thrown)");
+ else
+ this.fail("function failed to throw exception");
+ } catch (e) {
+ var errorMessage;
+ if (typeof(e) == "string")
+ errorMessage = e;
+ else
+ errorMessage = e.message;
+ if (typeof(predicate) == "string")
+ this.assertEqual(errorMessage, predicate, message);
+ else
+ this.assertMatches(errorMessage, predicate, message);
+ }
+ },
+
+ assert: function assert(a, message) {
+ if (!a) {
+ if (!message)
+ message = "assertion failed, value is " + a;
+ this.fail(message);
+ } else
+ this.pass(message || "assertion successful");
+ },
+
+ assertNotEqual: function assertNotEqual(a, b, message) {
+ if (a != b) {
+ if (!message)
+ message = "a != b != " + uneval(a);
+ this.pass(message);
+ } else {
+ var equality = uneval(a) + " == " + uneval(b);
+ if (!message)
+ message = equality;
+ else
+ message += " (" + equality + ")";
+ this.fail(message);
+ }
+ },
+
+ assertEqual: function assertEqual(a, b, message) {
+ if (a == b) {
+ if (!message)
+ message = "a == b == " + uneval(a);
+ this.pass(message);
+ } else {
+ var inequality = uneval(a) + " != " + uneval(b);
+ if (!message)
+ message = inequality;
+ else
+ message += " (" + inequality + ")";
+ this.fail(message);
+ }
+ },
+
+ assertNotStrictEqual: function assertNotStrictEqual(a, b, message) {
+ if (a !== b) {
+ if (!message)
+ message = "a !== b !== " + uneval(a);
+ this.pass(message);
+ } else {
+ var equality = uneval(a) + " === " + uneval(b);
+ if (!message)
+ message = equality;
+ else
+ message += " (" + equality + ")";
+ this.fail(message);
+ }
+ },
+
+ assertStrictEqual: function assertStrictEqual(a, b, message) {
+ if (a === b) {
+ if (!message)
+ message = "a === b === " + uneval(a);
+ this.pass(message);
+ } else {
+ var inequality = uneval(a) + " !== " + uneval(b);
+ if (!message)
+ message = inequality;
+ else
+ message += " (" + inequality + ")";
+ this.fail(message);
+ }
+ },
+
+ assertFunction: function assertFunction(a, message) {
+ this.assertStrictEqual('function', typeof a, message);
+ },
+
+ assertUndefined: function(a, message) {
+ this.assertStrictEqual('undefined', typeof a, message);
+ },
+
+ assertNotUndefined: function(a, message) {
+ this.assertNotStrictEqual('undefined', typeof a, message);
+ },
+
+ assertNull: function(a, message) {
+ this.assertStrictEqual(null, a, message);
+ },
+
+ assertNotNull: function(a, message) {
+ this.assertNotStrictEqual(null, a, message);
+ },
+
+ assertObject: function(a, message) {
+ this.assertStrictEqual('[object Object]', Object.prototype.toString.apply(a), message);
+ },
+
+ assertString: function(a, message) {
+ this.assertStrictEqual('[object String]', Object.prototype.toString.apply(a), message);
+ },
+
+ assertArray: function(a, message) {
+ this.assertStrictEqual('[object Array]', Object.prototype.toString.apply(a), message);
+ },
+
+ assertNumber: function(a, message) {
+ this.assertStrictEqual('[object Number]', Object.prototype.toString.apply(a), message);
+ },
+
+ done: function done() {
+ if (!this.isDone) {
+ this.isDone = true;
+ if(this.test.teardown) {
+ this.test.teardown(this);
+ }
+ if (this.waitTimeout !== null) {
+ timer.clearTimeout(this.waitTimeout);
+ this.waitTimeout = null;
+ }
+ if (this.test.passed == 0 && this.test.failed == 0) {
+ this._logTestFailed("empty test");
+ this.failed++;
+ this.test.failed++;
+ }
+
+ this.testRunSummary.push({
+ name: this.test.name,
+ passed: this.test.passed,
+ failed: this.test.failed,
+ errors: [error for (error in this.test.errors)].join(", ")
+ });
+
+ if (this.onDone !== null) {
+ var onDone = this.onDone;
+ var self = this;
+ this.onDone = null;
+ timer.setTimeout(function() { onDone(self); }, 0);
+ }
+ }
+ },
+
+ // Set of assertion functions to wait for an assertion to become true
+ // These functions take the same arguments as the TestRunner.assert* methods.
+ waitUntil: function waitUntil() {
+ return this._waitUntil(this.assert, arguments);
+ },
+
+ waitUntilNotEqual: function waitUntilNotEqual() {
+ return this._waitUntil(this.assertNotEqual, arguments);
+ },
+
+ waitUntilEqual: function waitUntilEqual() {
+ return this._waitUntil(this.assertEqual, arguments);
+ },
+
+ waitUntilMatches: function waitUntilMatches() {
+ return this._waitUntil(this.assertMatches, arguments);
+ },
+
+ /**
+ * Internal function that waits for an assertion to become true.
+ * @param {Function} assertionMethod
+ * Reference to a TestRunner assertion method like test.assert,
+ * test.assertEqual, ...
+ * @param {Array} args
+ * List of arguments to give to the previous assertion method.
+ * All functions in this list are going to be called to retrieve current
+ * assertion values.
+ */
+ _waitUntil: function waitUntil(assertionMethod, args) {
+ let count = 0;
+ let maxCount = this.DEFAULT_PAUSE_TIMEOUT / this.PAUSE_DELAY;
+
+ // We need to ensure that test is asynchronous
+ if (!this.waitTimeout)
+ this.waitUntilDone(this.DEFAULT_PAUSE_TIMEOUT);
+
+ let callback = null;
+ let finished = false;
+
+ let test = this;
+
+ // capture a traceback before we go async.
+ let traceback = require("./traceback");
+ let stack = traceback.get();
+ stack.splice(-2, 2);
+ let currentWaitStack = traceback.format(stack);
+ let timeout = null;
+
+ function loop(stopIt) {
+ timeout = null;
+
+ // Build a mockup object to fake TestRunner API and intercept calls to
+ // pass and fail methods, in order to retrieve nice error messages
+ // and assertion result
+ let mock = {
+ pass: function (msg) {
+ test.pass(msg);
+ test.waitUntilCallback = null;
+ if (callback && !stopIt)
+ callback();
+ finished = true;
+ },
+ fail: function (msg) {
+ // If we are called on test timeout, we stop the loop
+ // and print which test keeps failing:
+ if (stopIt) {
+ test.console.error("test assertion never became true:\n",
+ msg + "\n",
+ currentWaitStack);
+ if (timeout)
+ timer.clearTimeout(timeout);
+ return;
+ }
+ timeout = timer.setTimeout(loop, test.PAUSE_DELAY);
+ }
+ };
+
+ // Automatically call args closures in order to build arguments for
+ // assertion function
+ let appliedArgs = [];
+ for (let i = 0, l = args.length; i < l; i++) {
+ let a = args[i];
+ if (typeof a == "function") {
+ try {
+ a = a();
+ }
+ catch(e) {
+ test.fail("Exception when calling asynchronous assertion: " + e);
+ finished = true;
+ return;
+ }
+ }
+ appliedArgs.push(a);
+ }
+
+ // Finally call assertion function with current assertion values
+ assertionMethod.apply(mock, appliedArgs);
+ }
+ loop();
+ this.waitUntilCallback = loop;
+
+ // Return an object with `then` method, to offer a way to execute
+ // some code when the assertion passed or failed
+ return {
+ then: function (c) {
+ callback = c;
+
+ // In case of immediate positive result, we need to execute callback
+ // immediately here:
+ if (finished)
+ callback();
+ }
+ };
+ },
+
+ waitUntilDone: function waitUntilDone(ms) {
+ if (ms === undefined)
+ ms = this.DEFAULT_PAUSE_TIMEOUT;
+
+ var self = this;
+
+ function tiredOfWaiting() {
+ self._logTestFailed("timed out");
+ if (self.waitUntilCallback) {
+ self.waitUntilCallback(true);
+ self.waitUntilCallback = null;
+ }
+ self.failed++;
+ self.test.failed++;
+ self.done();
+ }
+
+ // We may already have registered a timeout callback
+ if (this.waitTimeout)
+ timer.clearTimeout(this.waitTimeout);
+
+ this.waitTimeout = timer.setTimeout(tiredOfWaiting, ms);
+ },
+
+ startMany: function startMany(options) {
+ function runNextTest(self) {
+ var test = options.tests.shift();
+ if (options.stopOnError && self.test && self.test.failed) {
+ self.console.error("aborted: test failed and --stop-on-error was specified");
+ options.onDone(self);
+ } else if (test) {
+ self.start({test: test, onDone: runNextTest});
+ } else {
+ options.onDone(self);
+ }
+ }
+ runNextTest(this);
+ },
+
+ start: function start(options) {
+ this.test = options.test;
+ this.test.passed = 0;
+ this.test.failed = 0;
+ this.test.errors = {};
+
+ this.isDone = false;
+ this.onDone = options.onDone;
+ this.waitTimeout = null;
+ this.testFailureLogged = false;
+
+ try {
+ if(this.test.setup) {
+ this.test.setup(this);
+ }
+ this.test.testFunction(this);
+ } catch (e) {
+ this.exception(e);
+ }
+ if (this.waitTimeout === null)
+ this.done();
+ }
+};
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/unload.js b/tools/addon-sdk-1.7/packages/api-utils/lib/unload.js
new file mode 100644
index 0000000..aba5923
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/unload.js
@@ -0,0 +1,63 @@
+/* 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/. */
+
+// Parts of this module were taken from narwhal:
+//
+// http://narwhaljs.org
+
+var observers = [];
+var unloaders = [];
+
+var when = exports.when = function when(observer) {
+ if (observers.indexOf(observer) != -1)
+ return;
+ observers.unshift(observer);
+};
+
+var send = exports.send = function send(reason, onError) {
+ onError = onError || console.exception;
+ observers.forEach(function (observer) {
+ try {
+ observer(reason);
+ } catch (e) {
+ onError(e);
+ }
+ });
+};
+
+var ensure = exports.ensure = function ensure(obj, destructorName) {
+ if (!destructorName)
+ destructorName = "unload";
+ if (!(destructorName in obj))
+ throw new Error("object has no '" + destructorName + "' property");
+
+ let called = false;
+ let originalDestructor = obj[destructorName];
+
+ function unloadWrapper(reason) {
+ if (!called) {
+ called = true;
+ let index = unloaders.indexOf(unloadWrapper);
+ if (index == -1)
+ throw new Error("internal error: unloader not found");
+ unloaders.splice(index, 1);
+ originalDestructor.call(obj, reason);
+ originalDestructor = null;
+ destructorName = null;
+ obj = null;
+ }
+ };
+
+ unloaders.push(unloadWrapper);
+
+ obj[destructorName] = unloadWrapper;
+};
+
+when(
+ function(reason) {
+ unloaders.slice().forEach(
+ function(unloadWrapper) {
+ unloadWrapper(reason);
+ });
+ });
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/url.js b/tools/addon-sdk-1.7/packages/api-utils/lib/url.js
new file mode 100644
index 0000000..29fc12c
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/url.js
@@ -0,0 +1,113 @@
+/* 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,Cr} = require("chrome");
+
+var ios = Cc['@mozilla.org/network/io-service;1']
+ .getService(Ci.nsIIOService);
+
+var resProt = ios.getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler);
+
+function newURI(uriStr, base) {
+ try {
+ let baseURI = base ? ios.newURI(base, null, null) : null;
+ return ios.newURI(uriStr, null, baseURI);
+ }
+ catch (e if e.result == Cr.NS_ERROR_MALFORMED_URI) {
+ throw new Error("malformed URI: " + uriStr);
+ }
+ catch (e if (e.result == Cr.NS_ERROR_FAILURE ||
+ e.result == Cr.NS_ERROR_ILLEGAL_VALUE)) {
+ throw new Error("invalid URI: " + uriStr);
+ }
+}
+
+function resolveResourceURI(uri) {
+ var resolved;
+ try {
+ resolved = resProt.resolveURI(uri);
+ } catch (e if e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
+ throw new Error("resource does not exist: " + uri.spec);
+ };
+ return resolved;
+}
+
+let fromFilename = exports.fromFilename = function fromFilename(path) {
+ var file = Cc['@mozilla.org/file/local;1']
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(path);
+ return ios.newFileURI(file).spec;
+};
+
+let toFilename = exports.toFilename = function toFilename(url) {
+ var uri = newURI(url);
+ if (uri.scheme == "resource")
+ uri = newURI(resolveResourceURI(uri));
+ if (uri.scheme == "chrome") {
+ var channel = ios.newChannelFromURI(uri);
+ try {
+ channel = channel.QueryInterface(Ci.nsIFileChannel);
+ return channel.file.path;
+ } catch (e if e.result == Cr.NS_NOINTERFACE) {
+ throw new Error("chrome url isn't on filesystem: " + url);
+ }
+ }
+ if (uri.scheme == "file") {
+ var file = uri.QueryInterface(Ci.nsIFileURL).file;
+ return file.path;
+ }
+ throw new Error("cannot map to filename: " + url);
+};
+
+function URL(url, base) {
+ if (!(this instanceof URL)) {
+ return new URL(url, base);
+ }
+
+ var uri = newURI(url, base);
+
+ var userPass = null;
+ try {
+ userPass = uri.userPass ? uri.userPass : null;
+ } catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
+
+ var host = null;
+ try {
+ host = uri.host;
+ } catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
+
+ var port = null;
+ try {
+ port = uri.port == -1 ? null : uri.port;
+ } catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
+
+ this.__defineGetter__("scheme", function() uri.scheme);
+ this.__defineGetter__("userPass", function() userPass);
+ this.__defineGetter__("host", function() host);
+ this.__defineGetter__("port", function() port);
+ this.__defineGetter__("path", function() uri.path);
+
+ Object.defineProperties(this, {
+ toString: {
+ value: function URL_toString() new String(uri.spec).toString(),
+ enumerable: false
+ },
+ valueOf: {
+ value: function() new String(uri.spec).valueOf(),
+ enumerable: false
+ },
+ toSource: {
+ value: function() new String(uri.spec).toSource(),
+ enumerable: false
+ }
+ });
+
+ return this;
+};
+
+URL.prototype = Object.create(String.prototype);
+exports.URL = URL;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/utils/data.js b/tools/addon-sdk-1.7/packages/api-utils/lib/utils/data.js
new file mode 100644
index 0000000..33356cd
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/utils/data.js
@@ -0,0 +1,71 @@
+/* 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 } = require("chrome");
+const IOService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+const AppShellService = Cc["@mozilla.org/appshell/appShellService;1"].
+ getService(Ci.nsIAppShellService);
+
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm");
+const FaviconService = Cc["@mozilla.org/browser/favicon-service;1"].
+ getService(Ci.nsIFaviconService);
+
+const PNG_B64 = "data:image/png;base64,";
+const DEF_FAVICON_URI = "chrome://mozapps/skin/places/defaultFavicon.png";
+let DEF_FAVICON = null;
+
+/**
+ * Takes URI of the page and returns associated favicon URI.
+ * If page under passed uri has no favicon then base64 encoded data URI of
+ * default faveicon is returned.
+ * @param {String} uri
+ * @returns {String}
+ */
+exports.getFaviconURIForLocation = function getFaviconURIForLocation(uri) {
+ let pageURI = NetUtil.newURI(uri);
+ try {
+ return FaviconService.getFaviconDataAsDataURL(
+ FaviconService.getFaviconForPage(pageURI));
+ }
+ catch(e) {
+ if (!DEF_FAVICON) {
+ DEF_FAVICON = PNG_B64 +
+ base64Encode(getChromeURIContent(DEF_FAVICON_URI));
+ }
+ return DEF_FAVICON;
+ }
+}
+
+/**
+ * Takes chrome URI and returns content under that URI.
+ * @param {String} chromeURI
+ * @returns {String}
+ */
+function getChromeURIContent(chromeURI) {
+ let channel = IOService.newChannel(chromeURI, null, null);
+ let input = channel.open();
+ let stream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ stream.setInputStream(input);
+ let content = stream.readBytes(input.available());
+ stream.close();
+ input.close();
+ return content;
+}
+exports.getChromeURIContent = getChromeURIContent;
+
+/**
+ * Creates a base-64 encoded ASCII string from a string of binary data.
+ */
+function base64Encode(data) AppShellService.hiddenDOMWindow.btoa(String(data));
+exports.base64Encode = base64Encode;
+
+/**
+ * Decodes a string of data which has been encoded using base-64 encoding.
+ */
+function base64Decode(data) AppShellService.hiddenDOMWindow.atob(String(data));
+exports.base64Decode = base64Decode;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/utils/object.js b/tools/addon-sdk-1.7/packages/api-utils/lib/utils/object.js
new file mode 100644
index 0000000..165aa0b
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/utils/object.js
@@ -0,0 +1,46 @@
+/* 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";
+
+/**
+ * Merges all the properties of all arguments into first argument. If two or
+ * more argument objects have own properties with the same name, the property
+ * is overridden, with precedence from right to left, implying, that properties
+ * of the object on the left are overridden by a same named property of the
+ * object on the right.
+ * @examples
+ * var a = { bar: 0, a: 'a' }
+ * var b = merge(a, { foo: 'foo', bar: 1 }, { foo: 'bar', name: 'b' });
+ * b === a // true
+ * b.a // 'a'
+ * b.foo // 'bar'
+ * b.bar // 1
+ * b.name // 'b'
+ */
+function merge(source) {
+ let descriptor = {};
+ Array.slice(arguments, 1).forEach(function onEach(properties) {
+ Object.getOwnPropertyNames(properties).forEach(function(name) {
+ descriptor[name] = Object.getOwnPropertyDescriptor(properties, name);
+ });
+ });
+ return Object.defineProperties(source, descriptor);
+}
+exports.merge = merge;
+
+/**
+ * Returns an object that inherits from the first argument and contains all the
+ * properties from all following arguments.
+ * `extend(source1, source2, source3)` is equivalent of
+ * `merge(Object.create(source1), source2, source3)`.
+ */
+function extend(source) {
+ let rest = Array.slice(arguments, 1);
+ rest.unshift(Object.create(source));
+ return merge.apply(null, rest);
+}
+exports.extend = extend;
+
+
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/utils/registry.js b/tools/addon-sdk-1.7/packages/api-utils/lib/utils/registry.js
new file mode 100644
index 0000000..2ce6a6e
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/utils/registry.js
@@ -0,0 +1,57 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { EventEmitter } = require('../events');
+const unload = require('../unload');
+
+const Registry = EventEmitter.compose({
+ _registry: null,
+ _constructor: null,
+ constructor: function Registry(constructor) {
+ this._registry = [];
+ this._constructor = constructor;
+ this.on('error', this._onError = this._onError.bind(this));
+ unload.ensure(this, "_destructor");
+ },
+ _destructor: function _destructor() {
+ let _registry = this._registry.slice(0);
+ for each (let instance in _registry)
+ this._emit('remove', instance);
+ this._registry.splice(0);
+ },
+ _onError: function _onError(e) {
+ if (!this._listeners('error').length)
+ console.error(e);
+ },
+ has: function has(instance) {
+ let _registry = this._registry;
+ return (
+ (0 <= _registry.indexOf(instance)) ||
+ (instance && instance._public && 0 <= _registry.indexOf(instance._public))
+ );
+ },
+ add: function add(instance) {
+ let { _constructor, _registry } = this;
+ if (!(instance instanceof _constructor))
+ instance = new _constructor(instance);
+ if (0 > _registry.indexOf(instance)) {
+ _registry.push(instance);
+ this._emit('add', instance);
+ }
+ return instance;
+ },
+ remove: function remove(instance) {
+ let _registry = this._registry;
+ let index = _registry.indexOf(instance)
+ if (0 <= index) {
+ this._emit('remove', instance);
+ _registry.splice(index, 1);
+ }
+ }
+});
+exports.Registry = Registry;
+
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/utils/thumbnail.js b/tools/addon-sdk-1.7/packages/api-utils/lib/utils/thumbnail.js
new file mode 100644
index 0000000..dfd03dd
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/utils/thumbnail.js
@@ -0,0 +1,43 @@
+/* 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 } = require("chrome");
+const AppShellService = Cc["@mozilla.org/appshell/appShellService;1"].
+ getService(Ci.nsIAppShellService);
+
+const NS = "http://www.w3.org/1999/xhtml";
+const COLOR = "rgb(255,255,255)";
+
+/**
+ * Creates canvas element with a thumbnail of the passed window.
+ * @param {Window} window
+ * @returns {Element}
+ */
+function getThumbnailCanvasForWindow(window) {
+ let aspectRatio = 0.5625; // 16:9
+ let thumbnail = AppShellService.hiddenDOMWindow.document
+ .createElementNS(NS, "canvas");
+ thumbnail.mozOpaque = true;
+ thumbnail.width = Math.ceil(window.screen.availWidth / 5.75);
+ thumbnail.height = Math.round(thumbnail.width * aspectRatio);
+ let ctx = thumbnail.getContext("2d");
+ let snippetWidth = window.innerWidth * .6;
+ let scale = thumbnail.width / snippetWidth;
+ ctx.scale(scale, scale);
+ ctx.drawWindow(window, window.scrollX, window.scrollY, snippetWidth,
+ snippetWidth * aspectRatio, COLOR);
+ return thumbnail;
+}
+exports.getThumbnailCanvasForWindow = getThumbnailCanvasForWindow;
+
+/**
+ * Creates Base64 encoded data URI of the thumbnail for the passed window.
+ * @param {Window} window
+ * @returns {String}
+ */
+exports.getThumbnailURIForWindow = function getThumbnailURIForWindow(window) {
+ return getThumbnailCanvasForWindow(window).toDataURL()
+};
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/uuid.js b/tools/addon-sdk-1.7/packages/api-utils/lib/uuid.js
new file mode 100644
index 0000000..cd667ae
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/uuid.js
@@ -0,0 +1,13 @@
+/* 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, components: { ID: parseUUID } } = require('chrome');
+const { generateUUID } = Cc['@mozilla.org/uuid-generator;1'].
+ getService(Ci.nsIUUIDGenerator);
+
+// Returns `uuid`. If `id` is passed then it's parsed to `uuid` and returned
+// if not then new one is generated.
+exports.uuid = function uuid(id) id ? parseUUID(id) : generateUUID()
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/window-utils.js b/tools/addon-sdk-1.7/packages/api-utils/lib/window-utils.js
new file mode 100644
index 0000000..bd2f2d5
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/window-utils.js
@@ -0,0 +1,278 @@
+/* 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 } = require("chrome");
+const { EventEmitter } = require('./events'),
+ { Trait } = require('./traits');
+const { when } = require('./unload');
+const { getInnerId, getOuterId } = require('./window-utils');
+const errors = require("./errors");
+
+const windowWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher);
+const appShellService = Cc["@mozilla.org/appshell/appShellService;1"].
+ getService(Ci.nsIAppShellService);
+const observers = require('api-utils/observer-service');
+
+
+const XUL = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
+
+/**
+ * An iterator for XUL windows currently in the application.
+ *
+ * @return A generator that yields XUL windows exposing the
+ * nsIDOMWindow interface.
+ */
+function windowIterator() {
+ let winEnum = windowWatcher.getWindowEnumerator();
+ while (winEnum.hasMoreElements())
+ yield winEnum.getNext().QueryInterface(Ci.nsIDOMWindow);
+};
+exports.windowIterator = windowIterator;
+
+/**
+ * An iterator for browser windows currently open in the application.
+ * @returns {Function}
+ * A generator that yields browser windows exposing the `nsIDOMWindow`
+ * interface.
+ */
+function browserWindowIterator() {
+ for each (let window in windowIterator()) {
+ if (isBrowser(window))
+ yield window;
+ }
+}
+exports.browserWindowIterator = browserWindowIterator;
+
+function WindowTracker(delegate) {
+ if (!(this instanceof WindowTracker)) {
+ return new WindowTracker(delegate);
+ }
+
+ this._delegate = delegate;
+ this._loadingWindows = [];
+
+ for (let window in windowIterator())
+ this._regWindow(window);
+ windowWatcher.registerNotification(this);
+
+ require("./unload").ensure(this);
+
+ return this;
+};
+
+WindowTracker.prototype = {
+ _regLoadingWindow: function _regLoadingWindow(window) {
+ this._loadingWindows.push(window);
+ window.addEventListener("load", this, true);
+ },
+
+ _unregLoadingWindow: function _unregLoadingWindow(window) {
+ var index = this._loadingWindows.indexOf(window);
+
+ if (index != -1) {
+ this._loadingWindows.splice(index, 1);
+ window.removeEventListener("load", this, true);
+ }
+ },
+
+ _regWindow: function _regWindow(window) {
+ if (window.document.readyState == "complete") {
+ this._unregLoadingWindow(window);
+ this._delegate.onTrack(window);
+ } else
+ this._regLoadingWindow(window);
+ },
+
+ _unregWindow: function _unregWindow(window) {
+ if (window.document.readyState == "complete") {
+ if (this._delegate.onUntrack)
+ this._delegate.onUntrack(window);
+ } else {
+ this._unregLoadingWindow(window);
+ }
+ },
+
+ unload: function unload() {
+ windowWatcher.unregisterNotification(this);
+ for (let window in windowIterator())
+ this._unregWindow(window);
+ },
+
+ handleEvent: errors.catchAndLog(function handleEvent(event) {
+ if (event.type == "load" && event.target) {
+ var window = event.target.defaultView;
+ if (window)
+ this._regWindow(window);
+ }
+ }),
+
+ observe: errors.catchAndLog(function observe(subject, topic, data) {
+ var window = subject.QueryInterface(Ci.nsIDOMWindow);
+ if (topic == "domwindowopened")
+ this._regWindow(window);
+ else
+ this._unregWindow(window);
+ })
+};
+exports.WindowTracker = WindowTracker;
+
+const WindowTrackerTrait = Trait.compose({
+ _onTrack: Trait.required,
+ _onUntrack: Trait.required,
+ constructor: function WindowTrackerTrait() {
+ WindowTracker({
+ onTrack: this._onTrack.bind(this),
+ onUntrack: this._onUntrack.bind(this)
+ });
+ }
+});
+exports.WindowTrackerTrait = WindowTrackerTrait;
+
+var gDocsToClose = [];
+
+function onDocUnload(event) {
+ var index = gDocsToClose.indexOf(event.target);
+ if (index == -1)
+ throw new Error("internal error: unloading document not found");
+ var document = gDocsToClose.splice(index, 1)[0];
+ // Just in case, let's remove the event listener too.
+ document.defaultView.removeEventListener("unload", onDocUnload, false);
+}
+
+onDocUnload = require("./errors").catchAndLog(onDocUnload);
+
+exports.closeOnUnload = function closeOnUnload(window) {
+ window.addEventListener("unload", onDocUnload, false);
+ gDocsToClose.push(window.document);
+};
+
+Object.defineProperties(exports, {
+ activeWindow: {
+ enumerable: true,
+ get: function() {
+ return Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator)
+ .getMostRecentWindow(null);
+ },
+ set: function(window) {
+ try { window.focus(); } catch (e) { }
+ }
+ },
+ activeBrowserWindow: {
+ enumerable: true,
+ get: function() {
+ return Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator)
+ .getMostRecentWindow("navigator:browser");
+ }
+ }
+});
+
+
+/**
+ * Returns the ID of the window's current inner window.
+ */
+exports.getInnerId = function(window) {
+ console.warn('require("window-utils").getInnerId is deprecated, ' +
+ 'please use require("window/utils").getInnerId instead');
+ return getInnerId(window);
+};
+
+exports.getOuterId = function(window) {
+ console.warn('require("window-utils").getOuterId is deprecated, ' +
+ 'please use require("window/utils").getOuterId instead');
+ return getOuterId(window);
+};
+
+function isBrowser(window) {
+ return window.document.documentElement.getAttribute("windowtype") ===
+ "navigator:browser";
+};
+exports.isBrowser = isBrowser;
+
+exports.hiddenWindow = appShellService.hiddenDOMWindow;
+
+function createHiddenXULFrame() {
+ return function promise(deliver) {
+ let window = appShellService.hiddenDOMWindow;
+
+ // Ensuring waiting for hidden window end of loading
+ // (The hidden window is still loading on windows/thunderbird)
+ if (window.document.readyState != "complete") {
+ window.addEventListener("load", function onload() {
+ window.removeEventListener("load", onload, false);
+ // We recurse with same arguments, when the window is ready
+ promise(deliver);
+ }, false);
+ return;
+ }
+
+ let document = window.document;
+ let isXMLDoc = (document.contentType == "application/xhtml+xml" ||
+ document.contentType == "application/vnd.mozilla.xul+xml")
+
+ if (isXMLDoc) {
+ deliver(window)
+ }
+ else {
+ let frame = document.createElement('iframe');
+ // This is ugly but we need window for XUL document in order to create
+ // browser elements.
+
+ // See bug 725323: hiddenWindow URL is different on each mozilla product
+ let prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ let hiddenWindowURL = prefs.getCharPref("browser.hiddenWindowChromeURL", "");
+
+ frame.src = hiddenWindowURL;
+ frame.setAttribute('src', hiddenWindowURL);
+ frame.addEventListener('DOMContentLoaded', function onLoad(event) {
+ frame.removeEventListener('DOMContentLoaded', onLoad, false);
+ deliver(frame.contentWindow);
+ }, false);
+ document.documentElement.appendChild(frame);
+ }
+ }
+};
+exports.createHiddenXULFrame = createHiddenXULFrame;
+
+function createRemoteBrowser(remote) {
+ return function promise(deliver) {
+ createHiddenXULFrame()(function(hiddenWindow) {
+ let document = hiddenWindow.document;
+ let browser = document.createElementNS(XUL, "browser");
+ // Remote="true" enable everything here:
+ // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsFrameLoader.cpp#1347
+ if (remote !== false)
+ browser.setAttribute("remote","true");
+ // Type="content" is mandatory to enable stuff here:
+ // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsFrameLoader.cpp#1776
+ browser.setAttribute("type","content");
+ // We remove XBL binding to avoid execution of code that is not going to work
+ // because browser has no docShell attribute in remote mode (for example)
+ browser.setAttribute("style","-moz-binding: none;");
+ // Flex it in order to be visible (optional, for debug purpose)
+ browser.setAttribute("flex", "1");
+ document.documentElement.appendChild(browser);
+
+ // Bug 724433: do not leak this <browser> DOM node
+ when(function () {
+ document.documentElement.removeChild(browser);
+ });
+
+ // Return browser
+ deliver(browser);
+ });
+ };
+};
+exports.createRemoteBrowser = createRemoteBrowser;
+
+require("./unload").when(
+ function() {
+ gDocsToClose.slice().forEach(
+ function(doc) { doc.defaultView.close(); });
+ });
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/window/utils.js b/tools/addon-sdk-1.7/packages/api-utils/lib/window/utils.js
new file mode 100644
index 0000000..b4a8493
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/window/utils.js
@@ -0,0 +1,110 @@
+/* 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 } = require('chrome');
+
+const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1'].
+ getService(Ci.nsIWindowWatcher);
+const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
+ getService(Ci.nsIAppShellService);
+const observers = require('api-utils/observer-service');
+
+/**
+ * Returns the ID of the window's current inner window.
+ */
+function getInnerId(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
+};
+exports.getInnerId = getInnerId;
+
+/**
+ * Returns the ID of the window's outer window.
+ */
+function getOuterId(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
+};
+exports.getOuterId = getOuterId;
+
+/**
+ * Returns `nsIXULWindow` for the given `nsIDOMWindow`.
+ */
+function getXULWindow(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebNavigation).
+ QueryInterface(Ci.nsIDocShellTreeItem).
+ treeOwner.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIXULWindow);
+};
+exports.getXULWindow = getXULWindow;
+
+/**
+ * Returns `nsIBaseWindow` for the given `nsIDOMWindow`.
+ */
+function getBaseWindow(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebNavigation).
+ QueryInterface(Ci.nsIDocShell).
+ QueryInterface(Ci.nsIDocShellTreeItem).
+ treeOwner.
+ QueryInterface(Ci.nsIBaseWindow);
+}
+exports.getBaseWindow = getBaseWindow;
+
+/**
+ * Removes given window from the application's window registry. Unless
+ * `options.close` is `false` window is automatically closed on application
+ * quit.
+ * @params {nsIDOMWindow} window
+ * @params {Boolean} options.close
+ */
+function backgroundify(window, options) {
+ let base = getBaseWindow(window);
+ base.visibility = false;
+ base.enabled = false;
+ appShellService.unregisterTopLevelWindow(getXULWindow(window));
+ if (!options || options.close !== false)
+ observers.add('quit-application-granted', window.close.bind(window));
+
+ return window;
+}
+exports.backgroundify = backgroundify;
+
+/**
+ * Takes hash of options and serializes it to a features string that
+ * can be used passed to `window.open`. For more details on features string see:
+ * https://developer.mozilla.org/en/DOM/window.open#Position_and_size_features
+ */
+function serializeFeatures(options) {
+ return Object.keys(options).reduce(function(result, name) {
+ let value = options[name];
+ return result + ',' + name + '=' +
+ (value === true ? 'yes' : value === false ? 'no' : value);
+ }, '').substr(1);
+}
+
+/**
+ * Opens a top level window and returns it's `nsIDOMWindow` representation.
+ * @params {String} uri
+ * URI of the document to be loaded into window.
+ * @params {nsIDOMWindow} options.parent
+ * Used as parent for the created window.
+ * @params {String} options.name
+ * Optional name that is assigned to the window.
+ * @params {Object} options.features
+ * Map of key, values like: `{ width: 10, height: 15, chrome: true }`.
+ */
+function open(uri, options) {
+ options = options || {};
+ return windowWatcher.
+ openWindow(options.parent || null,
+ uri,
+ options.name || null,
+ serializeFeatures(options.features || {}),
+ null);
+}
+exports.open = open;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/windows/dom.js b/tools/addon-sdk-1.7/packages/api-utils/lib/windows/dom.js
new file mode 100644
index 0000000..1ff966a
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/windows/dom.js
@@ -0,0 +1,27 @@
+/* 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 { Trait } = require('../traits');
+
+const WindowDom = Trait.compose({
+ _window: Trait.required,
+ get title() {
+ let window = this._window;
+ return window && window.document ? window.document.title : null
+ },
+ close: function close() {
+ let window = this._window;
+ if (window) window.close();
+ return this._public;
+ },
+ activate: function activate() {
+ let window = this._window;
+ if (window) window.focus();
+ return this._public;
+ }
+});
+exports.WindowDom = WindowDom;
+
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/windows/loader.js b/tools/addon-sdk-1.7/packages/api-utils/lib/windows/loader.js
new file mode 100644
index 0000000..b596e73
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/windows/loader.js
@@ -0,0 +1,120 @@
+/* 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 } = require('chrome'),
+ { setTimeout } = require("../timer"),
+ { Trait } = require('../traits'),
+
+ WM = Cc['@mozilla.org/appshell/window-mediator;1'].
+ getService(Ci.nsIWindowMediator),
+
+ URI_BROWSER = 'chrome://browser/content/browser.xul',
+ NAME = '_blank',
+ FEATURES = 'chrome,all,dialog=no',
+ PARAMS = [ URI_BROWSER, NAME, FEATURES ],
+ ON_LOAD = 'load',
+ ON_UNLOAD = 'unload',
+ STATE_LOADED = 'complete',
+ BROWSER = 'navigator:browser';
+
+/**
+ * Trait provides private `_window` property and requires `_onLoad` property
+ * that will be called when `_window` is loaded. If `_window` property value
+ * is changed with already loaded window `_onLoad` still will be called.
+ */
+const WindowLoader = Trait.compose({
+ /**
+ * Internal listener that is called when window is loaded.
+ * Please keep in mind that this trait will not handle exceptions that may
+ * be thrown by this method so method itself should take care of
+ * handling them.
+ * @param {nsIWindow} window
+ */
+ _onLoad: Trait.required,
+ _tabOptions: Trait.required,
+ /**
+ * Internal listener that is called when `_window`'s DOM 'unload' event
+ * is dispatched. Please note that this trait will not handle exceptions that
+ * may be thrown by this method so method itself should take care of
+ * handling them.
+ */
+ _onUnload: Trait.required,
+ _load: function _load() {
+ if (this.__window) return;
+ let params = PARAMS.slice()
+ params.push(this._tabOptions.map(function(options) options.url).join("|"))
+ let browser = WM.getMostRecentWindow(BROWSER);
+ this._window = browser.openDialog.apply(browser, params);
+ },
+ /**
+ * Private window who's load event is being tracked. Once window is loaded
+ * `_onLoad` is called.
+ * @type {nsIWindow}
+ */
+ get _window() this.__window,
+ set _window(window) {
+ let _window = this.__window;
+ if (!window) window = null;
+ if (window !== _window) {
+ if (_window) {
+ _window.removeEventListener(ON_UNLOAD, this.__unloadListener, false);
+ _window.removeEventListener(ON_LOAD, this.__loadListener, false);
+ }
+ if (window) {
+ window.addEventListener(
+ ON_UNLOAD,
+ this.__unloadListener ||
+ (this.__unloadListener = this._unloadListener.bind(this))
+ ,
+ false
+ );
+ this.__window = window;
+ // If window is not loaded yet setting up a listener.
+ if (STATE_LOADED != window.document.readyState) {
+ window.addEventListener(
+ ON_LOAD,
+ this.__loadListener ||
+ (this.__loadListener = this._loadListener.bind(this))
+ ,
+ false
+ );
+ }
+ else { // If window is loaded calling listener next turn of event loop.
+ this._onLoad(window)
+ }
+ }
+ }
+ },
+ __window: null,
+ /**
+ * Internal method used for listening 'load' event on the `_window`.
+ * Method takes care of removing itself from 'load' event listeners once
+ * event is being handled.
+ */
+ _loadListener: function _loadListener(event) {
+ let window = this._window;
+ if (!event.target || event.target.defaultView != window) return;
+ window.removeEventListener(ON_LOAD, this.__loadListener, false);
+ this._onLoad(window);
+ },
+ __loadListener: null,
+ /**
+ * Internal method used for listening 'unload' event on the `_window`.
+ * Method takes care of removing itself from 'unload' event listeners once
+ * event is being handled.
+ */
+ _unloadListener: function _unloadListener(event) {
+ let window = this._window;
+ if (!event.target
+ || event.target.defaultView != window
+ || STATE_LOADED != window.document.readyState
+ ) return;
+ window.removeEventListener(ON_UNLOAD, this.__unloadListener, false);
+ this._onUnload(window);
+ },
+ __unloadListener: null
+});
+exports.WindowLoader = WindowLoader;
+
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/windows/observer.js b/tools/addon-sdk-1.7/packages/api-utils/lib/windows/observer.js
new file mode 100644
index 0000000..45191b7
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/windows/observer.js
@@ -0,0 +1,53 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { EventEmitterTrait: EventEmitter } = require("../events");
+const { WindowTracker, windowIterator } = require("../window-utils");
+const { DOMEventAssembler } = require("../events/assembler");
+const { Trait } = require("../light-traits");
+
+// Event emitter objects used to register listeners and emit events on them
+// when they occur.
+const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
+ /**
+ * Method is implemented by `EventEmitter` and is used just for emitting
+ * events on registered listeners.
+ */
+ _emit: Trait.required,
+ /**
+ * Events that are supported and emitted by the module.
+ */
+ supportedEventsTypes: [ "activate", "deactivate" ],
+ /**
+ * Function handles all the supported events on all the windows that are
+ * observed. Method is used to proxy events to the listeners registered on
+ * this event emitter.
+ * @param {Event} event
+ * Keyboard event being emitted.
+ */
+ handleEvent: function handleEvent(event) {
+ this._emit(event.type, event.target, event);
+ }
+});
+
+// Using `WindowTracker` to track window events.
+WindowTracker({
+ onTrack: function onTrack(chromeWindow) {
+ observer._emit("open", chromeWindow);
+ observer.observe(chromeWindow);
+ },
+ onUntrack: function onUntrack(chromeWindow) {
+ observer._emit("close", chromeWindow);
+ observer.ignore(chromeWindow);
+ }
+});
+
+// Making observer aware of already opened windows.
+for each (let window in windowIterator())
+ observer.observe(window);
+
+exports.observer = observer;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/windows/tabs.js b/tools/addon-sdk-1.7/packages/api-utils/lib/windows/tabs.js
new file mode 100644
index 0000000..231a22e
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/windows/tabs.js
@@ -0,0 +1,178 @@
+/* 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 { Trait } = require("../traits");
+const { List } = require("../list");
+const { Tab, Options } = require("../tabs/tab");
+const { EventEmitter } = require("../events");
+const { EVENTS } = require("../tabs/events");
+const { getOwnerWindow, getActiveTab, getTabs,
+ openTab, activateTab } = require("../tabs/utils");
+const { observer: tabsObserver } = require("../tabs/observer");
+
+const TAB_BROWSER = "tabbrowser";
+
+/**
+ * This is a trait that is used in composition of window wrapper. Trait tracks
+ * tab related events of the wrapped window in order to keep track of open
+ * tabs and maintain their wrappers. Every new tab gets wrapped and jetpack
+ * type event is emitted.
+ */
+const WindowTabTracker = Trait.compose({
+ /**
+ * Chrome window whose tabs are tracked.
+ */
+ _window: Trait.required,
+ /**
+ * Function used to emit events.
+ */
+ _emit: EventEmitter.required,
+ _tabOptions: Trait.required,
+ /**
+ * Function to add event listeners.
+ */
+ on: EventEmitter.required,
+ removeListener: EventEmitter.required,
+ /**
+ * Initializes tab tracker for a browser window.
+ */
+ _initWindowTabTracker: function _initWindowTabTracker() {
+ // Ugly hack that we have to remove at some point (see Bug 658059). At this
+ // point it is necessary to invoke lazy `tabs` getter on the windows object
+ // which creates a `TabList` instance.
+ this.tabs;
+ // Binding all methods used as event listeners to the instance.
+ this._onTabReady = this._emitEvent.bind(this, "ready");
+ this._onTabOpen = this._onTabEvent.bind(this, "open");
+ this._onTabClose = this._onTabEvent.bind(this, "close");
+ this._onTabActivate = this._onTabEvent.bind(this, "activate");
+ this._onTabDeactivate = this._onTabEvent.bind(this, "deactivate");
+ this._onTabPinned = this._onTabEvent.bind(this, "pinned");
+ this._onTabUnpinned = this._onTabEvent.bind(this, "unpinned");
+
+ for each (let tab in getTabs(this._window)) {
+ // We emulate "open" events for all open tabs since gecko does not emits
+ // them on the tabs that new windows are open with. Also this is
+ // necessary to synchronize tabs lists with an actual state.
+ this._onTabOpen(tab);
+ }
+ // We also emulate "activate" event so that it's picked up by a tab list.
+ this._onTabActivate(getActiveTab(this._window));
+
+ // Setting up event listeners
+ tabsObserver.on("open", this._onTabOpen);
+ tabsObserver.on("close", this._onTabClose);
+ tabsObserver.on("activate", this._onTabActivate);
+ tabsObserver.on("deactivate", this._onTabDeactivate);
+ tabsObserver.on("pinned", this._onTabPinned);
+ tabsObserver.on("unpinned", this._onTabUnpinned);
+ },
+ _destroyWindowTabTracker: function _destroyWindowTabTracker() {
+ // We emulate close events on all tabs, since gecko does not emits such
+ // events by itself.
+ for each (let tab in this.tabs)
+ this._emitEvent("close", tab);
+
+ this._tabs._clear();
+
+ tabsObserver.removeListener("open", this._onTabOpen);
+ tabsObserver.removeListener("close", this._onTabClose);
+ tabsObserver.removeListener("activate", this._onTabActivate);
+ tabsObserver.removeListener("deactivate", this._onTabDeactivate);
+ },
+ _onTabEvent: function _onTabEvent(type, tab) {
+ if (this._window === getOwnerWindow(tab)) {
+ let options = this._tabOptions.shift() || {};
+ options.tab = tab;
+ options.window = this._public;
+ // creating tab wrapper and adding listener to "ready" events.
+ let wrappedTab = Tab(options);
+
+ // Setting up an event listener for ready events.
+ if (type === "open")
+ wrappedTab.on("ready", this._onTabReady);
+
+ this._emitEvent(type, wrappedTab);
+ }
+ },
+ _emitEvent: function _emitEvent(type, tab) {
+ // Notifies combined tab list that tab was added / removed.
+ tabs._emit(type, tab);
+ // Notifies contained tab list that window was added / removed.
+ this._tabs._emit(type, tab);
+ }
+});
+exports.WindowTabTracker = WindowTabTracker;
+
+/**
+ * This trait is used to create live representation of open tab lists. Each
+ * window wrapper's tab list is represented by an object created from this
+ * trait. It is also used to represent list of all the open windows. Trait is
+ * composed out of `EventEmitter` in order to emit 'TabOpen', 'TabClose' events.
+ * **Please note** that objects created by this trait can't be exposed outside
+ * instead you should expose it's `_public` property, see comments in
+ * constructor for details.
+ */
+const TabList = List.resolve({ constructor: "_init" }).compose(
+ // This is ugly, but necessary. Will be removed by #596248
+ EventEmitter.resolve({ toString: null }),
+ Trait.compose({
+ on: Trait.required,
+ _emit: Trait.required,
+ constructor: function TabList(options) {
+ this._window = options.window;
+ // Add new items to the list
+ this.on(EVENTS.open.name, this._add.bind(this));
+ // Remove closed items from the list
+ this.on(EVENTS.close.name, this._remove.bind(this));
+
+ // Set value whenever new tab becomes active.
+ this.on("activate", function onTabActivate(tab) {
+ this._activeTab = tab;
+ }.bind(this));
+ // Initialize list.
+ this._init();
+ // This list is not going to emit any events, object holding this list
+ // will do it instead, to make that possible we return a private API.
+ return this;
+ },
+ get activeTab() this._activeTab,
+ _activeTab: null,
+
+ open: function open(options) {
+ options = Options(options);
+ this._window._tabOptions.push(options);
+ let tab = openTab(this._window._window, options.url);
+ if (!options.inBackground)
+ activateTab(tab);
+ }
+ // This is ugly, but necessary. Will be removed by #596248
+ }).resolve({ toString: null })
+);
+
+/**
+ * Combined list of all open tabs on all the windows.
+ * type {TabList}
+ */
+var tabs = TabList({ window: null });
+exports.tabs = tabs._public;
+
+/**
+ * Trait is a part of composition that represents window wrapper. This trait is
+ * composed out of `WindowTabTracker` that allows it to keep track of open tabs
+ * on the window being wrapped.
+ */
+const WindowTabs = Trait.compose(
+ WindowTabTracker,
+ Trait.compose({
+ _window: Trait.required,
+ /**
+ * List of tabs
+ */
+ get tabs() (this._tabs || (this._tabs = TabList({ window: this })))._public,
+ _tabs: null,
+ })
+);
+exports.WindowTabs = WindowTabs;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/xhr.js b/tools/addon-sdk-1.7/packages/api-utils/lib/xhr.js
new file mode 100644
index 0000000..b7808dc
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/xhr.js
@@ -0,0 +1,150 @@
+/* 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} = require("chrome");
+const memory = require('api-utils/memory');
+
+// ## Implementation Notes ##
+//
+// Making `XMLHttpRequest` objects available to Jetpack code involves a
+// few key principles universal to all low-level module implementations:
+//
+// * **Unloadability**. A Jetpack-based extension using this module can be
+// asked to unload itself at any time, e.g. because the user decides to
+// uninstall or disable the extension. This means we need to keep track of
+// all in-progress reqests and abort them on unload.
+//
+// * **Developer-Ergonomic Tracebacks**. Whenever an exception is raised
+// by a Jetpack-based extension, we want it to be logged in a
+// place that is specific to that extension--so that a developer
+// can distinguish it from an error on a web page or in another
+// extension, for instance. We also want it to be logged with a
+// full stack traceback, which the Mozilla platform doesn't usually
+// do.
+//
+// Because of this, we don't actually want to give the Mozilla
+// platform's "real" XHR implementation to clients, but instead provide
+// a simple wrapper that trivially delegates to the implementation in
+// all cases except where callbacks are involved: whenever Mozilla
+// platform code calls into the extension, such as during the XHR's
+// `onreadystatechange` callback, we want to wrap the client's callback
+// in a try-catch clause that traps any exceptions raised by the
+// callback and logs them via console.exception() instead of allowing
+// them to propagate back into Mozilla platform code.
+
+// This is a private list of all active requests, so we know what to
+// abort if we're asked to unload.
+var requests = [];
+
+// Events on XHRs that we should listen for, so we know when to remove
+// a request from our private list.
+const TERMINATE_EVENTS = ["load", "error", "abort"];
+
+// Read-only properties of XMLHttpRequest objects that we want to
+// directly delegate to.
+const READ_ONLY_PROPS = ["readyState", "responseText", "responseXML",
+ "status", "statusText"];
+
+// Methods of XMLHttpRequest that we want to directly delegate to.
+const DELEGATED_METHODS = ["abort", "getAllResponseHeaders",
+ "getResponseHeader", "overrideMimeType",
+ "send", "sendAsBinary", "setRequestHeader",
+ "open"];
+
+var getRequestCount = exports.getRequestCount = function getRequestCount() {
+ return requests.length;
+};
+
+var XMLHttpRequest = exports.XMLHttpRequest = function XMLHttpRequest() {
+ var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ // For the sake of simplicity, don't tie this request to any UI.
+ req.mozBackgroundRequest = true;
+
+ memory.track(req, "XMLHttpRequest");
+
+ this._req = req;
+ this._orsc = null;
+
+ requests.push(this);
+
+ var self = this;
+
+ this._boundCleanup = function _boundCleanup() {
+ self._cleanup();
+ };
+
+ TERMINATE_EVENTS.forEach(
+ function(name) {
+ self._req.addEventListener(name, self._boundCleanup, false);
+ });
+};
+
+XMLHttpRequest.prototype = {
+ _cleanup: function _cleanup() {
+ this.onreadystatechange = null;
+ var index = requests.indexOf(this);
+ if (index != -1) {
+ var self = this;
+ TERMINATE_EVENTS.forEach(
+ function(name) {
+ self._req.removeEventListener(name, self._boundCleanup, false);
+ });
+ requests.splice(index, 1);
+ }
+ },
+ _unload: function _unload() {
+ this._req.abort();
+ this._cleanup();
+ },
+ addEventListener: function addEventListener() {
+ throw new Error("not implemented");
+ },
+ removeEventListener: function removeEventListener() {
+ throw new Error("not implemented");
+ },
+ set upload(newValue) {
+ throw new Error("not implemented");
+ },
+ get onreadystatechange() {
+ return this._orsc;
+ },
+ set onreadystatechange(cb) {
+ this._orsc = cb;
+ if (cb) {
+ var self = this;
+ this._req.onreadystatechange = function() {
+ try {
+ self._orsc.apply(self, arguments);
+ } catch (e) {
+ console.exception(e);
+ }
+ };
+ } else
+ this._req.onreadystatechange = null;
+ }
+};
+
+READ_ONLY_PROPS.forEach(
+ function(name) {
+ XMLHttpRequest.prototype.__defineGetter__(
+ name,
+ function() {
+ return this._req[name];
+ });
+ });
+
+DELEGATED_METHODS.forEach(
+ function(name) {
+ XMLHttpRequest.prototype[name] = function() {
+ return this._req[name].apply(this._req, arguments);
+ };
+ });
+
+require("./unload").when(
+ function() {
+ requests.slice().forEach(function(request) { request._unload(); });
+ });
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/xpcom.js b/tools/addon-sdk-1.7/packages/api-utils/lib/xpcom.js
new file mode 100644
index 0000000..efdf873
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/xpcom.js
@@ -0,0 +1,207 @@
+/* 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, Cr, Cm, components: { classesByID } } = require('chrome');
+const { registerFactory, unregisterFactory, isCIDRegistered } =
+ Cm.QueryInterface(Ci.nsIComponentRegistrar);
+
+const { when: unload } = require('./unload');
+const { Base } = require('./base');
+const { uuid } = require('./uuid');
+
+// This is a base prototype, that provides bare bones of XPCOM. JS based
+// components can be easily implement by extending it.
+const Unknown = Base.extend({
+ // Method `extend` is overridden so that resulting object will contain
+ // `interfaces` array property, containing elements from ancestor and all
+ // provided sources.
+ extend: function extend() {
+ let args = Array.slice(arguments);
+ return Base.extend.apply(this, args.concat([{
+ interfaces: args.reduce(function(interfaces, source) {
+ // If given source has `interfaces` property concatenate it's elements
+ // them resulting `interfaces` array, otherwise return resulting
+ // `interfaces` array.
+ return 'interfaces' in source ? source.interfaces.concat(interfaces)
+ : interfaces;
+ }, this.interfaces)
+ }]));
+ },
+ /**
+ * The `QueryInterface` method provides runtime type discovery used by XPCOM.
+ * This method return quired instance of `this` if given `iid` is listed in
+ * the `interfaces` property.
+ */
+ QueryInterface: function QueryInterface(iid) {
+ // For some reason there are cases when `iid` is `null`. In such cases we
+ // just return `this`. Otherwise we verify that component implements given
+ // `iid` interface.
+ if (iid && !this.interfaces.some(function(id) iid.equals(Ci[id])))
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ return this;
+ },
+ /**
+ * Array of `XPCOM` interfaces (as strings) implemented by this component. All
+ * components implement `nsISupports` by default which is default value here.
+ * Provide array of interfaces implemented by an object when extending, to
+ * append them to this list (Please note that there is no need to repeat
+ * interfaces implemented by super as they will be added automatically).
+ */
+ interfaces: [ 'nsISupports' ]
+});
+exports.Unknown = Unknown;
+
+// Base exemplar for creating instances implementing `nsIFactory` interface,
+// that maybe registered into runtime via `register` function. Instances of
+// this factory create instances of enclosed component on `createInstance`.
+const Factory = Unknown.extend({
+ interfaces: [ 'nsIFactory' ],
+ /**
+ * All the descendants will get auto generated `id` (also known as `classID`
+ * in XPCOM world) unless one is manually provided.
+ */
+ get id() { throw Error('Factory must implement `id` property') },
+ /**
+ * XPCOM `contractID` may optionally be provided to associate this factory
+ * with it. `contract` is a unique string that has a following format:
+ * '@vendor.com/unique/id;1'.
+ */
+ contract: null,
+ /**
+ * Class description that is being registered. This value is intended as a
+ * human-readable description for the given class and does not needs to be
+ * globally unique.
+ */
+ description: 'Jetpack generated factory',
+ /**
+ * This method is required by `nsIFactory` interfaces, but as in most
+ * implementations it does nothing interesting.
+ */
+ lockFactory: function lockFactory(lock) undefined,
+ /**
+ * If property is `true` XPCOM service / factory will be registered
+ * automatically on creation.
+ */
+ register: true,
+ /**
+ * If property is `true` XPCOM factory will be unregistered prior to add-on
+ * unload.
+ */
+ unregister: true,
+ /**
+ * Method is called on `Service.new(options)` passing given `options` to
+ * it. Options is expected to have `component` property holding XPCOM
+ * component implementation typically decedent of `Unknown` or any custom
+ * implementation with a `new` method and optional `register`, `unregister`
+ * flags. Unless `register` is `false` Service / Factory will be
+ * automatically registered. Unless `unregister` is `false` component will
+ * be automatically unregistered on add-on unload.
+ */
+ initialize: function initialize(options) {
+ options = options || {}
+ this.merge(options, { id: 'id' in options && options.id || uuid() });
+
+ // If service / factory has auto registration enabled then register.
+ if (this.register)
+ register(this);
+ },
+ /**
+ * Creates an instance of the class associated with this factory.
+ */
+ createInstance: function createInstance(outer, iid) {
+ try {
+ if (outer)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return this.create().QueryInterface(iid);
+ }
+ catch (error) {
+ throw error instanceof Ci.nsIException ? error : Cr.NS_ERROR_FAILURE;
+ }
+ },
+ create: function create() this.component.new()
+});
+exports.Factory = Factory;
+
+// Exemplar for creating services that implement `nsIFactory` interface, that
+// can be registered into runtime via call to `register`. This services return
+// enclosed `component` on `getService`.
+const Service = Factory.extend({
+ description: 'Jetpack generated service',
+ /**
+ * Creates an instance of the class associated with this factory.
+ */
+ create: function create() this.component
+});
+exports.Service = Service;
+
+function isRegistered({ id }) isCIDRegistered(id)
+exports.isRegistered = isRegistered;
+
+/**
+ * Registers given `component` object to be used to instantiate a particular
+ * class identified by `component.id`, and creates an association of class
+ * name and `component.contract` with the class.
+ */
+function register(factory) {
+ registerFactory(factory.id, factory.description, factory.contract, factory);
+
+ if (factory.unregister)
+ unload(unregister.bind(null, factory));
+}
+exports.register = register;
+
+/**
+ * Unregister a factory associated with a particular class identified by
+ * `factory.classID`.
+ */
+function unregister(factory) {
+ if (isRegistered(factory))
+ unregisterFactory(factory.id, factory);
+}
+exports.unregister = unregister;
+
+function autoRegister(path) {
+ // TODO: This assumes that the url points to a directory
+ // that contains subdirectories corresponding to OS/ABI and then
+ // further subdirectories corresponding to Gecko platform version.
+ // we should probably either behave intelligently here or allow
+ // the caller to pass-in more options if e.g. there aren't
+ // Gecko-specific binaries for a component (which will be the case
+ // if only frozen interfaces are used).
+
+ var runtime = require("./runtime");
+ var osDirName = runtime.OS + "_" + runtime.XPCOMABI;
+ var platformVersion = require("./xul-app").platformVersion.substring(0, 5);
+
+ var file = Cc['@mozilla.org/file/local;1']
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(path);
+ file.append(osDirName);
+ file.append(platformVersion);
+
+ if (!(file.exists() && file.isDirectory()))
+ throw new Error("component not available for OS/ABI " +
+ osDirName + " and platform " + platformVersion);
+
+ Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ Cm.autoRegister(file);
+}
+exports.autoRegister = autoRegister;
+
+/**
+ * Returns registered factory that has a given `id` or `null` if not found.
+ */
+function factoryByID(id) classesByID[id] || null
+exports.factoryByID = factoryByID;
+
+/**
+ * Returns factory registered with a given `contract` or `null` if not found.
+ * In contrast to `Cc[contract]` that does ignores new factory registration
+ * with a given `contract` this will return a factory currently associated
+ * with a `contract`.
+ */
+function factoryByContract(contract) factoryByID(Cm.contractIDToCID(contract))
+exports.factoryByContract = factoryByContract;
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/xul-app.js b/tools/addon-sdk-1.7/packages/api-utils/lib/xul-app.js
new file mode 100644
index 0000000..9272d88
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/lib/xul-app.js
@@ -0,0 +1,63 @@
+/* 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} = require("chrome");
+
+var appInfo = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULAppInfo);
+
+var ID = exports.ID = appInfo.ID;
+var name = exports.name = appInfo.name;
+var version = exports.version = appInfo.version;
+var platformVersion = exports.platformVersion = appInfo.platformVersion;
+
+// The following mapping of application names to GUIDs was taken from:
+//
+// https://addons.mozilla.org/en-US/firefox/pages/appversions
+//
+// Using the GUID instead of the app's name is preferable because sometimes
+// re-branded versions of a product have different names: for instance,
+// Firefox, Minefield, Iceweasel, and Shiretoko all have the same
+// GUID.
+// This mapping is duplicated in `app-extensions/bootstrap.js`. They should keep
+// in sync, so if you change one, change the other too!
+
+var ids = exports.ids = {
+ Firefox: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
+ Mozilla: "{86c18b42-e466-45a9-ae7a-9b95ba6f5640}",
+ Sunbird: "{718e30fb-e89b-41dd-9da7-e25a45638b28}",
+ SeaMonkey: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
+ Fennec: "{aa3c5121-dab2-40e2-81ca-7ea25febc110}",
+ Thunderbird: "{3550f703-e582-4d05-9a08-453d09bdfdc6}"
+};
+
+var is = exports.is = function is(name) {
+ if (!(name in ids))
+ throw new Error("Unkown Mozilla Application: " + name);
+ return ID == ids[name];
+};
+
+var isOneOf = exports.isOneOf = function isOneOf(names) {
+ for (var i = 0; i < names.length; i++)
+ if (is(names[i]))
+ return true;
+ return false;
+};
+
+/**
+ * Use this to check whether the given version (e.g. xulApp.platformVersion)
+ * is in the given range. Versions must be in version comparator-compatible
+ * format. See MDC for details:
+ * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIVersionComparator
+ */
+var versionInRange = exports.versionInRange =
+function versionInRange(version, lowInclusive, highExclusive) {
+ var vc = Cc["@mozilla.org/xpcom/version-comparator;1"]
+ .getService(Ci.nsIVersionComparator);
+ return (vc.compare(version, lowInclusive) >= 0) &&
+ (vc.compare(version, highExclusive) < 0);
+}
+