aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.4/packages/api-utils/lib
diff options
context:
space:
mode:
Diffstat (limited to 'tools/addon-sdk-1.4/packages/api-utils/lib')
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/api-utils.js186
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/app-strings.js95
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/array.js102
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/byte-streams.js135
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/channel.js67
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/collection.js141
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/content.js44
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/content/loader.js203
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/content/symbiont.js217
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/content/worker.js662
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/cortex.js139
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/cuddlefish.js320
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/dom/events.js169
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/dom/events/keys.js93
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/env!.js52
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/environment.js86
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/errors.js92
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/events.js202
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/events/assembler.js86
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/file.js227
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/find-tests.js1
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/globals!.js113
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/hidden-frame.js200
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/httpd.js5202
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/hotkeys.js141
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/observer.js86
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/utils.js220
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/light-traits.js626
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/list.js147
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/match-pattern.js137
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/memory.js146
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/namespace.js55
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/observer-service.js212
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/passwords/utils.js134
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/plain-text-console.js114
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/preferences-service.js138
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/process.js95
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/runtime.js48
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/self!.js82
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/system.js131
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/tab-browser.js761
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/tabs/events.js56
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/tabs/observer.js126
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/tabs/tab.js297
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/tabs/utils.js87
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/test.js140
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/test/assert.js360
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/text-streams.js273
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/timer.js141
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/traceback.js155
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/traits.js215
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/traits/core.js349
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/type.js372
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/unit-test-finder.js106
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/unit-test.js466
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/unload.js59
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/url.js123
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/utils/data.js104
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/utils/function.js64
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/utils/registry.js90
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/utils/thumbnail.js76
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/window-utils.js270
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/windows/dom.js60
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/windows/loader.js152
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/windows/observer.js86
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/windows/tabs.js207
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/xhr.js181
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/xpcom.js152
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/xul-app.js95
69 files changed, 16969 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/api-utils.js b/tools/addon-sdk-1.4/packages/api-utils/lib/api-utils.js
new file mode 100644
index 0000000..17d18cc
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/api-utils.js
@@ -0,0 +1,186 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ * Edward Lee <edilee@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+// 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.4/packages/api-utils/lib/app-strings.js b/tools/addon-sdk-1.4/packages/api-utils/lib/app-strings.js
new file mode 100644
index 0000000..120c26e
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/app-strings.js
@@ -0,0 +1,95 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is String Bundle.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Myk Melez <myk@mozilla.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/array.js b/tools/addon-sdk-1.4/packages/api-utils/lib/array.js
new file mode 100644
index 0000000..a41acdd
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/array.js
@@ -0,0 +1,102 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/byte-streams.js b/tools/addon-sdk-1.4/packages/api-utils/lib/byte-streams.js
new file mode 100644
index 0000000..b44357e
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/byte-streams.js
@@ -0,0 +1,135 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/channel.js b/tools/addon-sdk-1.4/packages/api-utils/lib/channel.js
new file mode 100644
index 0000000..b7f1e61
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/channel.js
@@ -0,0 +1,67 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const { jetpackID } = require('@packaging');
+
+// 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))
+ messageManager.removeMessageListener(address, listener);
+ });
+ messageManager.addMessageListener(address, listener);
+ },
+ 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.4/packages/api-utils/lib/collection.js b/tools/addon-sdk-1.4/packages/api-utils/lib/collection.js
new file mode 100644
index 0000000..5525a5a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/collection.js
@@ -0,0 +1,141 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/content.js b/tools/addon-sdk-1.4/packages/api-utils/lib/content.js
new file mode 100644
index 0000000..a46a5ff
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/content.js
@@ -0,0 +1,44 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"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.4/packages/api-utils/lib/content/loader.js b/tools/addon-sdk-1.4/packages/api-utils/lib/content/loader.js
new file mode 100644
index 0000000..25bc651
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/content/loader.js
@@ -0,0 +1,203 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const { EventEmitter } = require('../events');
+const { validateOptions, getTypeOf } = require('../api-utils');
+const { URL, toFilename } = require('../url');
+const file = require('../file');
+
+// 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: function(value) 'undefined' === getTypeOf(value) ? null : value,
+ ok: function(value) {
+ if (getTypeOf(value) === 'array') {
+ // Make sure every item is a local file URL.
+ return value.every(function (item) {
+ try {
+ toFilename(item);
+ return true;
+ }
+ catch(e) {
+ return false;
+ }
+ });
+ }
+ return true;
+ },
+ msg: 'The `contentScriptFile` option must be a local file URL or an array of'
+ + 'URLs.'
+ },
+ contentScript: {
+ is: ['undefined', 'null', 'string', 'array'],
+ map: function(value) 'undefined' === getTypeOf(value) ? null : value,
+ ok: function(value) 'array' !== getTypeOf(value) ? true :
+ value.every(function(item) 'string' === getTypeOf(item))
+ ,
+ msg: 'The script option must be a string or an array of strings.'
+ },
+ contentScriptWhen: {
+ is: ['string'],
+ ok: function(value) ['start', 'ready', 'end'].indexOf(value) >= 0,
+ 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.4/packages/api-utils/lib/content/symbiont.js b/tools/addon-sdk-1.4/packages/api-utils/lib/content/symbiont.js
new file mode 100644
index 0000000..d6dbf38
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/content/symbiont.js
@@ -0,0 +1,217 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Myk Melez <myk@mozilla.org> (Original Author)
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"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.4/packages/api-utils/lib/content/worker.js b/tools/addon-sdk-1.4/packages/api-utils/lib/content/worker.js
new file mode 100644
index 0000000..6ded506
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/content/worker.js
@@ -0,0 +1,662 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+const { Trait } = require('../traits');
+const { EventEmitter, EventEmitterTrait } = require('../events');
+const { Ci, Cu, Cc } = require('chrome');
+const timer = require('../timer');
+const { toFilename } = require('../url');
+const file = require('../file');
+const unload = require('../unload');
+const observers = require('../observer-service');
+const { Cortex } = require('../cortex');
+const { Enqueued } = require('../utils/function');
+const self = require("self");
+const scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+
+const CONTENT_PROXY_URL = self.data.url("content-proxy.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 = {};
+
+function ensureArgumentsAreJSON(array, window) {
+ // 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;
+ }
+ // If a window is given, we use its `JSON.parse` object in order to
+ // create JS objects for its compartments (See bug 714891)
+ let parse = JSON.parse;
+ if (window) {
+ // As we can't directly rely on `window.wrappedJSObject.JSON`, we create
+ // a temporary sandbox in order to get access to a safe `JSON` object:
+ parse = Cu.Sandbox(window).JSON.parse;
+ }
+ return parse(JSON.stringify(array, replacer));
+}
+
+/**
+ * Extended `EventEmitter` allowing us to emit events asynchronously.
+ */
+const AsyncEventEmitter = EventEmitter.compose({
+ /**
+ * Emits event in the next turn of event loop.
+ */
+ _asyncEmit: function _asyncEmit() {
+ timer.setTimeout(function emitter(emit, scope, params) {
+ emit.apply(scope, params);
+ }, 0, this._emit, this, arguments)
+ }
+});
+
+/**
+ * Local trait providing implementation of the workers global scope.
+ * Used to configure global object in the sandbox.
+ * @see http://www.w3.org/TR/workers/#workerglobalscope
+ */
+const WorkerGlobalScope = AsyncEventEmitter.compose({
+ on: Trait.required,
+ _removeAllListeners: Trait.required,
+
+ // wrapped functions from `'timer'` module.
+ // Wrapper adds `try catch` blocks to the callbacks in order to
+ // emit `error` event on a symbiont if exception is thrown in
+ // the Worker global scope.
+ // @see http://www.w3.org/TR/workers/#workerutils
+
+ // List of all living timeouts/intervals
+ _timers: null,
+
+ setTimeout: function setTimeout(callback, delay) {
+ let params = Array.slice(arguments, 2);
+ let id = timer.setTimeout(function(self) {
+ try {
+ delete self._timers[id];
+ callback.apply(null, params);
+ } catch(e) {
+ self._addonWorker._asyncEmit('error', e);
+ }
+ }, delay, this);
+ this._timers[id] = true;
+ return id;
+ },
+ clearTimeout: function clearTimeout(id){
+ delete this._timers[id];
+ return timer.clearTimeout(id);
+ },
+
+ setInterval: function setInterval(callback, delay) {
+ let params = Array.slice(arguments, 2);
+ let id = timer.setInterval(function(self) {
+ try {
+ callback.apply(null, params);
+ } catch(e) {
+ self._addonWorker._asyncEmit('error', e);
+ }
+ }, delay, this);
+ this._timers[id] = true;
+ return id;
+ },
+ clearInterval: function clearInterval(id) {
+ delete this._timers[id];
+ return timer.clearInterval(id);
+ },
+
+ /**
+ * `onMessage` function defined in the global scope of the worker context.
+ */
+ get _onMessage() this.__onMessage,
+ set _onMessage(value) {
+ let listener = this.__onMessage;
+ if (listener && value !== listener) {
+ this.removeListener('message', listener);
+ this.__onMessage = undefined;
+ }
+ if (value)
+ this.on('message', this.__onMessage = value);
+ },
+ __onMessage: undefined,
+
+ /**
+ * Function for sending data to the addon side.
+ * Validates that data is a `JSON` or primitive value and emits
+ * 'message' event on the worker in the next turn of the event loop.
+ * _Later this will be sending data across process boundaries._
+ * @param {JSON|String|Number|Boolean} data
+ */
+ postMessage: function postMessage(data) {
+ if (!this._addonWorker)
+ throw new Error(ERR_DESTROYED);
+ this._addonWorker._asyncEmit('message', ensureArgumentsAreJSON(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 self.on / self.emit
+ */
+ get port() this._port._public,
+
+ /**
+ * Same object than this.port but private API.
+ * Allow access to _asyncEmit, in order to send event to port.
+ */
+ _port: null,
+
+ /**
+ * Alias to the global scope in the context of worker. Similar to
+ * `window` concept.
+ */
+ get self() this._public,
+
+ /**
+ * Configures sandbox and loads content scripts into it.
+ * @param {Worker} worker
+ * content worker
+ */
+ constructor: function WorkerGlobalScope(worker) {
+ this._addonWorker = worker;
+
+ // Hack in order to allow addon worker to access _asyncEmit
+ // as this is the private object of WorkerGlobalScope
+ worker._contentWorker = this;
+
+ // create an event emitter that receive and send events from/to the addon
+ let contentWorker = this;
+ this._port = EventEmitterTrait.create({
+ emit: function () {
+ let addonWorker = contentWorker._addonWorker;
+ if (!addonWorker)
+ throw new Error(ERR_DESTROYED);
+ addonWorker._onContentScriptEvent.apply(addonWorker, arguments);
+ }
+ });
+ // create emit that executes in next turn of event loop.
+ this._port._asyncEmit = Enqueued(this._port._emit);
+ // expose wrapped port, that exposes only public properties.
+ this._port._public = Cortex(this._port);
+
+ // We receive an unwrapped window, with raw js access
+ let window = worker._window;
+
+ let proto = window;
+ let proxySandbox = null;
+ // Build content proxies only if the document has a non-system principal
+ if (window.wrappedJSObject) {
+ // Instantiate the proxy code in another Sandbox in order to prevent
+ // content script from polluting globals used by proxy code
+ proxySandbox = Cu.Sandbox(window, {
+ wantXrays: true
+ });
+ proxySandbox.console = console;
+ // Execute the proxy code
+ scriptLoader.loadSubScript(CONTENT_PROXY_URL, proxySandbox);
+ // Get a reference of the window's proxy
+ proto = proxySandbox.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 sandbox = this._sandbox = new Cu.Sandbox(window, {
+ sandboxPrototype: proto,
+ wantXrays: true
+ });
+ Object.defineProperties(sandbox, {
+ // We need "this === window === top" to be true in toplevel scope:
+ window: { get: function() sandbox },
+ top: { get: function() sandbox },
+ // 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!
+ unsafeWindow: { get: function () window.wrappedJSObject }
+ });
+
+ // Internal feature that is only used by SDK tests:
+ // Expose unlock key to content script context.
+ // See `PRIVATE_KEY` definition for more information.
+ if (proxySandbox && worker._expose_key)
+ sandbox.UNWRAP_ACCESS_KEY = proxySandbox.UNWRAP_ACCESS_KEY;
+ // Initialize timer lists
+ this._timers = {};
+
+ let publicAPI = this._public;
+
+ // List of content script globals:
+ let keys = ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval',
+ 'self'];
+ for each (let key in keys) {
+ Object.defineProperty(
+ sandbox, key, Object.getOwnPropertyDescriptor(publicAPI, key)
+ );
+ }
+ let self = this;
+ Object.defineProperties(sandbox, {
+ onMessage: {
+ get: function() self._onMessage,
+ set: function(value) {
+ console.warn("The global `onMessage` function in content scripts " +
+ "is deprecated in favor of the `self.on()` function. " +
+ "Replace `onMessage = function (data){}` definitions " +
+ "with calls to `self.on('message', function (data){})`. " +
+ "For more info on `self.on`, see " +
+ "<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.");
+ self._onMessage = value;
+ },
+ configurable: true
+ },
+ console: { value: console, configurable: true },
+
+ // Deprecated use of on/postMessage from globals
+ on: {
+ value: function () {
+ console.warn("The global `on()` function in content scripts is " +
+ "deprecated in favor of the `self.on()` function, " +
+ "which works the same. Replace calls to `on()` with " +
+ "calls to `self.on()`" +
+ "For more info on `self.on`, see " +
+ "<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.");
+ publicAPI.on.apply(publicAPI, arguments);
+ },
+ configurable: true
+ },
+ postMessage: {
+ value: function () {
+ console.warn("The global `postMessage()` function in content " +
+ "scripts is deprecated in favor of the " +
+ "`self.postMessage()` function, which works the same. " +
+ "Replace calls to `postMessage()` with calls to " +
+ "`self.postMessage()`." +
+ "For more info on `self.on`, see " +
+ "<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.");
+ publicAPI.postMessage.apply(publicAPI, arguments);
+ },
+ configurable: true
+ }
+ });
+
+ // Temporary fix for test-widget, that pass self.postMessage to proxy code
+ // that first try to access to `___proxy` and then call it through `apply`.
+ // We need to move function given to content script to a sandbox
+ // with same principal than the content script.
+ // In the meantime, we need to allow such access explicitly
+ // by using `__exposedProps__` property, documented here:
+ // https://developer.mozilla.org/en/XPConnect_wrappers
+ sandbox.self.postMessage.__exposedProps__ = {
+ ___proxy: 'rw',
+ apply: 'rw'
+ }
+
+ // 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", {
+ get: function () publicAPI
+ }
+ );
+ }
+
+ // 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
+ );
+ }
+ },
+ _destructor: function _destructor() {
+ this._removeAllListeners();
+ // Unregister all setTimeout/setInterval
+ // We can use `clearTimeout` for both setTimeout/setInterval
+ // as internal implementation of timer module use same method for both.
+ for (let id in this._timers)
+ timer.clearTimeout(id);
+ this._sandbox = null;
+ this._addonWorker = null;
+ this.__onMessage = undefined;
+ },
+
+ /**
+ * 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) {
+ filename = filename || 'javascript:' + code;
+ try {
+ Cu.evalInSandbox(code, this._sandbox, JS_VERSION, filename, 1);
+ }
+ catch(e) {
+ this._addonWorker._asyncEmit('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 filename = toFilename(contentScriptFile);
+ this._evaluate(file.read(filename), filename);
+ }
+ catch(e) {
+ this._addonWorker._asyncEmit('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 = AsyncEventEmitter.compose({
+ on: Trait.required,
+ _asyncEmit: 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._asyncEmit('message',
+ ensureArgumentsAreJSON(data, this._window));
+ },
+
+ /**
+ * 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(arguments)
+ });
+ // create emit that executes in next turn of event loop.
+ this._port._asyncEmit = Enqueued(this._port._emit);
+ // 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 _asyncEmit, 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);
+ }
+
+ let scope = this._contentWorker._port;
+ // Ensure that we pass only JSON values
+ let array = Array.prototype.slice.call(args);
+ scope._asyncEmit.apply(scope, ensureArgumentsAreJSON(array, this._window));
+ },
+
+ // Is worker connected to the content worker (i.e. WorkerGlobalScope) ?
+ _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 WorkerGlobalScope(this)
+ this.port;
+
+ // will set this._contentWorker pointing to the private API:
+ WorkerGlobalScope(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('message');
+ this._removeAllListeners('error');
+ this._removeAllListeners('detach');
+ },
+
+ /**
+ * 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._destructor();
+ 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() {
+ // Ensure that we pass only JSON values
+ let array = Array.prototype.slice.call(arguments);
+ this._port._asyncEmit.apply(this._port, ensureArgumentsAreJSON(array));
+ },
+
+ /**
+ * 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.4/packages/api-utils/lib/cortex.js b/tools/addon-sdk-1.4/packages/api-utils/lib/cortex.js
new file mode 100644
index 0000000..059d9de
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/cortex.js
@@ -0,0 +1,139 @@
+/* vim:set ts=2 sw=2 sts=2
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/cuddlefish.js b/tools/addon-sdk-1.4/packages/api-utils/lib/cuddlefish.js
new file mode 100644
index 0000000..c0c9935
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/cuddlefish.js
@@ -0,0 +1,320 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+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 (prototype, principal) {
+ let sandbox = Object.create(Sandbox, {
+ sandbox: {
+ value: Cu.Sandbox(principal || Sandbox.principal, {
+ sandboxPrototype: prototype || Sandbox.prototype,
+ wantXrays: Sandbox.wantXrays
+ })
+ }
+ });
+ // 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);
+ },
+ 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;
+ let sandbox = this.sandboxes[module.path] = Sandbox.new(this.globals);
+ 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.4/packages/api-utils/lib/dom/events.js b/tools/addon-sdk-1.4/packages/api-utils/lib/dom/events.js
new file mode 100644
index 0000000..da9c93d
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/dom/events.js
@@ -0,0 +1,169 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/dom/events/keys.js b/tools/addon-sdk-1.4/packages/api-utils/lib/dom/events/keys.js
new file mode 100644
index 0000000..155625a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/dom/events/keys.js
@@ -0,0 +1,93 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/env!.js b/tools/addon-sdk-1.4/packages/api-utils/lib/env!.js
new file mode 100644
index 0000000..642d599
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/env!.js
@@ -0,0 +1,52 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+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.4/packages/api-utils/lib/environment.js b/tools/addon-sdk-1.4/packages/api-utils/lib/environment.js
new file mode 100644
index 0000000..5753801
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/environment.js
@@ -0,0 +1,86 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+'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.4/packages/api-utils/lib/errors.js b/tools/addon-sdk-1.4/packages/api-utils/lib/errors.js
new file mode 100644
index 0000000..56be526
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/errors.js
@@ -0,0 +1,92 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/events.js b/tools/addon-sdk-1.4/packages/api-utils/lib/events.js
new file mode 100644
index 0000000..4e36c00
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/events.js
@@ -0,0 +1,202 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ * Drew Willcoxon <adw@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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) {
+ this._emit('error', e);
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Removes all the event listeners for the specified event `type`.
+ * @param {String} type
+ * The type of event.
+ */
+ _removeAllListeners: function _removeAllListeners(type) {
+ 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.4/packages/api-utils/lib/events/assembler.js b/tools/addon-sdk-1.4/packages/api-utils/lib/events/assembler.js
new file mode 100644
index 0000000..26860d6
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/events/assembler.js
@@ -0,0 +1,86 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/file.js b/tools/addon-sdk-1.4/packages/api-utils/lib/file.js
new file mode 100644
index 0000000..30cb356
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/file.js
@@ -0,0 +1,227 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is nsINarwhal.
+ *
+ * The Initial Developer of the Original Code is
+ * Irakli Gozalishvili <rfobic@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <rfobic@gmail.com>
+ * Atul Varma <atul@mozilla.com>
+ * Drew Willcoxon <adw@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/find-tests.js b/tools/addon-sdk-1.4/packages/api-utils/lib/find-tests.js
new file mode 100644
index 0000000..be29d19
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/find-tests.js
@@ -0,0 +1 @@
+// this file left intentionally blank
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/globals!.js b/tools/addon-sdk-1.4/packages/api-utils/lib/globals!.js
new file mode 100644
index 0000000..27fd8ba
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/globals!.js
@@ -0,0 +1,113 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+let { Cc, Ci } = require('chrome');
+let { PlainTextConsole } = require('./plain-text-console');
+let options = require('@packaging');
+
+// 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);
+
+// TODO: Remove memory from the globals, as it raises security concerns and
+// there is no real reason to favor global memory over
+// `require('api-utils/memory')`. For details see: Bug-620559
+exports.memory = require('./memory');
+exports.console = new PlainTextConsole(exports.dump);
+
+// 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.4/packages/api-utils/lib/hidden-frame.js b/tools/addon-sdk-1.4/packages/api-utils/lib/hidden-frame.js
new file mode 100644
index 0000000..241a4bc
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/hidden-frame.js
@@ -0,0 +1,200 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Felipe Gomes <felipc@gmail.com> (Original Author)
+ * Myk Melez <myk@mozilla.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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", "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.4/packages/api-utils/lib/httpd.js b/tools/addon-sdk-1.4/packages/api-utils/lib/httpd.js
new file mode 100644
index 0000000..ebf3643
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/httpd.js
@@ -0,0 +1,5202 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+* Version: MPL 1.1/GPL 2.0/LGPL 2.1
+*
+* The contents of this file are subject to the Mozilla Public License Version
+* 1.1 (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+* http://www.mozilla.org/MPL/
+*
+* Software distributed under the License is distributed on an "AS IS" basis,
+* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+* for the specific language governing rights and limitations under the
+* License.
+*
+* The Original Code is the httpd.js server.
+*
+* The Initial Developer of the Original Code is
+* Mozilla Corporation.
+* Portions created by the Initial Developer are Copyright (C) 2006
+* the Initial Developer. All Rights Reserved.
+*
+* Contributor(s):
+* Darin Fisher (v1, netwerk/test/TestServ.js)
+* Christian Biesinger (v2, netwerk/test/unit/head_http_server.js)
+* Jeff Walden <jwalden+code@mit.edu> (v3, netwerk/test/httpserver/httpd.js)
+* Robert Sayre <sayrer@gmail.com>
+*
+* Alternatively, the contents of this file may be used under the terms of
+* either the GNU General Public License Version 2 or later (the "GPL"), or
+* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+* in which case the provisions of the GPL or the LGPL are applicable instead
+* of those above. If you wish to allow use of your version of this file only
+* under the terms of either the GPL or the LGPL, and not to allow others to
+* use your version of this file under the terms of the MPL, indicate your
+* decision by deleting the provisions above and replace them with the notice
+* and other provisions required by the GPL or the LGPL. If you do not delete
+* the provisions above, a recipient may use your version of this file under
+* the terms of any one of the MPL, the GPL or the LGPL.
+*
+* ***** END LICENSE BLOCK ***** */
+
+/*
+* 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.4/packages/api-utils/lib/keyboard/hotkeys.js b/tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/hotkeys.js
new file mode 100644
index 0000000..6851671
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/hotkeys.js
@@ -0,0 +1,141 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ * Paul Vet <original.roju@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/keyboard/observer.js b/tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/observer.js
new file mode 100644
index 0000000..6968551
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/observer.js
@@ -0,0 +1,86 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/keyboard/utils.js b/tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/utils.js
new file mode 100644
index 0000000..0c9a7fb
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/utils.js
@@ -0,0 +1,220 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ * Henri Wiechers <hwiechers@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/light-traits.js b/tools/addon-sdk-1.4/packages/api-utils/lib/light-traits.js
new file mode 100644
index 0000000..1afaad5
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/light-traits.js
@@ -0,0 +1,626 @@
+/* vim:ts=2:sts=2:sw=2:
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <rfobic@gmail.com> (Original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/list.js b/tools/addon-sdk-1.4/packages/api-utils/lib/list.js
new file mode 100644
index 0000000..8643a0a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/list.js
@@ -0,0 +1,147 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"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.4/packages/api-utils/lib/match-pattern.js b/tools/addon-sdk-1.4/packages/api-utils/lib/match-pattern.js
new file mode 100644
index 0000000..802fe3a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/match-pattern.js
@@ -0,0 +1,137 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Nickolay Ponomarev.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Nickolay Ponomarev <asqueella@gmail.com> (Original Author)
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ * Drew Willcoxon <adw@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/memory.js b/tools/addon-sdk-1.4/packages/api-utils/lib/memory.js
new file mode 100644
index 0000000..2ff0486
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/memory.js
@@ -0,0 +1,146 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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)
+ 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.4/packages/api-utils/lib/namespace.js b/tools/addon-sdk-1.4/packages/api-utils/lib/namespace.js
new file mode 100644
index 0000000..ae50f36
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/namespace.js
@@ -0,0 +1,55 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+/**
+ * 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) {
+ return map.get(target) ||
+ map.set(target, Object.create(prototype)), map.get(target);
+ };
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/observer-service.js b/tools/addon-sdk-1.4/packages/api-utils/lib/observer-service.js
new file mode 100644
index 0000000..f408dd5
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/observer-service.js
@@ -0,0 +1,212 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Observers.
+ *
+ * The Initial Developer of the Original Code is Daniel Aquino.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Daniel Aquino <mr.danielaquino@gmail.com>
+ * Myk Melez <myk@mozilla.org>
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc,Ci} = require("chrome");
+var xpcom = require("./xpcom");
+
+/**
+ * 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 thisObject {Object} [optional]
+ * the object to use as |this| when calling a Function callback
+ *
+ * @returns the observer
+ */
+var add = exports.add = function add(topic, callback, thisObject) {
+ var observer = new Observer(topic, callback, thisObject);
+ 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 thisObject {Object} [optional]
+ * the object being used as |this| when calling a Function callback
+ */
+var remove = exports.remove = function remove(topic, callback, thisObject) {
+ // 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 thisObject, 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.thisObject == thisObject);
+ });
+
+ 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 : new Subject(subject);
+ data = (typeof data == "undefined") ? null : data;
+ service.notifyObservers(subject, topic, data);
+};
+
+function Observer(topic, callback, thisObject) {
+ memory.track(this);
+ this.topic = topic;
+ this.callback = callback;
+ this.thisObject = thisObject;
+}
+
+Observer.prototype = {
+ QueryInterface: xpcom.utils.generateQI([Ci.nsIObserver,
+ Ci.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.thisObject)
+ this.callback.call(this.thisObject, subject, data);
+ else
+ this.callback(subject, data);
+ } else // typeof this.callback == "object" (nsIObserver)
+ this.callback.observe(subject, topic, data);
+ } catch (e) {
+ console.exception(e);
+ }
+ }
+};
+
+function Subject(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
+ };
+}
+
+Subject.prototype = {
+ QueryInterface: xpcom.utils.generateQI([]),
+ getHelperForLanguage: function() {},
+ getInterfaces: function() {}
+};
+
+require("./unload").when(
+ function removeAllObservers() {
+ // Make a copy of cache first, since cache will be changing as we
+ // iterate through it.
+ cache.slice().forEach(
+ function(observer) {
+ remove(observer.topic, observer.callback, observer.thisObject);
+ });
+ });
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/passwords/utils.js b/tools/addon-sdk-1.4/packages/api-utils/lib/passwords/utils.js
new file mode 100644
index 0000000..950aac2
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/passwords/utils.js
@@ -0,0 +1,134 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack Packages.
+ *
+ * The Initial Developer of the Original Code is Red Hat.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Matěj Cepl <mcepl@redhat.com> (Original Author)
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/plain-text-console.js b/tools/addon-sdk-1.4/packages/api-utils/lib/plain-text-console.js
new file mode 100644
index 0000000..f7ea74e
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/plain-text-console.js
@@ -0,0 +1,114 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/preferences-service.js b/tools/addon-sdk-1.4/packages/api-utils/lib/preferences-service.js
new file mode 100644
index 0000000..127a1cf
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/preferences-service.js
@@ -0,0 +1,138 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Preferences.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Myk Melez <myk@mozilla.org>
+ * Daniel Aquino <mr.danielaquino@gmail.com>
+ * Atul Varma <atul@mozilla.com>
+ * Erik Vold <erikvvold@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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");
+
+var prefSvc = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService).getBranch(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.
+ }
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/process.js b/tools/addon-sdk-1.4/packages/api-utils/lib/process.js
new file mode 100644
index 0000000..3f429eb
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/process.js
@@ -0,0 +1,95 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const { Cc, Ci } = require("chrome");
+const { createRemoteBrowser } = require("api-utils/window-utils");
+const { channel } = require("./channel");
+const { setTimout } = require('./timer');
+const packaging = require('@packaging');
+const { when } = require('./unload');
+
+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;
+
+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).
+
+ loadScript(target, packaging.uriPrefix + packaging.loader, false);
+ loadScript(target, 'data:,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.
+ loadScript(target, 'data:,loader.unload("' + reason + '")', true);
+ });
+
+ return { channel: channel.bind(null, scope, target) }
+}
+
+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 {
+ 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.4/packages/api-utils/lib/runtime.js b/tools/addon-sdk-1.4/packages/api-utils/lib/runtime.js
new file mode 100644
index 0000000..135617e
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/runtime.js
@@ -0,0 +1,48 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/self!.js b/tools/addon-sdk-1.4/packages/api-utils/lib/self!.js
new file mode 100644
index 0000000..bd9b6da
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/self!.js
@@ -0,0 +1,82 @@
+/* vim:st=2:sts=2:sw=2:
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Brian Warner <warner@mozilla.com>
+ * Erik Vold <erikvvold@gmail.com>
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/system.js b/tools/addon-sdk-1.4/packages/api-utils/lib/system.js
new file mode 100644
index 0000000..70896e7
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/system.js
@@ -0,0 +1,131 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+'use strict';
+
+const { Cc, Ci, CC } = require('chrome');
+const options = require('@packaging');
+const file = require('./file');
+
+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 runtime = Cc["@mozilla.org/xre/app-info;1"].
+ getService(Ci.nsIXULRuntime);
+
+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);
+};
+
+/**
+ * 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 = runtime.platformVersion;
+
+
+/**
+ * The name of the application vendor, for example "Mozilla".
+ */
+exports.vendor = appInfo.vendor;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/tab-browser.js b/tools/addon-sdk-1.4/packages/api-utils/lib/tab-browser.js
new file mode 100644
index 0000000..272a7c6
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/tab-browser.js
@@ -0,0 +1,761 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ * Dietrich Ayala <dietrich@mozilla.com>
+ * Felipe Gomes <felipc@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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 = new 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.4/packages/api-utils/lib/tabs/events.js b/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/events.js
new file mode 100644
index 0000000..96309bb
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/events.js
@@ -0,0 +1,56 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+const ON_PREFIX = "on";
+const TAB_PREFIX = "Tab";
+
+const EVENTS = {
+ ready: "DOMContentLoaded",
+ open: "TabOpen",
+ close: "TabClose",
+ activate: "TabSelect",
+ deactivate: null
+}
+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.4/packages/api-utils/lib/tabs/observer.js b/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/observer.js
new file mode 100644
index 0000000..f679917
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/observer.js
@@ -0,0 +1,126 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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": "pin",
+ "TabUnpinned": "unpin"
+};
+
+
+// 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.4/packages/api-utils/lib/tabs/tab.js b/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/tab.js
new file mode 100644
index 0000000..17e2ff6
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/tab.js
@@ -0,0 +1,297 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+const { Ci } = require('chrome');
+const { Trait } = require("../traits");
+const { EventEmitter } = require("../events");
+const { validateOptions } = require("../api-utils");
+const { Enqueued } = require("../utils/function");
+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() {
+ for each (let type in EVENTS)
+ this._removeAllListeners(type.name);
+ 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: Enqueued(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: Enqueued(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.4/packages/api-utils/lib/tabs/utils.js b/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/utils.js
new file mode 100644
index 0000000..030956c
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/utils.js
@@ -0,0 +1,87 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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 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.4/packages/api-utils/lib/test.js b/tools/addon-sdk-1.4/packages/api-utils/lib/test.js
new file mode 100644
index 0000000..6490630
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/test.js
@@ -0,0 +1,140 @@
+/* vim:ts=2:sts=2:sw=2:
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/test/assert.js b/tools/addon-sdk-1.4/packages/api-utils/lib/test/assert.js
new file mode 100644
index 0000000..0c29d62
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/test/assert.js
@@ -0,0 +1,360 @@
+/* vim:ts=2:sts=2:sw=2:
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"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) {
+ return isArrayEquivalent(Object.keys(a).sort(),
+ Object.keys(b).sort()) &&
+ Object.keys(a).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.4/packages/api-utils/lib/text-streams.js b/tools/addon-sdk-1.4/packages/api-utils/lib/text-streams.js
new file mode 100644
index 0000000..87d375d
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/text-streams.js
@@ -0,0 +1,273 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/timer.js b/tools/addon-sdk-1.4/packages/api-utils/lib/timer.js
new file mode 100644
index 0000000..052f506
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/timer.js
@@ -0,0 +1,141 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ * Drew Willcoxon <adw@mozilla.com>
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ * Erik Vold <erikvvold@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc,Ci} = require("chrome");
+var xpcom = require("./xpcom");
+
+var timerClass = Cc["@mozilla.org/timer;1"];
+var nextID = 1;
+var timers = {};
+
+function TimerCallback(timerID, callback, params) {
+ this._callback = callback;
+ this._params = params;
+};
+TimerCallback.prototype = {
+ QueryInterface : xpcom.utils.generateQI([Ci.nsITimerCallback])
+};
+
+function TimeoutCallback(timerID, callback, params) {
+ memory.track(this);
+ TimerCallback.apply(this, arguments)
+ this._timerID = timerID;
+};
+TimeoutCallback.prototype = new TimerCallback();
+TimeoutCallback.prototype.notify = function notifyOnTimeout(timer) {
+ try {
+ delete timers[this._timerID];
+ this._callback.apply(null, this._params);
+ } catch (e) {
+ console.exception(e);
+ }
+};
+
+function IntervalCallback(timerID, callback, params) {
+ memory.track(this);
+ TimerCallback.apply(this, arguments)
+};
+IntervalCallback.prototype = new TimerCallback();
+IntervalCallback.prototype.notify = function notifyOnInterval() {
+ try {
+ this._callback.apply(null, this._params);
+ } catch (e) {
+ console.exception(e);
+ }
+};
+
+
+var setTimeout = exports.setTimeout = function setTimeout(callback, delay) {
+ return makeTimer(
+ Ci.nsITimer.TYPE_ONE_SHOT,
+ callback,
+ TimeoutCallback,
+ delay,
+ Array.slice(arguments, 2));
+};
+
+var clearTimeout = exports.clearTimeout = function clearTimeout(timerID) {
+ cancelTimer(timerID);
+};
+
+var setInterval = exports.setInterval = function setInterval(callback, delay) {
+ return makeTimer(
+ Ci.nsITimer.TYPE_REPEATING_SLACK,
+ callback,
+ IntervalCallback,
+ delay,
+ Array.slice(arguments, 2));
+};
+
+var clearInterval = exports.clearInterval = function clearInterval(timerID) {
+ cancelTimer(timerID);
+};
+
+function makeTimer(type, callback, callbackType, delay, params) {
+ var timer = timerClass.createInstance(Ci.nsITimer);
+
+ memory.track(timer, "nsITimer");
+
+ var timerID = nextID++;
+ timers[timerID] = timer;
+
+ timer.initWithCallback(
+ new callbackType(timerID, callback, params),
+ delay || 0,
+ type
+ );
+ return timerID;
+}
+
+function cancelTimer(timerID) {
+ var timer = timers[timerID];
+ if (timer) {
+ timer.cancel();
+ delete timers[timerID];
+ }
+}
+
+require("./unload").when(
+ function cancelAllPendingTimers() {
+ var timerIDs = [timerID for (timerID in timers)];
+ timerIDs.forEach(function(timerID) { cancelTimer(timerID); });
+ });
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/traceback.js b/tools/addon-sdk-1.4/packages/api-utils/lib/traceback.js
new file mode 100644
index 0000000..fdedd99
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/traceback.js
@@ -0,0 +1,155 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/traits.js b/tools/addon-sdk-1.4/packages/api-utils/lib/traits.js
new file mode 100644
index 0000000..59193aa
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/traits.js
@@ -0,0 +1,215 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/traits/core.js b/tools/addon-sdk-1.4/packages/api-utils/lib/traits/core.js
new file mode 100644
index 0000000..bc8ae14
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/traits/core.js
@@ -0,0 +1,349 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/type.js b/tools/addon-sdk-1.4/packages/api-utils/lib/type.js
new file mode 100644
index 0000000..012d2d1
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/type.js
@@ -0,0 +1,372 @@
+/* vim:ts=2:sts=2:sw=2:
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/unit-test-finder.js b/tools/addon-sdk-1.4/packages/api-utils/lib/unit-test-finder.js
new file mode 100644
index 0000000..a8dc14a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/unit-test-finder.js
@@ -0,0 +1,106 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const file = require("./file");
+const packaging = require('@packaging');
+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.4/packages/api-utils/lib/unit-test.js b/tools/addon-sdk-1.4/packages/api-utils/lib/unit-test.js
new file mode 100644
index 0000000..4b3ac89
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/unit-test.js
@@ -0,0 +1,466 @@
+/* vim:st=2:sts=2:sw=2:
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+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,
+ 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 (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.4/packages/api-utils/lib/unload.js b/tools/addon-sdk-1.4/packages/api-utils/lib/unload.js
new file mode 100644
index 0000000..3bbeb38
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/unload.js
@@ -0,0 +1,59 @@
+// 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.4/packages/api-utils/lib/url.js b/tools/addon-sdk-1.4/packages/api-utils/lib/url.js
new file mode 100644
index 0000000..4502129
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/url.js
@@ -0,0 +1,123 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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) {
+ 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);
+ this.toString = function URL_toString() uri.spec;
+};
+exports.URL = require("./api-utils").publicConstructor(URL);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/utils/data.js b/tools/addon-sdk-1.4/packages/api-utils/lib/utils/data.js
new file mode 100644
index 0000000..bf621ad
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/utils/data.js
@@ -0,0 +1,104 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/utils/function.js b/tools/addon-sdk-1.4/packages/api-utils/lib/utils/function.js
new file mode 100644
index 0000000..ba43d59
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/utils/function.js
@@ -0,0 +1,64 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+var { setTimeout } = require("../timer");
+
+/**
+ * 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 Enqueued(callee) {
+ return function enqueued()
+ setTimeout(invoke, 0, callee, arguments, this);
+}
+exports.Enqueued = Enqueued;
+
+/**
+ * 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;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/utils/registry.js b/tools/addon-sdk-1.4/packages/api-utils/lib/utils/registry.js
new file mode 100644
index 0000000..6cd11b2
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/utils/registry.js
@@ -0,0 +1,90 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"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.4/packages/api-utils/lib/utils/thumbnail.js b/tools/addon-sdk-1.4/packages/api-utils/lib/utils/thumbnail.js
new file mode 100644
index 0000000..d9012e1
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/utils/thumbnail.js
@@ -0,0 +1,76 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.4/packages/api-utils/lib/window-utils.js b/tools/addon-sdk-1.4/packages/api-utils/lib/window-utils.js
new file mode 100644
index 0000000..2d123a8
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/window-utils.js
@@ -0,0 +1,270 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const { Cc, Ci } = require("chrome");
+const { EventEmitter } = require('./events'),
+ { Trait } = require('./traits');
+const errors = require("./errors");
+
+const gWindowWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher);
+const appShellService = Cc["@mozilla.org/appshell/appShellService;1"].
+ getService(Ci.nsIAppShellService);
+
+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.
+ */
+var windowIterator = exports.windowIterator = function windowIterator() {
+ let winEnum = gWindowWatcher.getWindowEnumerator();
+ while (winEnum.hasMoreElements())
+ yield winEnum.getNext().QueryInterface(Ci.nsIDOMWindow);
+};
+
+/**
+ * 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;
+
+var WindowTracker = exports.WindowTracker = function WindowTracker(delegate) {
+ this.delegate = delegate;
+ this._loadingWindows = [];
+ for (let window in windowIterator())
+ this._regWindow(window);
+ gWindowWatcher.registerNotification(this);
+ require("./unload").ensure(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() {
+ gWindowWatcher.unregisterNotification(this);
+ for (let window in windowIterator())
+ this._unregWindow(window);
+ },
+
+ handleEvent: function handleEvent(event) {
+ if (event.type == "load" && event.target) {
+ var window = event.target.defaultView;
+ if (window)
+ this._regWindow(window);
+ }
+ },
+
+ observe: function observe(subject, topic, data) {
+ var window = subject.QueryInterface(Ci.nsIDOMWindow);
+ if (topic == "domwindowopened")
+ this._regWindow(window);
+ else
+ this._unregWindow(window);
+ }
+};
+
+errors.catchAndLogProps(WindowTracker.prototype, ["handleEvent", "observe"]);
+
+const WindowTrackerTrait = Trait.compose({
+ _onTrack: Trait.required,
+ _onUntrack: Trait.required,
+ constructor: function WindowTrackerTrait() {
+ new 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);
+};
+
+exports.__defineGetter__("activeWindow", function() {
+ return Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator)
+ .getMostRecentWindow(null);
+});
+exports.__defineSetter__("activeWindow", function(window) {
+ try {
+ window.focus();
+ }
+ catch (e) { }
+});
+
+exports.__defineGetter__("activeBrowserWindow", 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 getInnerId(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
+};
+
+/**
+ * Returns the ID of the window's outer window.
+ */
+exports.getOuterId = function getOuterId(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
+};
+
+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;
+ 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.
+ frame.setAttribute('src', 'chrome://browser/content/hiddenWindow.xul');
+ frame.addEventListener('DOMContentLoaded', function onLoad(event) {
+ frame.removeEventListener('DOMContentLoaded', onLoad, false);
+ deliver(frame.contentWindow);
+ }, false);
+ document.documentElement.appendChild(frame);
+ }
+ }
+};
+exports.createHiddenXULFrame = createHiddenXULFrame;
+
+exports.createRemoteBrowser = 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);
+
+ // Return browser
+ deliver(browser);
+ });
+ };
+};
+
+require("./unload").when(
+ function() {
+ gDocsToClose.slice().forEach(
+ function(doc) { doc.defaultView.close(); });
+ });
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/windows/dom.js b/tools/addon-sdk-1.4/packages/api-utils/lib/windows/dom.js
new file mode 100644
index 0000000..d74e314
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/windows/dom.js
@@ -0,0 +1,60 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+'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.4/packages/api-utils/lib/windows/loader.js b/tools/addon-sdk-1.4/packages/api-utils/lib/windows/loader.js
new file mode 100644
index 0000000..6b43fb6
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/windows/loader.js
@@ -0,0 +1,152 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"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.4/packages/api-utils/lib/windows/observer.js b/tools/addon-sdk-1.4/packages/api-utils/lib/windows/observer.js
new file mode 100644
index 0000000..d7b01e6
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/windows/observer.js
@@ -0,0 +1,86 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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.
+new 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.4/packages/api-utils/lib/windows/tabs.js b/tools/addon-sdk-1.4/packages/api-utils/lib/windows/tabs.js
new file mode 100644
index 0000000..551b1ce
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/windows/tabs.js
@@ -0,0 +1,207 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"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");
+
+ 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);
+ },
+ _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.4/packages/api-utils/lib/xhr.js b/tools/addon-sdk-1.4/packages/api-utils/lib/xhr.js
new file mode 100644
index 0000000..10b83db
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/xhr.js
@@ -0,0 +1,181 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc,Ci} = require("chrome");
+
+// ## 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.4/packages/api-utils/lib/xpcom.js b/tools/addon-sdk-1.4/packages/api-utils/lib/xpcom.js
new file mode 100644
index 0000000..10f1d6b
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/xpcom.js
@@ -0,0 +1,152 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ * Drew Willcoxon <adw@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc,Ci,Cm,Cr,Cu} = require("chrome");
+
+var jsm = {};
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", jsm);
+var utils = exports.utils = jsm.XPCOMUtils;
+
+Cm.QueryInterface(Ci.nsIComponentRegistrar);
+
+var factories = [];
+
+function Factory(options) {
+ memory.track(this);
+
+ this.wrappedJSObject = this;
+ this.create = options.create;
+ this.uuid = options.uuid;
+ this.name = options.name;
+ this.contractID = options.contractID;
+
+ Cm.registerFactory(this.uuid,
+ this.name,
+ this.contractID,
+ this);
+
+ var self = this;
+
+ factories.push(this);
+}
+
+Factory.prototype = {
+ createInstance: function(outer, iid) {
+ try {
+ if (outer)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return (new this.create()).QueryInterface(iid);
+ } catch (e) {
+ console.exception(e);
+ if (e instanceof Ci.nsIException)
+ throw e;
+ else
+ throw Cr.NS_ERROR_FAILURE;
+ }
+ },
+ unregister: function() {
+ var index = factories.indexOf(this);
+ if (index == -1)
+ throw new Error("factory already unregistered");
+
+ var self = this;
+
+ factories.splice(index, 1);
+ Cm.unregisterFactory(this.uuid, this);
+ },
+ QueryInterface: utils.generateQI([Ci.nsIFactory])
+};
+
+var makeUuid = exports.makeUuid = function makeUuid() {
+ var uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator);
+ var uuid = uuidGenerator.generateUUID();
+ return uuid;
+};
+
+var autoRegister = exports.autoRegister = 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 appInfo = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULAppInfo);
+ var runtime = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULRuntime);
+
+ var osDirName = runtime.OS + "_" + runtime.XPCOMABI;
+ var platformVersion = appInfo.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);
+};
+
+var register = exports.register = function register(options) {
+ options = {__proto__: options};
+ if (!options.uuid)
+ options.uuid = makeUuid();
+ return new Factory(options);
+};
+
+var getClass = exports.getClass = function getClass(contractID, iid) {
+ if (!iid)
+ iid = Ci.nsISupports;
+ return Cm.getClassObjectByContractID(contractID, iid);
+};
+
+require("./unload").when(
+ function() {
+ var copy = factories.slice();
+ copy.reverse();
+ copy.forEach(function(factory) { factory.unregister(); });
+ });
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/xul-app.js b/tools/addon-sdk-1.4/packages/api-utils/lib/xul-app.js
new file mode 100644
index 0000000..c118682
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/xul-app.js
@@ -0,0 +1,95 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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: "{a23983c0-fd0e-11dc-95ff-0800200c9a66}",
+ 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);
+}
+