aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.3/packages/addon-kit/lib
diff options
context:
space:
mode:
Diffstat (limited to 'tools/addon-sdk-1.3/packages/addon-kit/lib')
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/lib/clipboard.js266
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/lib/context-menu.js1527
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/lib/hotkeys.js71
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/lib/notifications.js112
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/lib/page-mod.js229
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/lib/page-worker.js101
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/lib/panel.js404
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/lib/passwords.js92
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/lib/private-browsing.js102
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/lib/request.js309
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/lib/selection.js448
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/lib/simple-storage.js263
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/lib/tabs.js62
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/lib/timers.js40
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/lib/widget.js938
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/lib/windows.js238
16 files changed, 5202 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/lib/clipboard.js b/tools/addon-sdk-1.3/packages/addon-kit/lib/clipboard.js
new file mode 100644
index 0000000..24f4641
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/lib/clipboard.js
@@ -0,0 +1,266 @@
+/* -*- 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):
+ * Paul O’Shannessy <paul@oshannessy.com> (Original Author)
+ * Dietrich Ayala <dietrich@mozilla.com>
+ * Myk Melez <myk@mozilla.org>
+ * 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");
+const errors = require("api-utils/errors");
+const apiUtils = require("api-utils/api-utils");
+
+/*
+While these data flavors resemble Internet media types, they do
+no directly map to them.
+*/
+const kAllowableFlavors = [
+ "text/unicode",
+ "text/html"
+ /* CURRENTLY UNSUPPORTED FLAVORS
+ "text/plain",
+ "image/png",
+ "image/jpg",
+ "image/gif"
+ "text/x-moz-text-internal",
+ "AOLMAIL",
+ "application/x-moz-file",
+ "text/x-moz-url",
+ "text/x-moz-url-data",
+ "text/x-moz-url-desc",
+ "text/x-moz-url-priv",
+ "application/x-moz-nativeimage",
+ "application/x-moz-nativehtml",
+ "application/x-moz-file-promise-url",
+ "application/x-moz-file-promise-dest-filename",
+ "application/x-moz-file-promise",
+ "application/x-moz-file-promise-dir"
+ */
+];
+
+/*
+Aliases for common flavors. Not all flavors will
+get an alias. New aliases must be approved by a
+Jetpack API druid.
+*/
+const kFlavorMap = [
+ { short: "text", long: "text/unicode" },
+ { short: "html", long: "text/html" }
+ // Images are currently unsupported.
+ //{ short: "image", long: "image/png" },
+];
+
+let clipboardService = Cc["@mozilla.org/widget/clipboard;1"].
+ getService(Ci.nsIClipboard);
+
+let clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+
+
+exports.set = function(aData, aDataType) {
+ let options = {
+ data: aData,
+ datatype: aDataType || "text"
+ };
+ options = apiUtils.validateOptions(options, {
+ data: {
+ is: ["string"]
+ },
+ datatype: {
+ is: ["string"]
+ }
+ });
+
+ var flavor = fromJetpackFlavor(options.datatype);
+
+ if (!flavor)
+ throw new Error("Invalid flavor");
+
+ // Additional checks for using the simple case
+ if (flavor == "text/unicode") {
+ clipboardHelper.copyString(options.data);
+ return true;
+ }
+
+ // Below are the more complex cases where we actually have to work with a
+ // nsITransferable object
+ var xferable = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ if (!xferable)
+ throw new Error("Couldn't set the clipboard due to an internal error " +
+ "(couldn't create a Transferable object).");
+
+ switch (flavor) {
+ case "text/html":
+ // add text/html flavor
+ let (str = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString))
+ {
+ str.data = options.data;
+ xferable.addDataFlavor(flavor);
+ xferable.setTransferData(flavor, str, str.data.length * 2);
+ }
+
+ // add a text/unicode flavor (html converted to plain text)
+ let (str = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString),
+ converter = Cc["@mozilla.org/feed-textconstruct;1"].
+ createInstance(Ci.nsIFeedTextConstruct))
+ {
+ converter.type = "html";
+ converter.text = options.data;
+ str.data = converter.plainText();
+ xferable.addDataFlavor("text/unicode");
+ xferable.setTransferData("text/unicode", str, str.data.length * 2);
+ }
+ break;
+ // TODO: images!
+ default:
+ throw new Error("Unable to handle the flavor " + flavor + ".");
+ }
+
+ // TODO: Not sure if this will ever actually throw. -zpao
+ try {
+ clipboardService.setData(
+ xferable,
+ null,
+ clipboardService.kGlobalClipboard
+ );
+ } catch (e) {
+ throw new Error("Couldn't set clipboard data due to an internal error: " + e);
+ }
+ return true;
+};
+
+
+exports.get = function(aDataType) {
+ let options = {
+ datatype: aDataType || "text"
+ };
+ options = apiUtils.validateOptions(options, {
+ datatype: {
+ is: ["string"]
+ }
+ });
+
+ var xferable = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ if (!xferable)
+ throw new Error("Couldn't set the clipboard due to an internal error " +
+ "(couldn't create a Transferable object).");
+
+ var flavor = fromJetpackFlavor(options.datatype);
+
+ // Ensure that the user hasn't requested a flavor that we don't support.
+ if (!flavor)
+ throw new Error("Getting the clipboard with the flavor '" + flavor +
+ "' is > not supported.");
+
+ // TODO: Check for matching flavor first? Probably not worth it.
+
+ xferable.addDataFlavor(flavor);
+
+ // Get the data into our transferable.
+ clipboardService.getData(
+ xferable,
+ clipboardService.kGlobalClipboard
+ );
+
+ var data = {};
+ var dataLen = {};
+ try {
+ xferable.getTransferData(flavor, data, dataLen);
+ } catch (e) {
+ // Clipboard doesn't contain data in flavor, return null.
+ return null;
+ }
+
+ // There's no data available, return.
+ if (data.value === null)
+ return null;
+
+ // TODO: Add flavors here as we support more in kAllowableFlavors.
+ switch (flavor) {
+ case "text/unicode":
+ case "text/html":
+ data = data.value.QueryInterface(Ci.nsISupportsString).data;
+ break;
+ default:
+ data = null;
+ }
+
+ return data;
+};
+
+exports.__defineGetter__("currentFlavors", function() {
+ // Loop over kAllowableFlavors, calling hasDataMatchingFlavors for each.
+ // This doesn't seem like the most efficient way, but we can't get
+ // confirmation for specific flavors any other way. This is supposed to be
+ // an inexpensive call, so performance shouldn't be impacted (much).
+ var currentFlavors = [];
+ for each (var flavor in kAllowableFlavors) {
+ var matches = clipboardService.hasDataMatchingFlavors(
+ [flavor],
+ 1,
+ clipboardService.kGlobalClipboard
+ );
+ if (matches)
+ currentFlavors.push(toJetpackFlavor(flavor));
+ }
+ return currentFlavors;
+});
+
+// SUPPORT FUNCTIONS ////////////////////////////////////////////////////////
+
+function toJetpackFlavor(aFlavor) {
+ for each (let flavorMap in kFlavorMap)
+ if (flavorMap.long == aFlavor)
+ return flavorMap.short;
+ // Return null in the case where we don't match
+ return null;
+}
+
+function fromJetpackFlavor(aJetpackFlavor) {
+ // TODO: Handle proper flavors better
+ for each (let flavorMap in kFlavorMap)
+ if (flavorMap.short == aJetpackFlavor || flavorMap.long == aJetpackFlavor)
+ return flavorMap.long;
+ // Return null in the case where we don't match.
+ return null;
+}
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/lib/context-menu.js b/tools/addon-sdk-1.3/packages/addon-kit/lib/context-menu.js
new file mode 100644
index 0000000..3c43e35
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/lib/context-menu.js
@@ -0,0 +1,1527 @@
+/* -*- 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)
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ * Matteo Ferretti <zer0@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");
+
+if (!require("api-utils/xul-app").is("Firefox")) {
+ throw new Error([
+ "The context-menu module currently supports only Firefox. In the future ",
+ "we would like it to support other applications, however. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=560716 for more information."
+ ].join(""));
+}
+
+const apiUtils = require("api-utils/api-utils");
+const collection = require("api-utils/collection");
+const { Worker } = require("api-utils/content");
+const url = require("api-utils/url");
+const { MatchPattern } = require("api-utils/match-pattern");
+const { EventEmitterTrait: EventEmitter } = require("api-utils/events");
+const observerServ = require("api-utils/observer-service");
+const jpSelf = require("self");
+const winUtils = require("api-utils/window-utils");
+const { Trait } = require("api-utils/light-traits");
+const { Cortex } = require("api-utils/cortex");
+const timer = require("timer");
+
+// All user items we add have this class name.
+const ITEM_CLASS = "jetpack-context-menu-item";
+
+// Items in the top-level context menu also have this class.
+const TOPLEVEL_ITEM_CLASS = "jetpack-context-menu-item-toplevel";
+
+// Items in the overflow submenu also have this class.
+const OVERFLOW_ITEM_CLASS = "jetpack-context-menu-item-overflow";
+
+// The ID of the menu separator that separates standard context menu items from
+// our user items.
+const SEPARATOR_ID = "jetpack-context-menu-separator";
+
+// If more than this number of items are added to the context menu, all items
+// overflow into a "Jetpack" submenu.
+const OVERFLOW_THRESH_DEFAULT = 10;
+const OVERFLOW_THRESH_PREF =
+ "extensions.addon-sdk.context-menu.overflowThreshold";
+
+// The label of the overflow sub-xul:menu.
+//
+// TODO: Localize this.
+const OVERFLOW_MENU_LABEL = "Add-ons";
+
+// The ID of the overflow sub-xul:menu.
+const OVERFLOW_MENU_ID = "jetpack-content-menu-overflow-menu";
+
+// The ID of the overflow submenu's xul:menupopup.
+const OVERFLOW_POPUP_ID = "jetpack-content-menu-overflow-popup";
+
+// These are used by PageContext.isCurrent below. If the popupNode or any of
+// its ancestors is one of these, Firefox uses a tailored context menu, and so
+// the page context doesn't apply.
+const NON_PAGE_CONTEXT_ELTS = [
+ Ci.nsIDOMHTMLAnchorElement,
+ Ci.nsIDOMHTMLAppletElement,
+ Ci.nsIDOMHTMLAreaElement,
+ Ci.nsIDOMHTMLButtonElement,
+ Ci.nsIDOMHTMLCanvasElement,
+ Ci.nsIDOMHTMLEmbedElement,
+ Ci.nsIDOMHTMLImageElement,
+ Ci.nsIDOMHTMLInputElement,
+ Ci.nsIDOMHTMLMapElement,
+ Ci.nsIDOMHTMLMediaElement,
+ Ci.nsIDOMHTMLMenuElement,
+ Ci.nsIDOMHTMLObjectElement,
+ Ci.nsIDOMHTMLOptionElement,
+ Ci.nsIDOMHTMLSelectElement,
+ Ci.nsIDOMHTMLTextAreaElement,
+];
+
+// This is used to access private properties of Item and Menu instances.
+const PRIVATE_PROPS_KEY = {
+ valueOf: function valueOf() "private properties key"
+};
+
+// Used as an internal ID for items and as part of a public ID for item DOM
+// elements. Careful: This number is not necessarily unique to any one instance
+// of the module. For each module instance, when the first item is created this
+// number will be 0, when the second is created it will be 1, and so on.
+let nextItemID = 0;
+
+// The number of items that haven't finished initializing yet. See
+// AIT__finishActiveItemInit().
+let numItemsWithUnfinishedInit = 0;
+
+exports.Item = Item;
+exports.Menu = Menu;
+exports.Separator = Separator;
+
+
+// A word about traits and privates. `this` inside of traits methods is an
+// object private to the implementation. It should never be publicly leaked.
+// We use Cortex in the exported menu item constructors to create public
+// reflections of the private objects that hide private properties -- those
+// prefixed with an underscore. Public reflections are attached to the private
+// objects via the `_public` property.
+//
+// All item objects passed into the implementation by the client will be public
+// reflections, not private objects. Likewise, all item objects passed out of
+// the implementation to the client must be public, not private. Mixing up
+// public and private is bad and easy to do, so not only are private objects
+// restricted to the implementation, but as much as possible we try to restrict
+// them to the Item, Menu, and Separator traits and constructors. Everybody
+// else in the implementation should expect to be passed public reflections, and
+// they must specifically request private objects via privateItem().
+
+// Item, Menu, and Separator are composed of this trait.
+const ItemBaseTrait = Trait({
+
+ _initBase: function IBT__initBase(opts, optRules, optsToNotSet) {
+ this._optRules = optRules;
+ for (let optName in optRules)
+ if (optsToNotSet.indexOf(optName) < 0)
+ this[optName] = opts[optName];
+ optsToNotSet.forEach(function (opt) validateOpt(opts[opt], optRules[opt]));
+ this._isInited = true;
+
+ this._id = nextItemID++;
+ this._parentMenu = null;
+
+ // This makes the private properties accessible to anyone with access to
+ // PRIVATE_PROPS_KEY. Barring loader tricks, only this file has has access
+ // to it, so only this file has access to the private properties.
+ const self = this;
+ this.valueOf = function IBT_valueOf(key) {
+ return key === PRIVATE_PROPS_KEY ? self : self._public;
+ };
+ },
+
+ destroy: function IBT_destroy() {
+ if (this._wasDestroyed)
+ return;
+ if (this.parentMenu)
+ this.parentMenu.removeItem(this._public);
+ else if (!(this instanceof Separator) && this._hasFinishedInit)
+ browserManager.removeTopLevelItem(this._public);
+ browserManager.unregisterItem(this._public);
+ this._wasDestroyed = true;
+ },
+
+ get parentMenu() {
+ return this._parentMenu;
+ },
+
+ set parentMenu(val) {
+ throw new Error("The 'parentMenu' property is not intended to be set. " +
+ "Use menu.addItem(item) instead.");
+ },
+
+ set _isTopLevel(val) {
+ if (val)
+ this._workerReg = new WorkerRegistry(this._public);
+ else {
+ this._workerReg.destroy();
+ delete this._workerReg;
+ }
+ },
+
+ get _topLevelItem() {
+ let topLevelItem = this._public;
+ let parentMenu = this.parentMenu;
+ while (parentMenu) {
+ topLevelItem = parentMenu;
+ parentMenu = parentMenu.parentMenu;
+ }
+ return topLevelItem;
+ }
+});
+
+// Item and Menu are composed of this trait.
+const ActiveItemTrait = Trait.compose(ItemBaseTrait, EventEmitter, Trait({
+
+ _initActiveItem: function AIT__initActiveItem(opts, optRules, optsToNotSet) {
+ this._initBase(opts, optRules,
+ optsToNotSet.concat(["onMessage", "context"]));
+
+ if ("onMessage" in opts)
+ this.on("message", opts.onMessage);
+
+ // When a URL context is removed (by calling context.remove(urlContext)), we
+ // may need to create workers for windows containing pages that the item now
+ // matches. Likewise, when a URL context is added, we need to destroy
+ // workers for windows containing pages that the item now does not match.
+ //
+ // collection doesn't provide a way to listen for removals. utils/registry
+ // does, but it doesn't allow its elements to be enumerated. So as a hack,
+ // use a collection for item.context and replace its add and remove methods.
+ collection.addCollectionProperty(this, "context");
+ if (opts.context)
+ this.context.add(opts.context);
+
+ const self = this;
+
+ let add = this.context.add;
+ this.context.add = function itemContextAdd() {
+ let args = Array.slice(arguments);
+ add.apply(self.context, args);
+ if (self._workerReg && args.some(function (a) a instanceof URLContext))
+ self._workerReg.destroyUnneededWorkers();
+ };
+
+ let remove = this.context.remove;
+ this.context.remove = function itemContextRemove() {
+ let args = Array.slice(arguments);
+ remove.apply(self.context, args);
+ if (self._workerReg && args.some(function (a) a instanceof URLContext))
+ self._workerReg.createNeededWorkers();
+ };
+ },
+
+ // Workers are only created for top-level menu items. When a top-level item
+ // is later added to a Menu, its workers are destroyed. Well, all items start
+ // out as top-level because there is, unfortunately, no contextMenu.add(). So
+ // when an item is created and immediately added to a Menu, workers for it are
+ // needlessly created and destroyed. The point of this timeout is to avoid
+ // that. Items that are created and added to Menus in the same turn of the
+ // event loop won't have workers created for them.
+ _finishActiveItemInit: function AIT__finishActiveItemInit() {
+ numItemsWithUnfinishedInit++;
+ const self = this;
+ timer.setTimeout(function AIT__finishActiveItemInitTimeout() {
+ if (!self.parentMenu && !self._wasDestroyed)
+ browserManager.addTopLevelItem(self._public);
+ self._hasFinishedInit = true;
+ numItemsWithUnfinishedInit--;
+ }, 0);
+ },
+
+ get label() {
+ return this._label;
+ },
+
+ set label(val) {
+ this._label = validateOpt(val, this._optRules.label);
+ if (this._isInited)
+ browserManager.setItemLabel(this, this._label);
+ return this._label;
+ },
+
+ get image() {
+ return this._image;
+ },
+
+ set image(val) {
+ this._image = validateOpt(val, this._optRules.image);
+ if (this._isInited)
+ browserManager.setItemImage(this, this._image);
+ return this._image;
+ },
+
+ get contentScript() {
+ return this._contentScript;
+ },
+
+ set contentScript(val) {
+ this._contentScript = validateOpt(val, this._optRules.contentScript);
+ return this._contentScript;
+ },
+
+ get contentScriptFile() {
+ return this._contentScriptFile;
+ },
+
+ set contentScriptFile(val) {
+ this._contentScriptFile =
+ validateOpt(val, this._optRules.contentScriptFile);
+ return this._contentScriptFile;
+ }
+}));
+
+// Item is composed of this trait.
+const ItemTrait = Trait.compose(ActiveItemTrait, Trait({
+
+ _initItem: function IT__initItem(opts, optRules) {
+ this._initActiveItem(opts, optRules, []);
+ },
+
+ get data() {
+ return this._data;
+ },
+
+ set data(val) {
+ this._data = validateOpt(val, this._optRules.data);
+ if (this._isInited)
+ browserManager.setItemData(this, this._data);
+ return this._data;
+ },
+
+ toString: function IT_toString() {
+ return '[object Item "' + this.label + '"]';
+ }
+}));
+
+// The exported Item constructor.
+function Item(options) {
+ let optRules = optionsRules();
+ optRules.data = {
+ map: function (v) v.toString(),
+ is: ["string", "undefined"]
+ };
+
+ let item = ItemTrait.create(Item.prototype);
+ item._initItem(options, optRules);
+
+ item._public = Cortex(item);
+ browserManager.registerItem(item._public);
+ item._finishActiveItemInit();
+
+ return item._public;
+}
+
+// Menu is composed of this trait.
+const MenuTrait = Trait.compose(
+ ActiveItemTrait.resolve({ destroy: "_destroyThisItem" }),
+ Trait({
+
+ _initMenu: function MT__initMenu(opts, optRules, optsToNotSet) {
+ this._items = [];
+ this._initActiveItem(opts, optRules, optsToNotSet);
+ },
+
+ destroy: function MT_destroy() {
+ while (this.items.length)
+ this.items[0].destroy();
+ this._destroyThisItem();
+ },
+
+ get items() {
+ return this._items;
+ },
+
+ set items(val) {
+ let newItems = validateOpt(val, this._optRules.items);
+ while (this.items.length)
+ this.items[0].destroy();
+ newItems.forEach(function (i) this.addItem(i), this);
+ return newItems;
+ },
+
+ addItem: function MT_addItem(item) {
+ // First, remove the item from its current parent.
+ let privates = privateItem(item);
+ if (item.parentMenu)
+ item.parentMenu.removeItem(item);
+ else if (!(item instanceof Separator) && privates._hasFinishedInit)
+ browserManager.removeTopLevelItem(item);
+
+ // Now add the item to this menu.
+ this._items.push(item);
+ privates._parentMenu = this._public;
+ browserManager.addItemToMenu(item, this._public);
+ },
+
+ removeItem: function MT_removeItem(item) {
+ let idx = this._items.indexOf(item);
+ if (idx < 0)
+ return;
+ this._items.splice(idx, 1);
+ privateItem(item)._parentMenu = null;
+ browserManager.removeItemFromMenu(item, this._public);
+ },
+
+ toString: function MT_toString() {
+ return '[object Menu "' + this.label + '"]';
+ }
+}));
+
+// The exported Menu constructor.
+function Menu(options) {
+ let optRules = optionsRules();
+ optRules.items = {
+ is: ["array"],
+ ok: function (v) {
+ return v.every(function (item) {
+ return (item instanceof Item) ||
+ (item instanceof Menu) ||
+ (item instanceof Separator);
+ });
+ },
+ msg: "items must be an array, and each element in the array must be an " +
+ "Item, Menu, or Separator."
+ };
+
+ let menu = MenuTrait.create(Menu.prototype);
+
+ // We can't rely on _initBase to set the `items` property, because the menu
+ // needs to be registered with and added to the browserManager before any
+ // child items are added to it.
+ menu._initMenu(options, optRules, ["items"]);
+
+ menu._public = Cortex(menu);
+ browserManager.registerItem(menu._public);
+ menu.items = options.items;
+ menu._finishActiveItemInit();
+
+ return menu._public;
+}
+
+// The exported Separator constructor.
+function Separator() {
+ let sep = ItemBaseTrait.create(Separator.prototype);
+ sep._initBase({}, {}, []);
+
+ sep._public = Cortex(sep);
+ browserManager.registerItem(sep._public);
+ sep._hasFinishedInit = true;
+ return sep._public;
+}
+
+
+function Context() {}
+
+function PageContext() {
+ this.isCurrent = function PageContext_isCurrent(popupNode) {
+ let win = popupNode.ownerDocument.defaultView;
+ if (win && !win.getSelection().isCollapsed)
+ return false;
+
+ let cursor = popupNode;
+ while (cursor && !(cursor instanceof Ci.nsIDOMHTMLHtmlElement)) {
+ if (NON_PAGE_CONTEXT_ELTS.some(function (iface) cursor instanceof iface))
+ return false;
+ cursor = cursor.parentNode;
+ }
+ return true;
+ };
+}
+
+PageContext.prototype = new Context();
+
+function SelectorContext(selector) {
+ let opts = apiUtils.validateOptions({ selector: selector }, {
+ selector: {
+ is: ["string"],
+ msg: "selector must be a string."
+ }
+ });
+
+ this.adjustPopupNode = function SelectorContext_adjustPopupNode(node) {
+ return closestMatchingAncestor(node);
+ };
+
+ this.isCurrent = function SelectorContext_isCurrent(popupNode) {
+ return !!closestMatchingAncestor(popupNode);
+ };
+
+ // Returns node if it matches selector, or the closest ancestor of node that
+ // matches, or null if node and none of its ancestors matches.
+ function closestMatchingAncestor(node) {
+ let cursor = node;
+ while (cursor) {
+ if (cursor.mozMatchesSelector(selector))
+ return cursor;
+ if (cursor instanceof Ci.nsIDOMHTMLHtmlElement)
+ break;
+ cursor = cursor.parentNode;
+ }
+ return null;
+ }
+}
+
+SelectorContext.prototype = new Context();
+
+function SelectionContext() {
+ this.isCurrent = function SelectionContext_isCurrent(popupNode) {
+ let win = popupNode.ownerDocument.defaultView;
+ if (!win)
+ return false;
+
+ let hasSelection = !win.getSelection().isCollapsed;
+ if (!hasSelection) {
+ // window.getSelection doesn't return a selection for text selected in a
+ // form field (see bug 85686), so before returning false we want to check
+ // if the popupNode is a text field.
+ let { selectionStart, selectionEnd } = popupNode;
+ hasSelection = !isNaN(selectionStart) &&
+ !isNaN(selectionEnd) &&
+ selectionStart !== selectionEnd;
+ }
+ return hasSelection;
+ };
+}
+
+SelectionContext.prototype = new Context();
+
+function URLContext(patterns) {
+ let opts = apiUtils.validateOptions({ patterns: patterns }, {
+ patterns: {
+ map: function (v) apiUtils.getTypeOf(v) === "array" ? v : [v],
+ ok: function (v) v.every(function (p) typeof(p) === "string"),
+ msg: "patterns must be a string or an array of strings."
+ }
+ });
+ try {
+ patterns = opts.patterns.map(function (p) new MatchPattern(p));
+ }
+ catch (err) {
+ console.error("Error creating URLContext match pattern:");
+ throw err;
+ }
+
+ const self = this;
+
+ this.isCurrent = function URLContext_isCurrent(popupNode) {
+ return self.isCurrentForURL(popupNode.ownerDocument.URL);
+ };
+
+ this.isCurrentForURL = function URLContext_isCurrentForURL(url) {
+ return patterns.some(function (p) p.test(url));
+ };
+}
+
+URLContext.prototype = new Context();
+
+exports.PageContext = apiUtils.publicConstructor(PageContext);
+exports.SelectorContext = apiUtils.publicConstructor(SelectorContext);
+exports.SelectionContext = apiUtils.publicConstructor(SelectionContext);
+exports.URLContext = apiUtils.publicConstructor(URLContext);
+
+
+// Returns a version of opt validated against the given rule.
+function validateOpt(opt, rule) {
+ let { opt } = apiUtils.validateOptions({ opt: opt }, { opt: rule });
+ return opt;
+}
+
+// Returns rules for apiUtils.validateOptions() common to Item and Menu.
+function optionsRules() {
+ return {
+ context: {
+ is: ["undefined", "object", "array"],
+ ok: function (v) {
+ if (!v)
+ return true;
+ let arr = apiUtils.getTypeOf(v) === "array" ? v : [v];
+ return arr.every(function (o) o instanceof Context);
+ },
+ msg: "The 'context' option must be a Context object or an array of " +
+ "Context objects."
+ },
+ label: {
+ map: function (v) v.toString(),
+ is: ["string"],
+ ok: function (v) !!v,
+ msg: "The item must have a non-empty string label."
+ },
+ image: {
+ map: function (v) v.toString(),
+ is: ["string", "undefined", "null"]
+ },
+ contentScript: {
+ is: ["string", "array", "undefined"],
+ ok: function (v) {
+ return apiUtils.getTypeOf(v) !== "array" ||
+ v.every(function (s) typeof(s) === "string");
+ }
+ },
+ contentScriptFile: {
+ is: ["string", "array", "undefined"],
+ ok: function (v) {
+ if (!v)
+ return true;
+ let arr = apiUtils.getTypeOf(v) === "array" ? v : [v];
+ try {
+ return arr.every(function (s) {
+ return apiUtils.getTypeOf(s) === "string" && url.toFilename(s);
+ });
+ }
+ catch (err) {}
+ return false;
+ },
+ msg: "The 'contentScriptFile' option must be a local file URL or " +
+ "an array of local file URLs."
+ },
+ onMessage: {
+ is: ["function", "undefined"]
+ }
+ };
+}
+
+// Does a binary search on elts, a NodeList, and returns the DOM element
+// before which an item with targetLabel should be inserted. null is returned
+// if the new item should be inserted at the end.
+function insertionPoint(targetLabel, elts) {
+ let from = 0;
+ let to = elts.length - 1;
+
+ while (from <= to) {
+ let i = Math.floor((from + to) / 2);
+ let comp = targetLabel.localeCompare(elts[i].getAttribute("label"));
+ if (comp < 0)
+ to = i - 1;
+ else if (comp > 0)
+ from = i + 1;
+ else
+ return elts[i];
+ }
+ return elts[from] || null;
+}
+
+// Builds an ID suitable for a DOM element from the given item ID.
+// isInOverflowSubtree should be true if the returned element will be inserted
+// into the DOM subtree rooted at the overflow menu.
+function domEltIDFromItemID(itemID, isInOverflowSubtree) {
+ let suffix = isInOverflowSubtree ? "-overflow" : "";
+ return jpSelf.id + "-context-menu-item-" + itemID + suffix;
+}
+
+// Parses the item ID out of the given DOM element ID and returns it. If the
+// element's ID is malformed or it indicates that the element was not created by
+// the instance of the module calling this function, returns -1.
+function itemIDFromDOMEltID(domEltID) {
+ let match = /^(.+?)-context-menu-item-([0-9]+)[-a-z]*$/.exec(domEltID);
+ return !match || match[1] !== jpSelf.id ? -1 : match[2];
+}
+
+// Returns the private version of the given public reflection.
+function privateItem(publicItem) {
+ return publicItem.valueOf(PRIVATE_PROPS_KEY);
+}
+
+
+// A type of Worker tailored to our uses.
+const ContextMenuWorker = Worker.compose({
+ destroy: Worker.required,
+
+ // Returns true if any context listeners are defined in the worker's port.
+ anyContextListeners: function CMW_anyContextListeners() {
+ return this._contentWorker._listeners("context").length > 0;
+ },
+
+ // Returns the first string or truthy value returned by a context listener in
+ // the worker's port. If none return a string or truthy value or if there are
+ // no context listeners, returns false. popupNode is the node that was
+ // context-clicked.
+ isAnyContextCurrent: function CMW_isAnyContextCurrent(popupNode) {
+ let listeners = this._contentWorker._listeners("context");
+ for (let i = 0; i < listeners.length; i++) {
+ try {
+ let val = listeners[i].call(this._contentWorker._sandbox, popupNode);
+ if (typeof(val) === "string" || val)
+ return val;
+ }
+ catch (err) {
+ console.exception(err);
+ }
+ }
+ return false;
+ },
+
+ // Emits a click event in the worker's port. popupNode is the node that was
+ // context-clicked, and clickedItemData is the data of the item that was
+ // clicked.
+ fireClick: function CMW_fireClick(popupNode, clickedItemData) {
+ this._contentWorker._asyncEmit("click", popupNode, clickedItemData);
+ }
+});
+
+
+// This class creates and stores content workers for pairs of menu items and
+// content windows. Use one instance for each item. Not all pairs need a
+// worker: if an item has a URL context that does not match a window's page,
+// then no worker is created for the pair.
+function WorkerRegistry(item) {
+ this.item = item;
+
+ // inner window ID => { win, worker }
+ this.winWorkers = {};
+
+ // inner window ID => content window
+ this.winsWithoutWorkers = {};
+}
+
+WorkerRegistry.prototype = {
+
+ // Registers a content window, creating a worker for it if it needs one.
+ registerContentWin: function WR_registerContentWin(win) {
+ let innerWinID = winUtils.getInnerId(win);
+ if ((innerWinID in this.winWorkers) ||
+ (innerWinID in this.winsWithoutWorkers))
+ return;
+ if (this._doesURLNeedWorker(win.document.URL))
+ this.winWorkers[innerWinID] = { win: win, worker: this._makeWorker(win) };
+ else
+ this.winsWithoutWorkers[innerWinID] = win;
+ },
+
+ // Unregisters a content window, destroying its related worker if it has one.
+ unregisterContentWin: function WR_unregisterContentWin(innerWinID) {
+ if (innerWinID in this.winWorkers) {
+ let winWorker = this.winWorkers[innerWinID];
+ winWorker.worker.destroy();
+ delete winWorker.worker;
+ delete winWorker.win;
+ delete this.winWorkers[innerWinID];
+ }
+ else
+ delete this.winsWithoutWorkers[innerWinID];
+ },
+
+ // Creates a worker for each window that needs a worker but doesn't have one.
+ createNeededWorkers: function WR_createNeededWorkers() {
+ for (let [innerWinID, win] in Iterator(this.winsWithoutWorkers)) {
+ delete this.winsWithoutWorkers[innerWinID];
+ this.registerContentWin(win);
+ }
+ },
+
+ // Destroys the worker for each window that has a worker but doesn't need it.
+ destroyUnneededWorkers: function WR_destroyUnneededWorkers() {
+ for (let [innerWinID, winWorker] in Iterator(this.winWorkers)) {
+ if (!this._doesURLNeedWorker(winWorker.win.document.URL)) {
+ this.unregisterContentWin(innerWinID);
+ this.winsWithoutWorkers[innerWinID] = winWorker.win;
+ }
+ }
+ },
+
+ // Returns the worker for the item-window pair or null if none exists.
+ find: function WR_find(contentWin) {
+ let innerWinID = winUtils.getInnerId(contentWin);
+ return (innerWinID in this.winWorkers) ?
+ this.winWorkers[innerWinID].worker :
+ null;
+ },
+
+ // Unregisters all content windows from the registry, which destroys all
+ // workers.
+ destroy: function WR_destroy() {
+ for (let innerWinID in this.winWorkers)
+ this.unregisterContentWin(innerWinID);
+ for (let innerWinID in this.winsWithoutWorkers)
+ this.unregisterContentWin(innerWinID);
+ },
+
+ // Returns false if the item has a URL context that does not match the given
+ // URL.
+ _doesURLNeedWorker: function WR__doesURLNeedWorker(url) {
+ for (let ctxt in this.item.context)
+ if ((ctxt instanceof URLContext) && !ctxt.isCurrentForURL(url))
+ return false;
+ return true;
+ },
+
+ _makeWorker: function WR__makeWorker(win) {
+ let worker = ContextMenuWorker({
+ window: win,
+ contentScript: this.item.contentScript,
+ contentScriptFile: this.item.contentScriptFile,
+ onError: function (err) console.exception(err)
+ });
+ let item = this.item;
+ worker.on("message", function workerOnMessage(msg) {
+ try {
+ privateItem(item)._emitOnObject(item, "message", msg);
+ }
+ catch (err) {
+ console.exception(err);
+ }
+ });
+ return worker;
+ }
+};
+
+
+// Mirrors state across all browser windows. Also responsible for detecting
+// all content window loads and unloads.
+let browserManager = {
+ topLevelItems: [],
+ browserWins: [],
+
+ // inner window ID => content window
+ contentWins: {},
+
+ // Call this when a new item is created, top-level or not.
+ registerItem: function BM_registerItem(item) {
+ this.browserWins.forEach(function (w) w.registerItem(item));
+ },
+
+ // Call this when an item is destroyed and won't be used again, top-level or
+ // not.
+ unregisterItem: function BM_unregisterItem(item) {
+ this.browserWins.forEach(function (w) w.unregisterItem(item));
+ },
+
+ addTopLevelItem: function BM_addTopLevelItem(item) {
+ this.topLevelItems.push(item);
+ this.browserWins.forEach(function (w) w.addTopLevelItem(item));
+
+ // Create the item's worker registry and register all currently loaded
+ // content windows with it.
+ let privates = privateItem(item);
+ privates._isTopLevel = true;
+ for each (let win in this.contentWins)
+ privates._workerReg.registerContentWin(win);
+ },
+
+ removeTopLevelItem: function BM_removeTopLevelItem(item) {
+ let idx = this.topLevelItems.indexOf(item);
+ if (idx < 0)
+ throw new Error("Internal error: item not in top-level menu: " + item);
+ this.topLevelItems.splice(idx, 1);
+ this.browserWins.forEach(function (w) w.removeTopLevelItem(item));
+ privateItem(item)._isTopLevel = false;
+ },
+
+ addItemToMenu: function BM_addItemToMenu(item, parentMenu) {
+ this.browserWins.forEach(function (w) w.addItemToMenu(item, parentMenu));
+ },
+
+ removeItemFromMenu: function BM_removeItemFromMenu(item, parentMenu) {
+ this.browserWins.forEach(function (w) w.removeItemFromMenu(item,
+ parentMenu));
+ },
+
+ setItemLabel: function BM_setItemLabel(item, label) {
+ this.browserWins.forEach(function (w) w.setItemLabel(item, label));
+ },
+
+ setItemImage: function BM_setItemImage(item, imageURL) {
+ this.browserWins.forEach(function (w) w.setItemImage(item, imageURL));
+ },
+
+ setItemData: function BM_setItemData(item, data) {
+ this.browserWins.forEach(function (w) w.setItemData(item, data));
+ },
+
+ // Note that calling this method will cause onTrack to be called immediately
+ // for each currently open browser window.
+ init: function BM_init() {
+ require("api-utils/unload").ensure(this);
+ let windowTracker = new winUtils.WindowTracker(this);
+
+ // Register content windows on content-document-global-created and
+ // unregister them on inner-window-destroyed. For rationale, see bug 667957
+ // for the former and bug 642004 for the latter.
+ observerServ.add("content-document-global-created",
+ this._onDocGlobalCreated, this);
+ observerServ.add("inner-window-destroyed",
+ this._onInnerWinDestroyed, this);
+ },
+
+ _onDocGlobalCreated: function BM__onDocGlobalCreated(contentWin) {
+ let doc = contentWin.document;
+ if (doc.readyState == "loading") {
+ const self = this;
+ doc.addEventListener("readystatechange", function onReadyStateChange(e) {
+ if (e.target != doc || doc.readyState != "complete")
+ return;
+ doc.removeEventListener("readystatechange", onReadyStateChange, false);
+ self._registerContentWin(contentWin);
+ }, false);
+ }
+ else if (doc.readyState == "complete")
+ this._registerContentWin(contentWin);
+ },
+
+ _onInnerWinDestroyed: function BM__onInnerWinDestroyed(subj) {
+ this._unregisterContentWin(
+ subj.QueryInterface(Ci.nsISupportsPRUint64).data);
+ },
+
+ // Stores the given content window with the manager and registers it with each
+ // top-level item's worker registry.
+ _registerContentWin: function BM__registerContentWin(win) {
+ let innerID = winUtils.getInnerId(win);
+
+ // It's an error to call this method for the same window more than once, but
+ // we allow it in one case: when onTrack races _onDocGlobalCreated. (See
+ // the comment in onTrack.) Make sure the window is registered only once.
+ if (innerID in this.contentWins)
+ return;
+
+ this.contentWins[innerID] = win;
+ this.topLevelItems.forEach(function (item) {
+ privateItem(item)._workerReg.registerContentWin(win);
+ });
+ },
+
+ // Removes the given content window from the manager and unregisters it from
+ // each top-level item's worker registry.
+ _unregisterContentWin: function BM__unregisterContentWin(innerID) {
+ delete this.contentWins[innerID];
+ this.topLevelItems.forEach(function (item) {
+ privateItem(item)._workerReg.unregisterContentWin(innerID);
+ });
+ },
+
+ unload: function BM_unload() {
+ // The window tracker is unloaded at the same time this method is called,
+ // which causes onUntrack to be called for each open browser window, so
+ // there's no need to clean up browser windows here.
+
+ while (this.topLevelItems.length) {
+ let item = this.topLevelItems[0];
+ this.removeTopLevelItem(item);
+ this.unregisterItem(item);
+ }
+ delete this.contentWins;
+ },
+
+ // Registers a browser window with the manager. This is a WindowTracker
+ // callback. Note that this is called in two cases: for each newly opened
+ // chrome window, and for each chrome window that is open when the loader
+ // loads this module.
+ onTrack: function BM_onTrack(window) {
+ if (!this._isBrowserWindow(window))
+ return;
+
+ let browserWin = new BrowserWindow(window);
+ this.browserWins.push(browserWin);
+
+ // Register all loaded content windows in the browser window. Be sure to
+ // include frames and iframes. If onTrack is called as a result of a new
+ // browser window being opened, as opposed to the module being loaded, then
+ // this will race the content-document-global-created notification. That's
+ // OK, since _registerContentWin will not register the same content window
+ // more than once.
+ window.gBrowser.browsers.forEach(function (browser) {
+ let topContentWin = browser.contentWindow;
+ let allContentWins = Array.slice(topContentWin.frames);
+ allContentWins.push(topContentWin);
+ allContentWins.forEach(function (contentWin) {
+ if (contentWin.document.readyState == "complete")
+ this._registerContentWin(contentWin);
+ }, this);
+ }, this);
+
+ // Add all top-level items and, recursively, their child items to the new
+ // browser window.
+ function addItemTree(item, parentMenu) {
+ browserWin.registerItem(item);
+ if (parentMenu)
+ browserWin.addItemToMenu(item, parentMenu);
+ else
+ browserWin.addTopLevelItem(item);
+ if (item instanceof Menu)
+ item.items.forEach(function (subitem) addItemTree(subitem, item));
+ }
+ this.topLevelItems.forEach(function (item) addItemTree(item, null));
+ },
+
+ // Unregisters a browser window from the manager. This is a WindowTracker
+ // callback. Note that this is called in two cases: for each newly closed
+ // chrome window, and for each chrome window that is open when this module is
+ // unloaded.
+ onUntrack: function BM_onUntrack(window) {
+ if (!this._isBrowserWindow(window))
+ return;
+
+ // Remove the window from the window list.
+ let idx = 0;
+ for (; idx < this.browserWins.length; idx++)
+ if (this.browserWins[idx].window == window)
+ break;
+ if (idx == this.browserWins.length)
+ throw new Error("Internal error: browser window not found");
+ let browserWin = this.browserWins.splice(idx, 1)[0];
+
+ // Remove all top-level items from the window.
+ this.topLevelItems.forEach(function (i) browserWin.removeTopLevelItem(i));
+ browserWin.destroy();
+ },
+
+ _isBrowserWindow: function BM__isBrowserWindow(win) {
+ let winType = win.document.documentElement.getAttribute("windowtype");
+ return winType === "navigator:browser";
+ }
+};
+
+
+// Responsible for creating and managing context menu item DOM elements for a
+// browser window. Also responsible for providing a description of the window's
+// current context and determining whether an item matches the current context.
+//
+// TODO: If other apps besides Firefox want to support the context menu in
+// whatever way is appropriate for them, plugging in a substitute for or an
+// adapter to this class should be the way to do it. Make it easy for them.
+// See bug 560716.
+function BrowserWindow(window) {
+ this.window = window;
+ this.doc = window.document;
+
+ let popupDOMElt = this.doc.getElementById("contentAreaContextMenu");
+ if (!popupDOMElt)
+ throw new Error("Internal error: Context menu popup not found.");
+ this.contextMenuPopup = new ContextMenuPopup(popupDOMElt, this);
+
+ // item ID => { item, domElt, overflowDOMElt, popup, overflowPopup }
+ // item may or may not be top-level. domElt is the item's DOM element
+ // contained in the subtree rooted in the top-level context menu.
+ // overflowDOMElt is the item's DOM element contained in the subtree rooted in
+ // the overflow submenu. popup and overflowPopup are only defined if the item
+ // is a Menu; they're the Popup instances containing the Menu's child items,
+ // with the aforementioned distinction between top-level and overflow
+ // subtrees.
+ this.items = {};
+}
+
+BrowserWindow.prototype = {
+
+ // Creates and stores DOM elements for the given item, top-level or not.
+ registerItem: function BW_registerItem(item) {
+ // this.items[id] is referenced by _makeMenu, so it needs to be defined
+ // before _makeDOMElt is called.
+ let props = { item: item };
+ this.items[privateItem(item)._id] = props;
+ props.domElt = this._makeDOMElt(item, false);
+ props.overflowDOMElt = this._makeDOMElt(item, true);
+ },
+
+ // Removes the given item's DOM elements from the store.
+ unregisterItem: function BW_unregisterItem(item) {
+ delete this.items[privateItem(item)._id];
+ },
+
+ addTopLevelItem: function BW_addTopLevelItem(item) {
+ this.contextMenuPopup.addItem(item);
+ },
+
+ removeTopLevelItem: function BW_removeTopLevelItem(item) {
+ this.contextMenuPopup.removeItem(item);
+ },
+
+ addItemToMenu: function BW_addItemToMenu(item, parentMenu) {
+ let { popup, overflowPopup } = this.items[privateItem(parentMenu)._id];
+ popup.addItem(item);
+ overflowPopup.addItem(item);
+ },
+
+ removeItemFromMenu: function BW_removeItemFromMenu(item, parentMenu) {
+ let { popup, overflowPopup } = this.items[privateItem(parentMenu)._id];
+ popup.removeItem(item);
+ overflowPopup.removeItem(item);
+ },
+
+ setItemLabel: function BW_setItemLabel(item, label) {
+ let privates = privateItem(item);
+ let { domElt, overflowDOMElt } = this.items[privates._id];
+ this._setDOMEltLabel(domElt, label);
+ this._setDOMEltLabel(overflowDOMElt, label);
+ if (!item.parentMenu && privates._hasFinishedInit)
+ this.contextMenuPopup.itemLabelDidChange(item);
+ },
+
+ _setDOMEltLabel: function BW__setDOMEltLabel(domElt, label) {
+ domElt.setAttribute("label", label);
+ },
+
+ setItemImage: function BW_setItemImage(item, imageURL) {
+ let { domElt, overflowDOMElt } = this.items[privateItem(item)._id];
+ let isMenu = item instanceof Menu;
+ this._setDOMEltImage(domElt, imageURL, isMenu);
+ this._setDOMEltImage(overflowDOMElt, imageURL, isMenu);
+ },
+
+ _setDOMEltImage: function BW__setDOMEltImage(domElt, imageURL, isMenu) {
+ if (!imageURL) {
+ domElt.removeAttribute("image");
+ domElt.classList.remove("menu-iconic");
+ domElt.classList.remove("menuitem-iconic");
+ }
+ else {
+ domElt.setAttribute("image", imageURL);
+ domElt.classList.add(isMenu ? "menu-iconic" : "menuitem-iconic");
+ }
+ },
+
+ setItemData: function BW_setItemData(item, data) {
+ let { domElt, overflowDOMElt } = this.items[privateItem(item)._id];
+ this._setDOMEltData(domElt, data);
+ this._setDOMEltData(overflowDOMElt, data);
+ },
+
+ _setDOMEltData: function BW__setDOMEltData(domElt, data) {
+ domElt.setAttribute("value", data);
+ },
+
+ // The context specified for a top-level item may not match exactly the real
+ // context that triggers it. For example, if the user context-clicks a span
+ // inside an anchor, we want items that specify an anchor context to be
+ // triggered, but the real context will indicate that the span was clicked,
+ // not the anchor. Where the real context and an item's context conflict,
+ // clients should be given the item's context, and this method can be used to
+ // make such adjustments. Returns an adjusted popupNode.
+ adjustPopupNode: function BW_adjustPopupNode(popupNode, topLevelItem) {
+ for (let ctxt in topLevelItem.context) {
+ if (typeof(ctxt.adjustPopupNode) === "function") {
+ let ctxtNode = ctxt.adjustPopupNode(popupNode);
+ if (ctxtNode) {
+ popupNode = ctxtNode;
+ break;
+ }
+ }
+ }
+ return popupNode;
+ },
+
+ // Returns true if all of item's contexts are current in the window.
+ areAllContextsCurrent: function BW_areAllContextsCurrent(item, popupNode) {
+ let win = popupNode.ownerDocument.defaultView;
+ let worker = privateItem(item)._workerReg.find(win);
+
+ // If the worker for the item-window pair doesn't exist (e.g., because the
+ // page hasn't loaded yet), we can't really make a good decision since the
+ // content script may have a context listener. So just don't show the item
+ // at all.
+ if (!worker)
+ return false;
+
+ // If there are no contexts given at all, the page context applies.
+ let hasContentContext = worker.anyContextListeners();
+ if (!hasContentContext && !item.context.length)
+ return new PageContext().isCurrent(popupNode);
+
+ // Otherwise, determine if all given contexts are current. Evaluate the
+ // declarative contexts first and the worker's context listeners last. That
+ // way the listener might be able to avoid some work.
+ let curr = true;
+ for (let ctxt in item.context) {
+ curr = curr && ctxt.isCurrent(popupNode);
+ if (!curr)
+ return false;
+ }
+ return !hasContentContext || worker.isAnyContextCurrent(popupNode);
+ },
+
+ // Sets this.popupNode to the node the user context-clicked to invoke the
+ // context menu. For Gecko 2.0 and later, triggerNode is this node; if it's
+ // falsey, document.popupNode is used. Returns the popupNode.
+ capturePopupNode: function BW_capturePopupNode(triggerNode) {
+ this.popupNode = triggerNode || this.doc.popupNode;
+ if (!this.popupNode)
+ console.warn("popupNode is null.");
+ return this.popupNode;
+ },
+
+ destroy: function BW_destroy() {
+ this.contextMenuPopup.destroy();
+ delete this.window;
+ delete this.doc;
+ delete this.items;
+ },
+
+ // Emits a click event in the port of the content worker related to given top-
+ // level item and popupNode's content window. Listeners will be passed
+ // popupNode and clickedItemData.
+ fireClick: function BW_fireClick(topLevelItem, popupNode, clickedItemData) {
+ let win = popupNode.ownerDocument.defaultView;
+ let worker = privateItem(topLevelItem)._workerReg.find(win);
+ if (worker)
+ worker.fireClick(popupNode, clickedItemData);
+ },
+
+ _makeDOMElt: function BW__makeDOMElt(item, isInOverflowSubtree) {
+ let elt = item instanceof Item ? this._makeMenuitem(item) :
+ item instanceof Menu ? this._makeMenu(item, isInOverflowSubtree) :
+ item instanceof Separator ? this._makeSeparator() :
+ null;
+ if (!elt)
+ throw new Error("Internal error: can't make element, unknown item type");
+
+ elt.id = domEltIDFromItemID(privateItem(item)._id, isInOverflowSubtree);
+ elt.classList.add(ITEM_CLASS);
+ return elt;
+ },
+
+ // Returns a new xul:menu representing the menu.
+ _makeMenu: function BW__makeMenu(menu, isInOverflowSubtree) {
+ let menuElt = this.doc.createElement("menu");
+ this._setDOMEltLabel(menuElt, menu.label);
+ if (menu.image)
+ this._setDOMEltImage(menuElt, menu.image, true);
+ let popupElt = this.doc.createElement("menupopup");
+ menuElt.appendChild(popupElt);
+
+ let popup = new Popup(popupElt, this, isInOverflowSubtree);
+ let props = this.items[privateItem(menu)._id];
+ if (isInOverflowSubtree)
+ props.overflowPopup = popup;
+ else
+ props.popup = popup;
+
+ return menuElt;
+ },
+
+ // Returns a new xul:menuitem representing the item.
+ _makeMenuitem: function BW__makeMenuitem(item) {
+ let elt = this.doc.createElement("menuitem");
+ this._setDOMEltLabel(elt, item.label);
+ if (item.image)
+ this._setDOMEltImage(elt, item.image, false);
+ if (item.data)
+ this._setDOMEltData(elt, item.data);
+ return elt;
+ },
+
+ // Returns a new xul:menuseparator.
+ _makeSeparator: function BW__makeSeparator() {
+ return this.doc.createElement("menuseparator");
+ }
+};
+
+
+// Responsible for adding DOM elements to and removing them from poupDOMElt.
+function Popup(popupDOMElt, browserWin, isInOverflowSubtree) {
+ this.popupDOMElt = popupDOMElt;
+ this.browserWin = browserWin;
+ this.isInOverflowSubtree = isInOverflowSubtree;
+}
+
+Popup.prototype = {
+
+ addItem: function Popup_addItem(item) {
+ let props = this.browserWin.items[privateItem(item)._id];
+ let elt = this.isInOverflowSubtree ? props.overflowDOMElt : props.domElt;
+ this.popupDOMElt.appendChild(elt);
+ },
+
+ removeItem: function Popup_removeItem(item) {
+ let props = this.browserWin.items[privateItem(item)._id];
+ let elt = this.isInOverflowSubtree ? props.overflowDOMElt : props.domElt;
+ this.popupDOMElt.removeChild(elt);
+ }
+};
+
+
+// Represents a browser window's context menu popup. Responsible for hiding and
+// showing items according to the browser window's current context and for
+// handling item clicks.
+function ContextMenuPopup(popupDOMElt, browserWin) {
+ this.popupDOMElt = popupDOMElt;
+ this.browserWin = browserWin;
+ this.doc = popupDOMElt.ownerDocument;
+
+ // item ID => item
+ // Calling this variable "topLevelItems" is redundant, since Popup and
+ // ContextMenuPopup are only responsible for their child items, not all their
+ // descendant items. But calling it "items" might encourage one to believe
+ // otherwise, so topLevelItems it is.
+ this.topLevelItems = {};
+
+ popupDOMElt.addEventListener("popupshowing", this, false);
+ popupDOMElt.addEventListener("command", this, false);
+}
+
+ContextMenuPopup.prototype = {
+
+ addItem: function CMP_addItem(item) {
+ this._ensureStaticEltsExist();
+ let itemID = privateItem(item)._id;
+ this.topLevelItems[itemID] = item;
+ let props = this.browserWin.items[itemID];
+ props.domElt.classList.add(TOPLEVEL_ITEM_CLASS);
+ props.overflowDOMElt.classList.add(OVERFLOW_ITEM_CLASS);
+ this._insertItemInSortedOrder(item);
+ },
+
+ removeItem: function CMP_removeItem(item) {
+ let itemID = privateItem(item)._id;
+ delete this.topLevelItems[itemID];
+ let { domElt, overflowDOMElt } = this.browserWin.items[itemID];
+ domElt.classList.remove(TOPLEVEL_ITEM_CLASS);
+ overflowDOMElt.classList.remove(OVERFLOW_ITEM_CLASS);
+ this.popupDOMElt.removeChild(domElt);
+ this._overflowPopup.removeChild(overflowDOMElt);
+ },
+
+ // Call this after the item's label changes. This re-inserts the item into
+ // the popup so that it remains in sorted order.
+ itemLabelDidChange: function CMP_itemLabelDidChange(item) {
+ let itemID = privateItem(item)._id;
+ let { domElt, overflowDOMElt } = this.browserWin.items[itemID];
+ this.popupDOMElt.removeChild(domElt);
+ this._overflowPopup.removeChild(overflowDOMElt);
+ this._insertItemInSortedOrder(item);
+ },
+
+ destroy: function CMP_destroy() {
+ // If there are no more items from any instance of the module, remove the
+ // separator and overflow submenu, if they exist.
+ let elts = this._topLevelElts;
+ if (!elts.length) {
+ let submenu = this._overflowMenu;
+ if (submenu)
+ this.popupDOMElt.removeChild(submenu);
+
+ let sep = this._separator;
+ if (sep)
+ this.popupDOMElt.removeChild(sep);
+ }
+
+ this.popupDOMElt.removeEventListener("popupshowing", this, false);
+ this.popupDOMElt.removeEventListener("command", this, false);
+ },
+
+ handleEvent: function CMP_handleEvent(event) {
+ try {
+ if (event.type === "command")
+ this._handleClick(event.target);
+ else if (event.type === "popupshowing" &&
+ event.target === this.popupDOMElt)
+ this._handlePopupShowing();
+ }
+ catch (err) {
+ console.exception(err);
+ }
+ },
+
+ // command events bubble to the context menu's top-level xul:menupopup and are
+ // caught here.
+ _handleClick: function CMP__handleClick(clickedDOMElt) {
+ if (!clickedDOMElt.classList.contains(ITEM_CLASS))
+ return;
+ let itemID = itemIDFromDOMEltID(clickedDOMElt.id);
+ if (itemID < 0)
+ return;
+ let { item, domElt, overflowDOMElt } = this.browserWin.items[itemID];
+
+ // Bail if the DOM element was not created by this module instance. In
+ // real-world add-ons, the itemID < 0 check above is sufficient, but for the
+ // unit test the JID never changes, making this necessary.
+ if (clickedDOMElt != domElt && clickedDOMElt != overflowDOMElt)
+ return;
+
+ let topLevelItem = privateItem(item)._topLevelItem;
+ let popupNode = this.browserWin.adjustPopupNode(this.browserWin.popupNode,
+ topLevelItem);
+ this.browserWin.fireClick(topLevelItem, popupNode, item.data);
+ },
+
+ // popupshowing is used to show top-level items that match the browser
+ // window's current context and hide items that don't. Each module instance
+ // is responsible for showing and hiding the items it owns.
+ _handlePopupShowing: function CMP__handlePopupShowing() {
+ // If there are items queued up to finish initializing, let them go first.
+ // Otherwise the overflow submenu and menu separator may end up in an
+ // inappropriate state when those items are later added to the menu.
+ if (numItemsWithUnfinishedInit) {
+ const self = this;
+ timer.setTimeout(function popupShowingTryAgain() {
+ self._handlePopupShowing();
+ }, 0);
+ return;
+ }
+
+ // popupDOMElt.triggerNode was added in Gecko 2.0 by bug 383930. The || is
+ // to avoid a Spidermonkey strict warning on earlier versions.
+ let triggerNode = this.popupDOMElt.triggerNode || undefined;
+ let popupNode = this.browserWin.capturePopupNode(triggerNode);
+
+ // Show and hide items. Set a "jetpackContextCurrent" property on the
+ // DOM elements to signal which of our items match the current context.
+ for (let [itemID, item] in Iterator(this.topLevelItems)) {
+ let areContextsCurr =
+ this.browserWin.areAllContextsCurrent(item, popupNode);
+
+ // Change the item's label if the return value was a string.
+ if (typeof(areContextsCurr) === "string") {
+ item.label = areContextsCurr;
+ areContextsCurr = true;
+ }
+
+ let { domElt, overflowDOMElt } = this.browserWin.items[itemID];
+ domElt.jetpackContextCurrent = areContextsCurr;
+ domElt.hidden = !areContextsCurr;
+ overflowDOMElt.jetpackContextCurrent = areContextsCurr;
+ overflowDOMElt.hidden = !areContextsCurr;
+ }
+
+ // Get the total number of items that match the current context. It's a
+ // little tricky: There may be other instances of this module loaded,
+ // each hiding and showing their items. So we can't base this number on
+ // only our items, or on the hidden state of items. That's why we set
+ // the jetpackContextCurrent property above. The last instance to run
+ // will leave the menupopup in the correct state.
+ let elts = this._topLevelElts;
+ let numShown = Array.reduce(elts, function (total, elt) {
+ return total + (elt.jetpackContextCurrent ? 1 : 0);
+ }, 0);
+
+ // If too many items are shown, show the submenu and hide the top-level
+ // items. Otherwise, hide the submenu and show the top-level items.
+ let overflow = numShown > this._overflowThreshold;
+ if (overflow)
+ Array.forEach(elts, function (e) e.hidden = true);
+
+ let submenu = this._overflowMenu;
+ if (submenu)
+ submenu.hidden = !overflow;
+
+ // If no items are shown, hide the menu separator.
+ let sep = this._separator;
+ if (sep)
+ sep.hidden = numShown === 0;
+ },
+
+ // Adds the menu separator and overflow submenu if they don't exist.
+ _ensureStaticEltsExist: function CMP__ensureStaticEltsExist() {
+ let sep = this._separator;
+ if (!sep) {
+ sep = this._makeSeparator();
+ this.popupDOMElt.appendChild(sep);
+ }
+
+ let submenu = this._overflowMenu;
+ if (!submenu) {
+ submenu = this._makeOverflowMenu();
+ submenu.hidden = true;
+ this.popupDOMElt.insertBefore(submenu, sep.nextSibling);
+ }
+ },
+
+ // Inserts the given item's DOM element into the popup in sorted order.
+ _insertItemInSortedOrder: function CMP__insertItemInSortedOrder(item) {
+ let props = this.browserWin.items[privateItem(item)._id];
+ this.popupDOMElt.insertBefore(
+ props.domElt, insertionPoint(item.label, this._topLevelElts));
+ this._overflowPopup.insertBefore(
+ props.overflowDOMElt, insertionPoint(item.label, this._overflowElts));
+ },
+
+ // Creates and returns the xul:menu that's shown when too many items are added
+ // to the popup.
+ _makeOverflowMenu: function CMP__makeOverflowMenu() {
+ let submenu = this.doc.createElement("menu");
+ submenu.id = OVERFLOW_MENU_ID;
+ submenu.setAttribute("label", OVERFLOW_MENU_LABEL);
+ let popup = this.doc.createElement("menupopup");
+ popup.id = OVERFLOW_POPUP_ID;
+ submenu.appendChild(popup);
+ return submenu;
+ },
+
+ // Creates and returns the xul:menuseparator that separates the standard
+ // context menu items from our items.
+ _makeSeparator: function CMP__makeSeparator() {
+ let elt = this.doc.createElement("menuseparator");
+ elt.id = SEPARATOR_ID;
+ return elt;
+ },
+
+ // Returns the item elements contained in the overflow menu, a NodeList.
+ get _overflowElts() {
+ return this._overflowPopup.getElementsByClassName(OVERFLOW_ITEM_CLASS);
+ },
+
+ // Returns the overflow xul:menu.
+ get _overflowMenu() {
+ return this.doc.getElementById(OVERFLOW_MENU_ID);
+ },
+
+ // Returns the overflow xul:menupopup.
+ get _overflowPopup() {
+ return this.doc.getElementById(OVERFLOW_POPUP_ID);
+ },
+
+ // Returns the OVERFLOW_THRESH_PREF pref value if it exists or
+ // OVERFLOW_THRESH_DEFAULT if it doesn't.
+ get _overflowThreshold() {
+ let prefs = require("api-utils/preferences-service");
+ return prefs.get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT);
+ },
+
+ // Returns the xul:menuseparator.
+ get _separator() {
+ return this.doc.getElementById(SEPARATOR_ID);
+ },
+
+ // Returns the item elements contained in the top-level menu, a NodeList.
+ get _topLevelElts() {
+ return this.popupDOMElt.getElementsByClassName(TOPLEVEL_ITEM_CLASS);
+ }
+};
+
+
+// Init the browserManager only after setting prototypes and such above, because
+// it will cause browserManager.onTrack to be called immediately if there are
+// open windows.
+browserManager.init();
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/lib/hotkeys.js b/tools/addon-sdk-1.3/packages/addon-kit/lib/hotkeys.js
new file mode 100644
index 0000000..f8c6434
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/lib/hotkeys.js
@@ -0,0 +1,71 @@
+/* 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 INVALID_HOTKEY = "Hotkey must have at least one modifier.";
+
+const { toJSON: jsonify, toString: stringify,
+ isFunctionKey } = require("api-utils/keyboard/utils");
+const { register, unregister } = require("api-utils/keyboard/hotkeys");
+
+const Hotkey = exports.Hotkey = function Hotkey(options) {
+ if (!(this instanceof Hotkey))
+ return new Hotkey(options);
+
+ // Parsing key combination string.
+ let hotkey = jsonify(options.combo);
+ if (!isFunctionKey(hotkey.key) && !hotkey.modifiers.length) {
+ throw new TypeError(INVALID_HOTKEY);
+ }
+
+ this.onPress = options.onPress;
+ this.toString = stringify.bind(null, hotkey);
+ // Registering listener on keyboard combination enclosed by this hotkey.
+ // Please note that `this.toString()` is a normalized version of
+ // `options.combination` where order of modifiers is sorted and `accel` is
+ // replaced with platform specific key.
+ register(this.toString(), this.onPress);
+ // We freeze instance before returning it in order to make it's properties
+ // read-only.
+ return Object.freeze(this);
+};
+Hotkey.prototype.destroy = function destroy() {
+ unregister(this.toString(), this.onPress);
+};
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/lib/notifications.js b/tools/addon-sdk-1.3/packages/addon-kit/lib/notifications.js
new file mode 100644
index 0000000..f2a4ea2
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/lib/notifications.js
@@ -0,0 +1,112 @@
+/* -*- 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, Cr } = require("chrome");
+const apiUtils = require("api-utils/api-utils");
+const errors = require("api-utils/errors");
+
+try {
+ let alertServ = Cc["@mozilla.org/alerts-service;1"].
+ getService(Ci.nsIAlertsService);
+
+ // The unit test sets this to a mock notification function.
+ var notify = alertServ.showAlertNotification.bind(alertServ);
+}
+catch (err) {
+ // An exception will be thrown if the platform doesn't provide an alert
+ // service, e.g., if Growl is not installed on OS X. In that case, use a
+ // mock notification function that just logs to the console.
+ notify = notifyUsingConsole;
+}
+
+exports.notify = function notifications_notify(options) {
+ let valOpts = validateOptions(options);
+ let clickObserver = !valOpts.onClick ? null : {
+ observe: function notificationClickObserved(subject, topic, data) {
+ if (topic === "alertclickcallback")
+ errors.catchAndLog(valOpts.onClick).call(exports, valOpts.data);
+ }
+ };
+ function notifyWithOpts(notifyFn) {
+ notifyFn(valOpts.iconURL, valOpts.title, valOpts.text, !!clickObserver,
+ valOpts.data, clickObserver);
+ }
+ try {
+ notifyWithOpts(notify);
+ }
+ catch (err if err instanceof Ci.nsIException &&
+ err.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
+ console.warn("The notification icon named by " + valOpts.iconURL +
+ " does not exist. A default icon will be used instead.");
+ delete valOpts.iconURL;
+ notifyWithOpts(notify);
+ }
+ catch (err) {
+ notifyWithOpts(notifyUsingConsole);
+ }
+};
+
+function notifyUsingConsole(iconURL, title, text) {
+ title = title ? "[" + title + "]" : "";
+ text = text || "";
+ let str = [title, text].filter(function (s) s).join(" ");
+ console.log(str);
+}
+
+function validateOptions(options) {
+ return apiUtils.validateOptions(options, {
+ data: {
+ is: ["string", "undefined"]
+ },
+ iconURL: {
+ is: ["string", "undefined"]
+ },
+ onClick: {
+ is: ["function", "undefined"]
+ },
+ text: {
+ is: ["string", "undefined"]
+ },
+ title: {
+ is: ["string", "undefined"]
+ }
+ });
+}
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/lib/page-mod.js b/tools/addon-sdk-1.3/packages/addon-kit/lib/page-mod.js
new file mode 100644
index 0000000..d511464
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/lib/page-mod.js
@@ -0,0 +1,229 @@
+/* -*- 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 Packages.
+ *
+ * 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>
+ *
+ * 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 observers = require("api-utils/observer-service");
+const { Worker, Loader } = require('api-utils/content');
+const { EventEmitter } = require('api-utils/events');
+const { List } = require('api-utils/list');
+const { Registry } = require('api-utils/utils/registry');
+const xulApp = require("api-utils/xul-app");
+const { MatchPattern } = require('api-utils/match-pattern');
+
+// Whether or not the host application dispatches a document-element-inserted
+// notification when the document element is inserted into the DOM of a page.
+// The notification was added in Gecko 2.0b6, it's a better time to attach
+// scripts with contentScriptWhen "start" than content-document-global-created,
+// since libraries like jQuery assume the presence of the document element.
+const HAS_DOCUMENT_ELEMENT_INSERTED =
+ xulApp.versionInRange(xulApp.platformVersion, "2.0b6", "*");
+const ON_CONTENT = HAS_DOCUMENT_ELEMENT_INSERTED ? 'document-element-inserted' :
+ 'content-document-global-created';
+
+// Workaround bug 642145: document-element-inserted is fired multiple times.
+// This bug is fixed in Firefox 4.0.1, but we want to keep FF 4.0 compatibility
+// Tracking bug 641457. To be removed when 4.0 has disappeared from earth.
+const HAS_BUG_642145_FIXED =
+ xulApp.versionInRange(xulApp.platformVersion, "2.0.1", "*");
+
+// rules registry
+const RULES = {};
+
+const Rules = EventEmitter.resolve({ toString: null }).compose(List, {
+ add: function() Array.slice(arguments).forEach(function onAdd(rule) {
+ if (this._has(rule))
+ return;
+ // registering rule to the rules registry
+ if (!(rule in RULES))
+ RULES[rule] = new MatchPattern(rule);
+ this._add(rule);
+ this._emit('add', rule);
+ }.bind(this)),
+ remove: function() Array.slice(arguments).forEach(function onRemove(rule) {
+ if (!this._has(rule))
+ return;
+ this._remove(rule);
+ this._emit('remove', rule);
+ }.bind(this)),
+});
+
+/**
+ * PageMod constructor (exported below).
+ * @constructor
+ */
+const PageMod = Loader.compose(EventEmitter, {
+ on: EventEmitter.required,
+ _listeners: EventEmitter.required,
+ contentScript: Loader.required,
+ contentScriptFile: Loader.required,
+ contentScriptWhen: Loader.required,
+ include: null,
+ constructor: function PageMod(options) {
+ this._onContent = this._onContent.bind(this);
+ options = options || {};
+
+ if ('contentScript' in options)
+ this.contentScript = options.contentScript;
+ if ('contentScriptFile' in options)
+ this.contentScriptFile = options.contentScriptFile;
+ if ('contentScriptWhen' in options)
+ this.contentScriptWhen = options.contentScriptWhen;
+ if ('onAttach' in options)
+ this.on('attach', options.onAttach);
+ if ('onError' in options)
+ this.on('error', options.onError);
+
+ let include = options.include;
+ let rules = this.include = Rules();
+ rules.on('add', this._onRuleAdd = this._onRuleAdd.bind(this));
+ rules.on('remove', this._onRuleRemove = this._onRuleRemove.bind(this));
+
+ if (Array.isArray(include))
+ rules.add.apply(null, include);
+ else
+ rules.add(include);
+
+ this.on('error', this._onUncaughtError = this._onUncaughtError.bind(this));
+ pageModManager.add(this._public);
+
+ this._loadingWindows = [];
+ },
+
+ destroy: function destroy() {
+ for each (let rule in this.include)
+ this.include.remove(rule);
+ pageModManager.remove(this._public);
+ this._loadingWindows = [];
+ },
+
+ _loadingWindows: [],
+
+ _onContent: function _onContent(window) {
+ // not registered yet
+ if (!pageModManager.has(this))
+ return;
+
+ if (!HAS_BUG_642145_FIXED) {
+ if (this._loadingWindows.indexOf(window) != -1)
+ return;
+ this._loadingWindows.push(window);
+ }
+
+ if ('start' == this.contentScriptWhen) {
+ this._createWorker(window);
+ return;
+ }
+
+ let eventName = 'end' == this.contentScriptWhen ? 'load' : 'DOMContentLoaded';
+ let self = this;
+ window.addEventListener(eventName, function onReady(event) {
+ if (event.target.defaultView != window)
+ return;
+ window.removeEventListener(eventName, onReady, true);
+
+ self._createWorker(window);
+ }, true);
+ },
+ _createWorker: function _createWorker(window) {
+ let worker = Worker({
+ window: window,
+ contentScript: this.contentScript,
+ contentScriptFile: this.contentScriptFile,
+ onError: this._onUncaughtError
+ });
+ this._emit('attach', worker);
+ let self = this;
+ worker.once('detach', function detach() {
+ worker.destroy();
+
+ if (!HAS_BUG_642145_FIXED) {
+ let idx = self._loadingWindows.indexOf(window);
+ if (idx != -1)
+ self._loadingWindows.splice(idx, 1);
+ }
+ });
+ },
+ _onRuleAdd: function _onRuleAdd(url) {
+ pageModManager.on(url, this._onContent);
+ },
+ _onRuleRemove: function _onRuleRemove(url) {
+ pageModManager.off(url, this._onContent);
+ },
+ _onUncaughtError: function _onUncaughtError(e) {
+ if (this._listeners('error').length == 1)
+ console.exception(e);
+ }
+});
+exports.PageMod = function(options) PageMod(options)
+exports.PageMod.prototype = PageMod.prototype;
+
+const PageModManager = Registry.resolve({
+ constructor: '_init',
+ _destructor: '_registryDestructor'
+}).compose({
+ constructor: function PageModRegistry(constructor) {
+ this._init(PageMod);
+ observers.add(
+ ON_CONTENT, this._onContentWindow = this._onContentWindow.bind(this)
+ );
+ },
+ _destructor: function _destructor() {
+ observers.remove(ON_CONTENT, this._onContentWindow);
+ for (let rule in RULES) {
+ this._removeAllListeners(rule);
+ delete RULES[rule];
+ }
+ this._registryDestructor();
+ },
+ _onContentWindow: function _onContentWindow(domObj) {
+ let window = HAS_DOCUMENT_ELEMENT_INSERTED ? domObj.defaultView : domObj;
+ // XML documents don't have windows, and we don't yet support them.
+ if (!window)
+ return;
+ for (let rule in RULES)
+ if (RULES[rule].test(window.document.URL))
+ this._emit(rule, window);
+ },
+ off: function off(topic, listener) {
+ this.removeListener(topic, listener);
+ if (!this._listeners(topic).length)
+ delete RULES[topic];
+ }
+});
+const pageModManager = PageModManager();
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/lib/page-worker.js b/tools/addon-sdk-1.3/packages/addon-kit/lib/page-worker.js
new file mode 100644
index 0000000..93750a1
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/lib/page-worker.js
@@ -0,0 +1,101 @@
+/* -*- 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>
+ * 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 { Symbiont } = require("api-utils/content");
+const { Trait } = require("api-utils/traits");
+
+if (!require("api-utils/xul-app").isOneOf(["Firefox", "Thunderbird"])) {
+ throw new Error([
+ "The page-worker 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(""));
+}
+
+const Page = Trait.compose(
+ Symbiont.resolve({
+ constructor: '_initSymbiont'
+ }),
+ {
+ _frame: Trait.required,
+ _initFrame: Trait.required,
+ postMessage: Symbiont.required,
+ on: Symbiont.required,
+ destroy: Symbiont.required,
+
+ constructor: function Page(options) {
+ options = options || {};
+
+ this.contentURL = 'contentURL' in options ? options.contentURL
+ : 'about:blank';
+ 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);
+
+ this.on('propertyChange', this._onChange.bind(this));
+
+ this._initSymbiont();
+ },
+
+ _onChange: function _onChange(e) {
+ if ('contentURL' in e && this._frame) {
+ // Cleanup the worker before injecting the content script in the new
+ // document
+ this._workerCleanup();
+ this._initFrame(this._frame);
+ }
+ }
+ }
+);
+exports.Page = function(options) Page(options);
+exports.Page.prototype = Page.prototype;
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/lib/panel.js b/tools/addon-sdk-1.3/packages/addon-kit/lib/panel.js
new file mode 100644
index 0000000..6fbe4fc
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/lib/panel.js
@@ -0,0 +1,404 @@
+/* ***** 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):
+ * Myk Melez <myk@mozilla.org> (Original Author)
+ * Irakli Gozalishvili <gozala@mazilla.com>
+ * Mihai Sucan <mihai.sucan@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";
+
+if (!require("api-utils/xul-app").is("Firefox")) {
+ throw new Error([
+ "The panel module currently supports only Firefox. In the future ",
+ "we would like it to support other applications, however. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps ",
+ "for more information."
+ ].join(""));
+}
+
+const { Ci } = require("chrome");
+const { validateOptions: valid } = require("api-utils/api-utils");
+const { Symbiont } = require("api-utils/content");
+const { EventEmitter } = require('api-utils/events');
+const timer = require("api-utils/timer");
+const runtime = require("api-utils/runtime");
+
+require("api-utils/xpcom").utils.defineLazyServiceGetter(
+ this,
+ "windowMediator",
+ "@mozilla.org/appshell/window-mediator;1",
+ "nsIWindowMediator"
+);
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ ON_SHOW = 'popupshown',
+ ON_HIDE = 'popuphidden',
+ validNumber = { is: ['number', 'undefined', 'null'] };
+
+/**
+ * Emits show and hide events.
+ */
+const Panel = Symbiont.resolve({
+ constructor: '_init',
+ _onInit: '_onSymbiontInit',
+ destroy: '_symbiontDestructor',
+ _documentUnload: '_workerDocumentUnload'
+}).compose({
+ _frame: Symbiont.required,
+ _init: Symbiont.required,
+ _onSymbiontInit: Symbiont.required,
+ _symbiontDestructor: Symbiont.required,
+ _emit: Symbiont.required,
+ _asyncEmit: Symbiont.required,
+ on: Symbiont.required,
+ removeListener: Symbiont.required,
+
+ _inited: false,
+
+ /**
+ * If set to `true` frame loaders between xul panel frame and
+ * hidden frame are swapped. If set to `false` frame loaders are
+ * set back to normal. Setting the value that was already set will
+ * have no effect.
+ */
+ set _frameLoadersSwapped(value) {
+ if (this.__frameLoadersSwapped == value) return;
+ this._frame.QueryInterface(Ci.nsIFrameLoaderOwner)
+ .swapFrameLoaders(this._viewFrame);
+ this.__frameLoadersSwapped = value;
+ },
+ __frameLoadersSwapped: false,
+
+ constructor: function Panel(options) {
+ this._onShow = this._onShow.bind(this);
+ this._onHide = this._onHide.bind(this);
+ this.on('inited', this._onSymbiontInit.bind(this));
+
+ options = options || {};
+ if ('onShow' in options)
+ this.on('show', options.onShow);
+ if ('onHide' in options)
+ this.on('hide', options.onHide);
+ if ('width' in options)
+ this.width = options.width;
+ if ('height' in options)
+ this.height = options.height;
+ if ('contentURL' in options)
+ this.contentURL = options.contentURL;
+
+ this._init(options);
+ },
+ _destructor: function _destructor() {
+ this.hide();
+ this._removeAllListeners('show');
+ // defer cleanup to be performed after panel gets hidden
+ this._xulPanel = null;
+ this._symbiontDestructor(this);
+ this._removeAllListeners(this, 'hide');
+ },
+ destroy: function destroy() {
+ this._destructor();
+ },
+ /* Public API: Panel.width */
+ get width() this._width,
+ set width(value)
+ this._width = valid({ $: value }, { $: validNumber }).$ || this._width,
+ _width: 320,
+ /* Public API: Panel.height */
+ get height() this._height,
+ set height(value)
+ this._height = valid({ $: value }, { $: validNumber }).$ || this._height,
+ _height: 240,
+
+ /* Public API: Panel.isShowing */
+ get isShowing() !!this._xulPanel && this._xulPanel.state == "open",
+
+ /* Public API: Panel.show */
+ show: function show(anchor) {
+ anchor = anchor || null;
+ let document = getWindow(anchor).document;
+ let xulPanel = this._xulPanel;
+ if (!xulPanel) {
+ xulPanel = this._xulPanel = document.createElementNS(XUL_NS, 'panel');
+ xulPanel.setAttribute("type", "arrow");
+
+ // One anonymous node has a big padding that doesn't work well with
+ // Jetpack, as we would like to display an iframe that completely fills
+ // the panel.
+ // -> Use a XBL wrapper with inner stylesheet to remove this padding.
+ let css = ".panel-inner-arrowcontent, .panel-arrowcontent {padding: 0;}";
+ let originalXBL = "chrome://global/content/bindings/popup.xml#arrowpanel";
+ let binding =
+ '<bindings xmlns="http://www.mozilla.org/xbl">' +
+ '<binding id="id" extends="' + originalXBL + '">' +
+ '<resources>' +
+ '<stylesheet src="data:text/css,' +
+ document.defaultView.encodeURIComponent(css) + '"/>' +
+ '</resources>' +
+ '</binding>' +
+ '</bindings>';
+ xulPanel.style.MozBinding = 'url("data:text/xml,' +
+ document.defaultView.encodeURIComponent(binding) + '")';
+
+ let frame = document.createElementNS(XUL_NS, 'iframe');
+ frame.setAttribute('type', 'content');
+ frame.setAttribute('flex', '1');
+ frame.setAttribute('transparent', 'transparent');
+ if (runtime.OS === "Darwin") {
+ frame.style.borderRadius = "6px";
+ frame.style.padding = "1px";
+ }
+
+ // Load an empty document in order to have an immediatly loaded iframe,
+ // so swapFrameLoaders is going to work without having to wait for load.
+ frame.setAttribute("src","data:,");
+
+ xulPanel.appendChild(frame);
+ document.getElementById("mainPopupSet").appendChild(xulPanel);
+ }
+ let { width, height } = this, x, y, position;
+
+ if (!anchor) {
+ // Open the popup in the middle of the window.
+ x = document.documentElement.clientWidth / 2 - width / 2;
+ y = document.documentElement.clientHeight / 2 - height / 2;
+ position = null;
+ }
+ else {
+ // Open the popup by the anchor.
+ let rect = anchor.getBoundingClientRect();
+
+ let window = anchor.ownerDocument.defaultView;
+
+ let zoom = window.mozScreenPixelsPerCSSPixel;
+ let screenX = rect.left + window.mozInnerScreenX * zoom;
+ let screenY = rect.top + window.mozInnerScreenY * zoom;
+
+ // Set up the vertical position of the popup relative to the anchor
+ // (always display the arrow on anchor center)
+ let horizontal, vertical;
+ if (screenY > window.screen.availHeight / 2 + height)
+ vertical = "top";
+ else
+ vertical = "bottom";
+
+ if (screenY > window.screen.availWidth / 2 + width)
+ horizontal = "left";
+ else
+ horizontal = "right";
+
+ let verticalInverse = vertical == "top" ? "bottom" : "top";
+ position = vertical + "center " + verticalInverse + horizontal;
+
+ // Allow panel to flip itself if the panel can't be displayed at the
+ // specified position (useful if we compute a bad position or if the
+ // user moves the window and panel remains visible)
+ xulPanel.setAttribute("flip","both");
+ }
+
+ // Resize the iframe instead of using panel.sizeTo
+ // because sizeTo doesn't work with arrow panels
+ xulPanel.firstChild.style.width = width + "px";
+ xulPanel.firstChild.style.height = height + "px";
+
+ // Wait for the XBL binding to be constructed
+ function waitForBinding() {
+ if (!xulPanel.openPopup) {
+ timer.setTimeout(waitForBinding, 50);
+ return;
+ }
+ xulPanel.openPopup(anchor, position, x, y);
+ }
+ waitForBinding();
+
+ return this._public;
+ },
+ /* Public API: Panel.hide */
+ hide: function hide() {
+ // The popuphiding handler takes care of swapping back the frame loaders
+ // and removing the XUL panel from the application window, we just have to
+ // trigger it by hiding the popup.
+ // XXX Sometimes I get "TypeError: xulPanel.hidePopup is not a function"
+ // when quitting the host application while a panel is visible. To suppress
+ // them, this now checks for "hidePopup" in xulPanel before calling it.
+ // It's not clear if there's an actual issue or the error is just normal.
+ let xulPanel = this._xulPanel;
+ if (xulPanel && "hidePopup" in xulPanel)
+ xulPanel.hidePopup();
+ return this._public;
+ },
+
+ /* Public API: Panel.resize */
+ resize: function resize(width, height) {
+ this.width = width;
+ this.height = height;
+ // Resize the iframe instead of using panel.sizeTo
+ // because sizeTo doesn't work with arrow panels
+ let xulPanel = this._xulPanel;
+ if (xulPanel) {
+ xulPanel.firstChild.style.width = width + "px";
+ xulPanel.firstChild.style.height = height + "px";
+ }
+ },
+
+ // While the panel is visible, this is the XUL <panel> we use to display it.
+ // Otherwise, it's null.
+ get _xulPanel() this.__xulPanel,
+ set _xulPanel(value) {
+ let xulPanel = this.__xulPanel;
+ if (value === xulPanel) return;
+ if (xulPanel) {
+ xulPanel.removeEventListener(ON_HIDE, this._onHide, false);
+ xulPanel.removeEventListener(ON_SHOW, this._onShow, false);
+ xulPanel.parentNode.removeChild(xulPanel);
+ }
+ if (value) {
+ value.addEventListener(ON_HIDE, this._onHide, false);
+ value.addEventListener(ON_SHOW, this._onShow, false);
+ }
+ this.__xulPanel = value;
+ },
+ __xulPanel: null,
+ get _viewFrame() this.__xulPanel.children[0],
+ /**
+ * When the XUL panel becomes hidden, we swap frame loaders back to move
+ * the content of the panel to the hidden frame & remove panel element.
+ */
+ _onHide: function _onHide() {
+ try {
+ this._frameLoadersSwapped = false;
+ this._xulPanel = null;
+ this._emit('hide');
+ } catch(e) {
+ this._emit('error', e);
+ }
+ },
+ /**
+ * When the XUL panel becomes shown, we swap frame loaders between panel
+ * frame and hidden frame to preserve state of the content dom.
+ */
+ _onShow: function _onShow() {
+ try {
+ if (!this._inited) // defer if not initialized yet
+ return this.on('inited', this._onShow.bind(this));
+ this._frameLoadersSwapped = true;
+
+ // Retrieve computed text color style in order to apply to the iframe
+ // document. As MacOS background is dark gray, we need to use skin's text
+ // color.
+ let win = this._xulPanel.ownerDocument.defaultView;
+ let node = win.document.getAnonymousElementByAttribute(this._xulPanel,
+ "class", "panel-inner-arrowcontent");
+ let textColor = win.getComputedStyle(node).getPropertyValue("color");
+ let doc = this._xulPanel.firstChild.contentDocument;
+ let style = doc.createElement("style");
+ style.textContent = "body { color: " + textColor + "; }";
+ let container = doc.head ? doc.head : doc.documentElement;
+ if (container.firstChild)
+ container.insertBefore(style, container.firstChild);
+ else
+ container.appendChild(style);
+
+
+ this._emit('show');
+ } catch(e) {
+ this._emit('error', e);
+ }
+ },
+ /**
+ * Notification that panel was fully initialized.
+ */
+ _onInit: function _onInit() {
+ this._inited = true;
+
+ // Avoid panel document from resizing the browser window
+ // New platform capability added through bug 635673
+ if ("allowWindowControl" in this._frame.docShell)
+ this._frame.docShell.allowWindowControl = false;
+
+ // perform all deferred tasks like initSymbiont, show, hide ...
+ // TODO: We're publicly exposing a private event here; this
+ // 'inited' event should really be made private, somehow.
+ this._emit('inited');
+ },
+
+ // Catch document unload event in order to rebind load event listener with
+ // Symbiont._initFrame if Worker._documentUnload destroyed the worker
+ _documentUnload: function(subject, topic, data) {
+ if (this._workerDocumentUnload(subject, topic, data)) {
+ this._initFrame(this._frame);
+ return true;
+ }
+ return false;
+ }
+});
+exports.Panel = function(options) Panel(options)
+exports.Panel.prototype = Panel.prototype;
+
+function getWindow(anchor) {
+ let window;
+
+ if (anchor) {
+ let anchorWindow = anchor.ownerDocument.defaultView.top;
+ let anchorDocument = anchorWindow.document;
+
+ let enumerator = windowMediator.getEnumerator("navigator:browser");
+ while (enumerator.hasMoreElements()) {
+ let enumWindow = enumerator.getNext();
+
+ // Check if the anchor is in this browser window.
+ if (enumWindow == anchorWindow) {
+ window = anchorWindow;
+ break;
+ }
+
+ // Check if the anchor is in a browser tab in this browser window.
+ let browser = enumWindow.gBrowser.getBrowserForDocument(anchorDocument);
+ if (browser) {
+ window = enumWindow;
+ break;
+ }
+
+ // Look in other subdocuments (sidebar, etc.)?
+ }
+ }
+
+ // If we didn't find the anchor's window (or we have no anchor),
+ // return the most recent browser window.
+ if (!window)
+ window = windowMediator.getMostRecentWindow("navigator:browser");
+
+ return window;
+}
+
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/lib/passwords.js b/tools/addon-sdk-1.3/packages/addon-kit/lib/passwords.js
new file mode 100644
index 0000000..8225da9
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/lib/passwords.js
@@ -0,0 +1,92 @@
+/* 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 { Trait } = require("api-utils/light-traits");
+const utils = require("api-utils/passwords/utils");
+const defer = require("api-utils/utils/function").Enqueued;
+
+/**
+ * Utility function that returns `onComplete` and `onError` callbacks form the
+ * given `options` objects. Also properties are removed from the passed
+ * `options` objects.
+ * @param {Object} options
+ * Object that is passed to the exported functions of this module.
+ * @returns {Function[]}
+ * Array with two elements `onComplete` and `onError` functions.
+ */
+function getCallbacks(options) {
+ let value = [
+ 'onComplete' in options ? options.onComplete : null,
+ 'onError' in options ? defer(options.onError) : console.exception
+ ];
+
+ delete options.onComplete;
+ delete options.onError;
+
+ return value;
+};
+
+/**
+ * Creates a wrapper function that tries to call `onComplete` with a return
+ * value of the wrapped function or falls back to `onError` if wrapped function
+ * throws an exception.
+ */
+function createWrapperMethod(wrapped) {
+ return function (options) {
+ let [ onComplete, onError ] = getCallbacks(options);
+ try {
+ let value = wrapped(options);
+ if (onComplete) {
+ defer(function() {
+ try {
+ onComplete(value);
+ } catch (exception) {
+ onError(exception);
+ }
+ })();
+ }
+ } catch (exception) {
+ onError(exception);
+ }
+ };
+}
+
+exports.search = createWrapperMethod(utils.search);
+exports.store = createWrapperMethod(utils.store);
+exports.remove = createWrapperMethod(utils.remove);
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/lib/private-browsing.js b/tools/addon-sdk-1.3/packages/addon-kit/lib/private-browsing.js
new file mode 100644
index 0000000..8336e7b
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/lib/private-browsing.js
@@ -0,0 +1,102 @@
+/* ***** 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 Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Paul O’Shannessy <paul@oshannessy.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 observers = require("api-utils/observer-service");
+const { EventEmitter } = require("api-utils/events");
+const { setTimeout } = require("api-utils/timer");
+const unload = require("api-utils/unload");
+
+const ON_START = "start";
+const ON_STOP = "stop";
+const ON_TRANSITION = "private-browsing-transition-complete";
+
+let pbService;
+// Currently, only Firefox implements the private browsing service.
+if (require("api-utils/xul-app").is("Firefox")) {
+ pbService = Cc["@mozilla.org/privatebrowsing;1"].
+ getService(Ci.nsIPrivateBrowsingService);
+}
+
+function toggleMode(value) pbService.privateBrowsingEnabled = !!value
+
+const privateBrowsing = EventEmitter.compose({
+ constructor: function PrivateBrowsing() {
+ // Binding method to instance since it will be used with `setTimeout`.
+ this._emitOnObject = this._emitOnObject.bind(this);
+ this.unload = this.unload.bind(this);
+ // Report unhandled errors from listeners
+ this.on("error", console.exception.bind(console));
+ unload.ensure(this);
+ // We only need to add observers if `pbService` exists.
+ if (pbService) {
+ observers.add(ON_TRANSITION, this.onTransition.bind(this));
+ this._isActive = pbService.privateBrowsingEnabled;
+ }
+ },
+ unload: function _destructor() {
+ this._removeAllListeners(ON_START);
+ this._removeAllListeners(ON_STOP);
+ },
+ // We don't need to do anything with cancel here.
+ onTransition: function onTransition() {
+ let isActive = this._isActive = pbService.privateBrowsingEnabled;
+ setTimeout(this._emitOnObject, 0, exports, isActive ? ON_START : ON_STOP);
+ },
+ get isActive() this._isActive,
+ set isActive(value) {
+ if (pbService)
+ // We toggle private browsing mode asynchronously in order to work around
+ // bug 659629. Since private browsing transitions are asynchronous
+ // anyway, this doesn't significantly change the behavior of the API.
+ setTimeout(toggleMode, 0, value);
+ },
+ _isActive: false
+})()
+
+Object.defineProperty(exports, "isActive", {
+ get: function() privateBrowsing.isActive
+});
+exports.activate = function activate() privateBrowsing.isActive = true;
+exports.deactivate = function deactivate() privateBrowsing.isActive = false;
+exports.on = privateBrowsing.on;
+exports.once = privateBrowsing.once;
+exports.removeListener = privateBrowsing.removeListener;
+
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/lib/request.js b/tools/addon-sdk-1.3/packages/addon-kit/lib/request.js
new file mode 100644
index 0000000..a72b28c
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/lib/request.js
@@ -0,0 +1,309 @@
+/* ***** 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):
+ * Paul O’Shannessy <paul@oshannessy.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 xpcom = require("api-utils/xpcom");
+const xhr = require("api-utils/xhr");
+const errors = require("api-utils/errors");
+const apiUtils = require("api-utils/api-utils");
+
+// Ugly but will fix with: https://bugzilla.mozilla.org/show_bug.cgi?id=596248
+const EventEmitter = require('api-utils/events').EventEmitter.compose({
+ constructor: function EventEmitter() this
+});
+
+// Instead of creating a new validator for each request, just make one and reuse it.
+const validator = new OptionsValidator({
+ url: {
+ //XXXzpao should probably verify that url is a valid url as well
+ is: ["string"]
+ },
+ headers: {
+ map: function (v) v || {},
+ is: ["object"],
+ },
+ content: {
+ map: function (v) v || null,
+ is: ["string", "object", "null"],
+ },
+ contentType: {
+ map: function (v) v || "application/x-www-form-urlencoded",
+ is: ["string"],
+ },
+ overrideMimeType: {
+ map: function(v) v || null,
+ is: ["string", "null"],
+ }
+});
+
+const REUSE_ERROR = "This request object has been used already. You must " +
+ "create a new one to make a new request."
+
+function Request(options) {
+ const self = EventEmitter(),
+ _public = self._public;
+ // request will hold the actual XHR object
+ let request;
+ let response;
+
+ if ('onComplete' in options)
+ self.on('complete', options.onComplete)
+ options = validator.validateOptions(options);
+
+ // function to prep the request since it's the same between GET and POST
+ function makeRequest(mode) {
+ // If this request has already been used, then we can't reuse it. Throw an error.
+ if (request) {
+ throw new Error(REUSE_ERROR);
+ }
+
+ request = new xhr.XMLHttpRequest();
+
+ let url = options.url;
+ // Build the data to be set. For GET requests, we want to append that to
+ // the URL before opening the request.
+ let data = makeQueryString(options.content);
+ if (mode == "GET" && data) {
+ // If the URL already has ? in it, then we want to just use &
+ url = url + (/\?/.test(url) ? "&" : "?") + data;
+ }
+
+ // open the request
+ request.open(mode, url);
+
+ // request header must be set after open, but before send
+ request.setRequestHeader("Content-Type", options.contentType);
+
+ // set other headers
+ for (let k in options.headers) {
+ request.setRequestHeader(k, options.headers[k]);
+ }
+
+ // set overrideMimeType
+ if (options.overrideMimeType) {
+ request.overrideMimeType(options.overrideMimeType);
+ }
+
+ // handle the readystate, create the response, and call the callback
+ request.onreadystatechange = function () {
+ if (request.readyState == 4) {
+ response = new Response(request);
+ errors.catchAndLog(function () {
+ self._emit('complete', response);
+ })();
+ }
+ }
+
+ // actually send the request. we only want to send data on POST requests
+ request.send(mode == "POST" ? data : null);
+ }
+
+ // Map these setters/getters to the options
+ ["url", "headers", "content", "contentType"].forEach(function (k) {
+ _public.__defineGetter__(k, function () options[k]);
+ _public.__defineSetter__(k, function (v) {
+ // This will automatically rethrow errors from apiUtils.validateOptions.
+ return options[k] = validator.validateSingleOption(k, v);
+ });
+ });
+
+ // response should be available as a getter
+ _public.__defineGetter__("response", function () response);
+
+ _public.get = function () {
+ makeRequest("GET");
+ return this;
+ };
+
+ _public.post = function () {
+ makeRequest("POST");
+ return this;
+ };
+
+ return _public;
+}
+exports.Request = Request;
+
+// Converts an object of unordered key-vals to a string that can be passed
+// as part of a request
+function makeQueryString(content) {
+ // Explicitly return null if we have null, and empty string, or empty object.
+ if (!content) {
+ return null;
+ }
+
+ // If content is already a string, just return it as is.
+ if (typeof(content) == "string") {
+ return content;
+ }
+
+ // At this point we have a k:v object. Iterate over it and encode each value.
+ // Arrays and nested objects will get encoded as needed. For example...
+ //
+ // { foo: [1, 2, { omg: "bbq", "all your base!": "are belong to us" }], bar: "baz" }
+ //
+ // will be encoded as
+ //
+ // foo[0]=1&foo[1]=2&foo[2][omg]=bbq&foo[2][all+your+base!]=are+belong+to+us&bar=baz
+ //
+ // Keys (including "[" and "]") and values will be encoded with
+ // fixedEncodeURIComponent before returning.
+ //
+ // Execution was inspired by jQuery, but some details have changed and numeric
+ // array keys are included (whereas they are not in jQuery).
+
+ let encodedContent = [];
+ function add(key, val) {
+ encodedContent.push(fixedEncodeURIComponent(key) + "=" +
+ fixedEncodeURIComponent(val));
+ }
+
+ function make(key, val) {
+ if (typeof(val) === "object" && val !== null) {
+ for ([k, v] in Iterator(val)) {
+ make(key + "[" + k + "]", v);
+ }
+ }
+ else {
+ add(key, val)
+ }
+ }
+ for ([k, v] in Iterator(content)) {
+ make(k, v);
+ }
+ return encodedContent.join("&");
+
+ //XXXzpao In theory, we can just use a FormData object on 1.9.3, but I had
+ // trouble getting that working. It would also be nice to stay
+ // backwards-compat as long as possible. Keeping this in for now...
+ // let formData = Cc["@mozilla.org/files/formdata;1"].
+ // createInstance(Ci.nsIDOMFormData);
+ // for ([k, v] in Iterator(content)) {
+ // formData.append(k, v);
+ // }
+ // return formData;
+}
+
+
+// encodes a string safely for application/x-www-form-urlencoded
+// adheres to RFC 3986
+// see https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Functions/encodeURIComponent
+function fixedEncodeURIComponent (str) {
+ return encodeURIComponent(str).replace(/%20/g, "+").replace(/!/g, "%21").
+ replace(/'/g, "%27").replace(/\(/g, "%28").
+ replace(/\)/g, "%29").replace(/\*/g, "%2A");
+}
+
+function Response(request) {
+ // Define the straight mappings of our value to original request value
+ xpcom.utils.defineLazyGetter(this, "text", function () request.responseText);
+ xpcom.utils.defineLazyGetter(this, "xml", function () {
+ throw new Error("Sorry, the 'xml' property is no longer available. " +
+ "see bug 611042 for more information.");
+ });
+ xpcom.utils.defineLazyGetter(this, "status", function () request.status);
+ xpcom.utils.defineLazyGetter(this, "statusText", function () request.statusText);
+
+ // this.json should be the JS object, so we need to attempt to parse it.
+ xpcom.utils.defineLazyGetter(this, "json", function () {
+ let _json = null;
+ try {
+ _json = JSON.parse(this.text);
+ }
+ catch (e) {}
+ return _json;
+ });
+
+ // this.headers also should be a JS object, so we need to split up the raw
+ // headers string provided by the request.
+ xpcom.utils.defineLazyGetter(this, "headers", function () {
+ let _headers = {};
+ let lastKey;
+ // Since getAllResponseHeaders() will return null if there are no headers,
+ // defend against it by defaulting to ""
+ let rawHeaders = request.getAllResponseHeaders() || "";
+ rawHeaders.split("\n").forEach(function (h) {
+ // According to the HTTP spec, the header string is terminated by an empty
+ // line, so we can just skip it.
+ if (!h.length) {
+ return;
+ }
+
+ let index = h.indexOf(":");
+ // The spec allows for leading spaces, so instead of assuming a single
+ // leading space, just trim the values.
+ let key = h.substring(0, index).trim(),
+ val = h.substring(index + 1).trim();
+
+ // For empty keys, that means that the header value spanned multiple lines.
+ // In that case we should append the value to the value of lastKey with a
+ // new line. We'll assume lastKey will be set because there should never
+ // be an empty key on the first pass.
+ if (key) {
+ _headers[key] = val;
+ lastKey = key;
+ }
+ else {
+ _headers[lastKey] += "\n" + val;
+ }
+ });
+ return _headers;
+ })
+}
+
+// apiUtils.validateOptions doesn't give the ability to easily validate single
+// options, so this is a wrapper that provides that ability.
+function OptionsValidator(rules) {
+ this.rules = rules;
+
+ this.validateOptions = function (options) {
+ return apiUtils.validateOptions(options, this.rules);
+ }
+
+ this.validateSingleOption = function (field, value) {
+ // We need to create a single rule object from our listed rules. To avoid
+ // JavaScript String warnings, check for the field & default to an empty object.
+ let singleRule = {};
+ if (field in this.rules) {
+ singleRule[field] = this.rules[field];
+ }
+ let singleOption = {};
+ singleOption[field] = value;
+ // This should throw if it's invalid, which will bubble up & out.
+ return apiUtils.validateOptions(singleOption, singleRule)[field];
+ }
+}
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/lib/selection.js b/tools/addon-sdk-1.3/packages/addon-kit/lib/selection.js
new file mode 100644
index 0000000..48db7fc
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/lib/selection.js
@@ -0,0 +1,448 @@
+/* ***** 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):
+ * Eric H. Jung <eric.jung@yahoo.com>
+ * Irakli Gozalishivili <gozala@mozilla.com>
+ * Matteo Ferretti <zer0@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";
+
+if (!require("api-utils/xul-app").is("Firefox")) {
+ throw new Error([
+ "The selection module currently supports only Firefox. In the future ",
+ "we would like it to support other applications, however. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=560716 for more information."
+ ].join(""));
+}
+
+let { Ci } = require("chrome"),
+ { setTimeout } = require("api-utils/timer"),
+ { EventEmitter } = require("api-utils/events");
+
+// The selection type HTML
+const HTML = 0x01;
+
+// The selection type TEXT
+const TEXT = 0x02;
+
+// The selection type DOM (internal use only)
+const DOM = 0x03;
+
+// A more developer-friendly message than the caught exception when is not
+// possible change a selection.
+const ERR_CANNOT_CHANGE_SELECTION =
+ "It isn't possible to change the selection, as there isn't currently a selection";
+
+/**
+ * Creates an object from which a selection can be set, get, etc. Each
+ * object has an associated with a range number. Range numbers are the
+ * 0-indexed counter of selection ranges as explained at
+ * https://developer.mozilla.org/en/DOM/Selection.
+ *
+ * @param rangeNumber
+ * The zero-based range index into the selection
+ */
+function Selection(rangeNumber) {
+
+ // In order to hide the private rangeNumber argument from API consumers while
+ // still enabling Selection getters/setters to access it, the getters/setters
+ // are defined as lexical closures in the Selector constructor.
+
+ this.__defineGetter__("text", function () getSelection(TEXT, rangeNumber));
+ this.__defineSetter__("text", function (str) setSelection(str, rangeNumber));
+
+ this.__defineGetter__("html", function () getSelection(HTML, rangeNumber));
+ this.__defineSetter__("html", function (str) setSelection(str, rangeNumber));
+
+ this.__defineGetter__("isContiguous", function () {
+ let sel = getSelection(DOM);
+
+ // If there are multiple ranges, the selection is definitely discontiguous.
+ // It returns `false` also if there are no selection; and `true` if there is
+ // a single non empty range, or a selection in a text field - contiguous or
+ // not (text field selection APIs doesn't support multiple selections).
+
+ if (sel.rangeCount > 1)
+ return false;
+
+ return !!(safeGetRange(sel, 0) || getElementWithSelection());
+ });
+}
+
+require("api-utils/xpcom").utils.defineLazyServiceGetter(this, "windowMediator",
+ "@mozilla.org/appshell/window-mediator;1", "nsIWindowMediator");
+
+/**
+ * Returns the most recent content window
+ */
+function context() {
+ // Overlay names should probably go into the xul-app module instead of here
+ return windowMediator.getMostRecentWindow("navigator:browser").document.
+ commandDispatcher.focusedWindow;
+}
+
+/**
+ * Returns the current selection from most recent content window. Depending on
+ * the specified |type|, the value returned can be a string of text, stringified
+ * HTML, or a DOM selection object as described at
+ * https://developer.mozilla.org/en/DOM/Selection.
+ *
+ * @param type
+ * Specifies the return type of the selection. Valid values are the one
+ * of the constants HTML, TEXT, or DOM.
+ *
+ * @param rangeNumber
+ * Specifies the zero-based range index of the returned selection.
+ */
+function getSelection(type, rangeNumber) {
+ let window, selection;
+ try {
+ window = context();
+ selection = window.getSelection();
+ }
+ catch (e) {
+ return null;
+ }
+
+ // Get the selected content as the specified type
+ if (type == DOM)
+ return selection;
+ else if (type == TEXT) {
+ let range = safeGetRange(selection, rangeNumber);
+
+ if (range)
+ return range.toString();
+
+ let node = getElementWithSelection(window);
+
+ if (!node)
+ return null;
+
+ return node.value.substring(node.selectionStart, node.selectionEnd);
+ }
+ else if (type == HTML) {
+ let range = safeGetRange(selection, rangeNumber);
+ // Another way, but this includes the xmlns attribute for all elements in
+ // Gecko 1.9.2+ :
+ // return Cc["@mozilla.org/xmlextras/xmlserializer;1"].
+ // createInstance(Ci.nsIDOMSerializer).serializeToSTring(range.
+ // cloneContents());
+ if (!range)
+ return null;
+ let node = window.document.createElement("span");
+ node.appendChild(range.cloneContents());
+ return node.innerHTML;
+ }
+ throw new Error("Type " + type + " is unrecognized.");
+}
+
+/**
+ * Returns the specified range in a selection without throwing an exception.
+ *
+ * @param selection
+ * A selection object as described at
+ * https://developer.mozilla.org/en/DOM/Selection
+ *
+ * @param rangeNumber
+ * Specifies the zero-based range index of the returned selection.
+ */
+function safeGetRange(selection, rangeNumber) {
+ try {
+ let range = selection.getRangeAt(rangeNumber);
+ if (!range || range.toString() == "")
+ return null;
+ return range;
+ }
+ catch (e) {
+ return null;
+ }
+}
+
+/**
+ * Returns a reference of the DOM's active element for the window given, if it
+ * supports the text field selection API and has a text selected.
+ *
+ * Note:
+ * we need this method because window.getSelection doesn't return a selection
+ * for text selected in a form field (see bug 85686)
+ *
+ * @param {nsIWindow} [window]
+ * A reference to a window
+ */
+function getElementWithSelection(window) {
+ let element;
+
+ try {
+ element = (window || context()).document.activeElement;
+ }
+ catch (e) {
+ element = null;
+ }
+
+ if (!element)
+ return null;
+
+ let { value, selectionStart, selectionEnd } = element;
+
+ let hasSelection = typeof value === "string" &&
+ !isNaN(selectionStart) &&
+ !isNaN(selectionEnd) &&
+ selectionStart !== selectionEnd;
+
+ return hasSelection ? element : null;
+}
+/**
+ * Sets the current selection of the most recent content document by changing
+ * the existing selected text/HTML range to the specified value.
+ *
+ * @param val
+ * The value for the new selection
+ *
+ * @param rangeNumber
+ * The zero-based range index of the selection to be set
+ *
+ */
+function setSelection(val, rangeNumber) {
+ // Make sure we have a window context & that there is a current selection.
+ // Selection cannot be set unless there is an existing selection.
+ let window, selection;
+
+ try {
+ window = context();
+ selection = window.getSelection();
+ }
+ catch (e) {
+ throw new Error(ERR_CANNOT_CHANGE_SELECTION);
+ }
+
+ let range = safeGetRange(selection, rangeNumber);
+
+ if (range) {
+ // Get rid of the current selection and insert our own
+ range.deleteContents();
+ let node = window.document.createElement("span");
+ range.surroundContents(node);
+
+ // Some relevant JEP-111 requirements:
+
+ // Setting the text property replaces the selection with the value to
+ // which the property is set and sets the html property to the same value
+ // to which the text property is being set.
+
+ // Setting the html property replaces the selection with the value to
+ // which the property is set and sets the text property to the text version
+ // of the HTML value.
+
+ // This sets both the HTML and text properties.
+ node.innerHTML = val;
+ } else {
+ let node = getElementWithSelection(window);
+
+ if (!node)
+ throw new Error(ERR_CANNOT_CHANGE_SELECTION);
+
+ let { value, selectionStart, selectionEnd } = node;
+
+ let newSelectionEnd = selectionStart + val.length;
+
+ node.value = value.substring(0, selectionStart) +
+ val +
+ value.substring(selectionEnd, value.length);
+
+ node.setSelectionRange(selectionStart, newSelectionEnd);
+ }
+}
+
+function onLoad(event) {
+ SelectionListenerManager.onLoad(event);
+}
+
+function onUnload(event) {
+ SelectionListenerManager.onUnload(event);
+}
+
+function onSelect() {
+ SelectionListenerManager.onSelect();
+}
+
+let SelectionListenerManager = {
+ QueryInterface: require("api-utils/xpcom").utils.
+ generateQI([Ci.nsISelectionListener]),
+
+ // The collection of listeners wanting to be notified of selection changes
+ listeners: EventEmitter.compose({
+ emit: function emit(type) this._emitOnObject(exports, type),
+ off: function() this._removeAllListeners.apply(this, arguments)
+ })(),
+ /**
+ * This is the nsISelectionListener implementation. This function is called
+ * by Gecko when a selection is changed interactively.
+ *
+ * We only pay attention to the SELECTALL, KEYPRESS, and MOUSEUP selection
+ * reasons. All reasons are listed here:
+ *
+ * http://mxr.mozilla.org/mozilla1.9.2/source/content/base/public/
+ * nsISelectionListener.idl
+ *
+ * The other reasons (NO_REASON, DRAG_REASON, MOUSEDOWN_REASON) aren't
+ * applicable to us.
+ */
+ notifySelectionChanged: function notifySelectionChanged(document, selection,
+ reason) {
+ if (!["SELECTALL", "KEYPRESS", "MOUSEUP"].some(function(type) reason &
+ Ci.nsISelectionListener[type + "_REASON"]) || selection.toString() == "")
+ return;
+
+ this.onSelect();
+ },
+
+ onSelect : function onSelect() {
+ setTimeout(this.listeners.emit, 0, "select");
+ },
+
+ /**
+ * Part of the Tracker implementation. This function is called by the
+ * tabs module when a browser is being tracked. Often, that means a new tab
+ * has been opened, but it can also mean an addon has been installed while
+ * tabs are already opened. In that case, this function is called for those
+ * already-opened tabs.
+ *
+ * @param browser
+ * The browser being tracked
+ */
+ onTrack: function onTrack(browser) {
+ browser.addEventListener("load", onLoad, true);
+ browser.addEventListener("unload", onUnload, true);
+ },
+
+ onLoad: function onLoad(event) {
+ // Nothing to do without a useful window
+ let window = event.target.defaultView;
+ if (!window)
+ return;
+
+ // Wrap the add selection call with some number of setTimeout 0 because some
+ // reason it's possible to add a selection listener "too early". 2 sometimes
+ // works for gmail, and more consistently with 3, so make it 5 to be safe.
+ let count = 0;
+ let self = this;
+ function wrap(count, func) {
+ if (count-- > 0)
+ require("api-utils/timer").setTimeout(wrap, 0);
+ else
+ self.addSelectionListener(window);
+ }
+ wrap();
+ },
+
+ addSelectionListener: function addSelectionListener(window) {
+ if (window.jetpack_core_selection_listener)
+ return;
+ let selection = window.getSelection();
+ if (selection instanceof Ci.nsISelectionPrivate)
+ selection.addSelectionListener(this);
+
+ // nsISelectionListener implementation seems not fire a notification if
+ // a selection is in a text field, therefore we need to add a listener to
+ // window.onselect, that is fired only for text fields.
+ // https://developer.mozilla.org/en/DOM/window.onselect
+ window.addEventListener("select", onSelect, true);
+
+ window.jetpack_core_selection_listener = true;
+ },
+
+ onUnload: function onUnload(event) {
+ // Nothing to do without a useful window
+ let window = event.target.defaultView;
+ if (!window)
+ return;
+ this.removeSelectionListener(window);
+ this.listeners.off('error');
+ this.listeners.off('selection');
+ },
+
+ removeSelectionListener: function removeSelectionListener(window) {
+ if (!window.jetpack_core_selection_listener)
+ return;
+ let selection = window.getSelection();
+ if (selection instanceof Ci.nsISelectionPrivate)
+ selection.removeSelectionListener(this);
+
+ window.removeEventListener("select", onSelect);
+
+ window.jetpack_core_selection_listener = false;
+ },
+
+ /**
+ * Part of the TabTracker implementation. This function is called by the
+ * tabs module when a browser is being untracked. Usually, that means a tab
+ * has been closed.
+ *
+ * @param browser
+ * The browser being untracked
+ */
+ onUntrack: function onUntrack(browser) {
+ browser.removeEventListener("load", onLoad, true);
+ browser.removeEventListener("unload", onUnload, true);
+ }
+};
+SelectionListenerManager.listeners.on('error', console.error);
+
+/**
+ * Install |SelectionListenerManager| as tab tracker in order to watch
+ * tab opening/closing
+ */
+require("api-utils/tab-browser").Tracker(SelectionListenerManager);
+
+/**
+ * Exports an iterator so that discontiguous selections can be iterated.
+ *
+ * If discontiguous selections are in a text field, only the first one
+ * is returned because the text field selection APIs doesn't support
+ * multiple selections.
+ */
+exports.__iterator__ = function __iterator__() {
+ let sel = getSelection(DOM);
+ let rangeCount = sel.rangeCount || (getElementWithSelection() ? 1 : 0);
+
+ for (let i = 0; i < rangeCount; i++)
+ yield new Selection(i);
+};
+
+exports.on = SelectionListenerManager.listeners.on;
+exports.removeListener = SelectionListenerManager.listeners.removeListener;
+
+// Export the Selection singleton. Its rangeNumber is always zero.
+Selection.call(exports, 0);
+
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/lib/simple-storage.js b/tools/addon-sdk-1.3/packages/addon-kit/lib/simple-storage.js
new file mode 100644
index 0000000..b719e27
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/lib/simple-storage.js
@@ -0,0 +1,263 @@
+/* -*- 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)
+ * 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 file = require("api-utils/file");
+const prefs = require("api-utils/preferences-service");
+const jpSelf = require("self");
+const timer = require("api-utils/timer");
+const unload = require("api-utils/unload");
+const { EventEmitter } = require("api-utils/events");
+const { Trait } = require("api-utils/traits");
+
+const WRITE_PERIOD_PREF = "extensions.addon-sdk.simple-storage.writePeriod";
+const WRITE_PERIOD_DEFAULT = 300000; // 5 minutes
+
+const QUOTA_PREF = "extensions.addon-sdk.simple-storage.quota";
+const QUOTA_DEFAULT = 5242880; // 5 MiB
+
+const JETPACK_DIR_BASENAME = "jetpack";
+
+
+// simpleStorage.storage
+exports.__defineGetter__("storage", function () manager.root);
+exports.__defineSetter__("storage", function (val) manager.root = val);
+
+// simpleStorage.quotaUsage
+exports.__defineGetter__("quotaUsage", function () manager.quotaUsage);
+
+// A generic JSON store backed by a file on disk. This should be isolated
+// enough to move to its own module if need be...
+function JsonStore(options) {
+ this.filename = options.filename;
+ this.quota = options.quota;
+ this.writePeriod = options.writePeriod;
+ this.onOverQuota = options.onOverQuota;
+ this.onWrite = options.onWrite;
+
+ unload.ensure(this);
+
+ this.writeTimer = timer.setInterval(this.write.bind(this),
+ this.writePeriod);
+}
+
+JsonStore.prototype = {
+ // The store's root.
+ get root() {
+ return this.isRootInited ? this._root : {};
+ },
+
+ // Performs some type checking.
+ set root(val) {
+ let types = ["array", "boolean", "null", "number", "object", "string"];
+ if (types.indexOf(typeof(val)) < 0) {
+ throw new Error("storage must be one of the following types: " +
+ types.join(", "));
+ }
+ this._root = val;
+ return val;
+ },
+
+ // True if the root has ever been set (either via the root setter or by the
+ // backing file's having been read).
+ get isRootInited() {
+ return this._root !== undefined;
+ },
+
+ // Percentage of quota used, as a number [0, Inf). > 1 implies over quota.
+ // Undefined if there is no quota.
+ get quotaUsage() {
+ return this.quota > 0 ?
+ JSON.stringify(this.root).length / this.quota :
+ undefined;
+ },
+
+ // Removes the backing file and all empty subdirectories.
+ purge: function JsonStore_purge() {
+ try {
+ // This'll throw if the file doesn't exist.
+ file.remove(this.filename);
+ let parentPath = this.filename;
+ do {
+ parentPath = file.dirname(parentPath);
+ // This'll throw if the dir isn't empty.
+ file.rmdir(parentPath);
+ } while (file.basename(parentPath) !== JETPACK_DIR_BASENAME);
+ }
+ catch (err) {}
+ },
+
+ // Initializes the root by reading the backing file.
+ read: function JsonStore_read() {
+ try {
+ let str = file.read(this.filename);
+
+ // Ideally we'd log the parse error with console.error(), but logged
+ // errors cause tests to fail. Supporting "known" errors in the test
+ // harness appears to be non-trivial. Maybe later.
+ this.root = JSON.parse(str);
+ }
+ catch (err) {
+ this.root = {};
+ }
+ },
+
+ // If the store is under quota, writes the root to the backing file.
+ // Otherwise quota observers are notified and nothing is written.
+ write: function JsonStore_write() {
+ if (this.quotaUsage > 1)
+ this.onOverQuota(this);
+ else
+ this._write();
+ },
+
+ // Cleans up on unload. If unloading because of uninstall, the store is
+ // purged; otherwise it's written.
+ unload: function JsonStore_unload(reason) {
+ timer.clearInterval(this.writeTimer);
+ this.writeTimer = null;
+
+ if (reason === "uninstall")
+ this.purge();
+ else
+ this._write();
+ },
+
+ // True if the root is an empty object.
+ get _isEmpty() {
+ if (this.root && typeof(this.root) === "object") {
+ let empty = true;
+ for (let key in this.root) {
+ empty = false;
+ break;
+ }
+ return empty;
+ }
+ return false;
+ },
+
+ // Writes the root to the backing file, notifying write observers when
+ // complete. If the store is over quota or if it's empty and the store has
+ // never been written, nothing is written and write observers aren't notified.
+ _write: function JsonStore__write() {
+ // Don't write if the root is uninitialized or if the store is empty and the
+ // backing file doesn't yet exist.
+ if (!this.isRootInited || (this._isEmpty && !file.exists(this.filename)))
+ return;
+
+ // If the store is over quota, don't write. The current under-quota state
+ // should persist.
+ if (this.quotaUsage > 1)
+ return;
+
+ // Finally, write.
+ let stream = file.open(this.filename, "w");
+ try {
+ stream.writeAsync(JSON.stringify(this.root), function writeAsync(err) {
+ if (err)
+ console.error("Error writing simple storage file: " + this.filename);
+ else if (this.onWrite)
+ this.onWrite(this);
+ }.bind(this));
+ }
+ catch (err) {
+ // writeAsync closes the stream after it's done, so only close on error.
+ stream.close();
+ }
+ }
+};
+
+
+// This manages a JsonStore singleton and tailors its use to simple storage.
+// The root of the JsonStore is lazy-loaded: The backing file is only read the
+// first time the root's gotten.
+let manager = Trait.compose(EventEmitter, Trait.compose({
+ jsonStore: null,
+
+ // The filename of the store, based on the profile dir and extension ID.
+ get filename() {
+ let storeFile = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+ storeFile.append(JETPACK_DIR_BASENAME);
+ storeFile.append(jpSelf.id);
+ storeFile.append("simple-storage");
+ file.mkpath(storeFile.path);
+ storeFile.append("store.json");
+ return storeFile.path;
+ },
+
+ get quotaUsage() {
+ return this.jsonStore.quotaUsage;
+ },
+
+ get root() {
+ if (!this.jsonStore.isRootInited)
+ this.jsonStore.read();
+ return this.jsonStore.root;
+ },
+
+ set root(val) {
+ return this.jsonStore.root = val;
+ },
+
+ unload: function manager_unload() {
+ this._removeAllListeners("OverQuota");
+ this._removeAllListeners("error");
+ },
+
+ constructor: function manager_constructor() {
+ // Log unhandled errors.
+ this.on("error", console.exception.bind(console));
+ unload.ensure(this);
+
+ this.jsonStore = new JsonStore({
+ filename: this.filename,
+ writePeriod: prefs.get(WRITE_PERIOD_PREF, WRITE_PERIOD_DEFAULT),
+ quota: prefs.get(QUOTA_PREF, QUOTA_DEFAULT),
+ onOverQuota: this._emitOnObject.bind(this, exports, "OverQuota")
+ });
+ }
+}))();
+
+exports.on = manager.on;
+exports.removeListener = manager.removeListener;
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/lib/tabs.js b/tools/addon-sdk-1.3/packages/addon-kit/lib/tabs.js
new file mode 100644
index 0000000..81681d6
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/lib/tabs.js
@@ -0,0 +1,62 @@
+/* ***** 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):
+ * Dietrich Ayala <dietrich@mozilla.com> (Original author)
+ * Felipe Gomes <felipc@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";
+
+if (!require("api-utils/xul-app").is("Firefox")) {
+ throw new Error([
+ "The tabs module currently supports only Firefox. In the future ",
+ "we would like it to support other applications, however. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=560716 for more information."
+ ].join(""));
+}
+
+const { browserWindows } = require("./windows");
+const { tabs } = require("api-utils/windows/tabs");
+
+Object.defineProperties(tabs, {
+ open: { value: function open(options) {
+ if (options.inNewWindow)
+ // `tabs` option is under review and may be removed.
+ return browserWindows.open({ tabs: [ options ] });
+ // Open in active window if new window was not required.
+ return browserWindows.activeWindow.tabs.open(options);
+ }}
+});
+// It's a hack but we will be able to remove it once will implement CommonJS
+// feature that would allow us to override exports.
+exports.__proto__ = tabs;
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/lib/timers.js b/tools/addon-sdk-1.3/packages/addon-kit/lib/timers.js
new file mode 100644
index 0000000..9373dea
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/lib/timers.js
@@ -0,0 +1,40 @@
+/* ***** 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";
+
+// This module just proxies to the low level equivalent "timer" in "api-utils".
+module.exports = require("api-utils/timer");
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/lib/widget.js b/tools/addon-sdk-1.3/packages/addon-kit/lib/widget.js
new file mode 100644
index 0000000..39f825a
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/lib/widget.js
@@ -0,0 +1,938 @@
+/* -*- 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):
+ * Dietrich Ayala <dietrich@mozilla.com> (Original Author)
+ * Drew Willcoxon <adw@mozilla.com>
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ * Alexandre Poirot <apoirot@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");
+
+// Widget content types
+const CONTENT_TYPE_URI = 1;
+const CONTENT_TYPE_HTML = 2;
+const CONTENT_TYPE_IMAGE = 3;
+
+const ERR_CONTENT = "No content or contentURL property found. Widgets must "
+ + "have one or the other.",
+ ERR_LABEL = "The widget must have a non-empty label property.",
+ ERR_ID = "You have to specify a unique value for the id property of " +
+ "your widget in order for the application to remember its " +
+ "position.",
+ ERR_DESTROYED = "The widget has been destroyed and can no longer be used.";
+
+// Supported events, mapping from DOM event names to our event names
+const EVENTS = {
+ "click": "click",
+ "mouseover": "mouseover",
+ "mouseout": "mouseout",
+};
+
+if (!require("api-utils/xul-app").is("Firefox")) {
+ throw new Error([
+ "The widget 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(""));
+}
+
+const { validateOptions } = require("api-utils/api-utils");
+const panels = require("./panel");
+const { EventEmitter, EventEmitterTrait } = require("api-utils/events");
+const { Trait } = require("api-utils/traits");
+const LightTrait = require('api-utils/light-traits').Trait;
+const { Loader, Symbiont } = require("api-utils/content");
+const timer = require("api-utils/timer");
+const { Cortex } = require('api-utils/cortex');
+const windowsAPI = require("./windows");
+const unload = require("api-utils/unload");
+
+// Data types definition
+const valid = {
+ number: { is: ["null", "undefined", "number"] },
+ string: { is: ["null", "undefined", "string"] },
+ id: {
+ is: ["string"],
+ ok: function (v) v.length > 0,
+ msg: ERR_ID,
+ readonly: true
+ },
+ label: {
+ is: ["string"],
+ ok: function (v) v.length > 0,
+ msg: ERR_LABEL
+ },
+ panel: {
+ is: ["null", "undefined", "object"],
+ ok: function(v) !v || v instanceof panels.Panel
+ },
+ width: {
+ is: ["null", "undefined", "number"],
+ map: function (v) {
+ if (null === v || undefined === v) v = 16;
+ return v;
+ },
+ defaultValue: 16
+ },
+};
+
+// Widgets attributes definition
+let widgetAttributes = {
+ label: valid.label,
+ id: valid.id,
+ tooltip: valid.string,
+ width: valid.width,
+ content: valid.string,
+ panel: valid.panel
+};
+
+// Import data definitions from loader, but don't compose with it as Model
+// functions allow us to recreate easily all Loader code.
+let loaderAttributes = require("api-utils/content/loader").validationAttributes;
+for (let i in loaderAttributes)
+ widgetAttributes[i] = loaderAttributes[i];
+
+widgetAttributes.contentURL.optional = true;
+
+// Widgets public events list, that are automatically binded in options object
+const WIDGET_EVENTS = [
+ "click",
+ "mouseover",
+ "mouseout",
+ "error",
+ "message",
+ "attach"
+];
+
+// `Model` utility functions that help creating these various Widgets objects
+let model = {
+
+ // Validate one attribute using api-utils.js:validateOptions function
+ _validate: function _validate(name, suspect, validation) {
+ let $1 = {};
+ $1[name] = suspect;
+ let $2 = {};
+ $2[name] = validation;
+ return validateOptions($1, $2)[name];
+ },
+
+ /**
+ * This method has two purposes:
+ * 1/ Validate and define, on a given object, a set of attribute
+ * 2/ Emit a "change" event on this object when an attribute is changed
+ *
+ * @params {Object} object
+ * Object on which we can bind attributes on and watch for their changes.
+ * This object must have an EventEmitter interface, or, at least `_emit`
+ * method
+ * @params {Object} attrs
+ * Dictionary of attributes definition following api-utils:validateOptions
+ * scheme
+ * @params {Object} values
+ * Dictionary of attributes default values
+ */
+ setAttributes: function setAttributes(object, attrs, values) {
+ let properties = {};
+ for (let name in attrs) {
+ let value = values[name];
+ let req = attrs[name];
+
+ // Retrieve default value from typedef if the value is not defined
+ if ((typeof value == "undefined" || value == null) && req.defaultValue)
+ value = req.defaultValue;
+
+ // Check for valid value if value is defined or mandatory
+ if (!req.optional || typeof value != "undefined")
+ value = model._validate(name, value, req);
+
+ // In any case, define this property on `object`
+ let property = null;
+ if (req.readonly) {
+ property = {
+ value: value,
+ writable: false,
+ enumerable: true,
+ configurable: false
+ };
+ }
+ else {
+ property = model._createWritableProperty(name, value);
+ }
+
+ properties[name] = property;
+ }
+ Object.defineProperties(object, properties);
+ },
+
+ // Generate ES5 property definition for a given attribute
+ _createWritableProperty: function _createWritableProperty(name, value) {
+ return {
+ get: function () {
+ return value;
+ },
+ set: function (newValue) {
+ value = newValue;
+ // The main goal of all this Model stuff is here:
+ // We want to forward all changes to some listeners
+ this._emit("change", name, value);
+ },
+ enumerable: true,
+ configurable: false
+ };
+ },
+
+ /**
+ * Automagically register listeners in options dictionary
+ * by detecting listener attributes with name starting with `on`
+ *
+ * @params {Object} object
+ * Target object that need to follow EventEmitter interface, or, at least,
+ * having `on` method.
+ * @params {Array} events
+ * List of events name to automatically bind.
+ * @params {Object} listeners
+ * Dictionary of event listener functions to register.
+ */
+ setEvents: function setEvents(object, events, listeners) {
+ for (let i = 0, l = events.length; i < l; i++) {
+ let name = events[i];
+ let onName = "on" + name[0].toUpperCase() + name.substr(1);
+ if (!listeners[onName])
+ continue;
+ object.on(name, listeners[onName].bind(object));
+ }
+ }
+
+};
+
+
+/**
+ * Main Widget class: entry point of the widget API
+ *
+ * Allow to control all widget across all existing windows with a single object.
+ * Widget.getView allow to retrieve a WidgetView instance to control a widget
+ * specific to one window.
+ */
+const WidgetTrait = LightTrait.compose(EventEmitterTrait, LightTrait({
+
+ _initWidget: function _initWidget(options) {
+ model.setAttributes(this, widgetAttributes, options);
+
+ browserManager.validate(this);
+
+ // We must have at least content or contentURL defined
+ if (!(this.content || this.contentURL))
+ throw new Error(ERR_CONTENT);
+
+ this._views = [];
+
+ // Set tooltip to label value if we don't have tooltip defined
+ if (!this.tooltip)
+ this.tooltip = this.label;
+
+ model.setEvents(this, WIDGET_EVENTS, options);
+
+ this.on('change', this._onChange.bind(this));
+
+ let self = this;
+ this._port = EventEmitterTrait.create({
+ emit: function () {
+ let args = arguments;
+ self._views.forEach(function(v) v.port.emit.apply(v.port, args));
+ }
+ });
+ // expose wrapped port, that exposes only public properties.
+ this._port._public = Cortex(this._port);
+
+ // Register this widget to browser manager in order to create new widget on
+ // all new windows
+ browserManager.addItem(this);
+ },
+
+ _onChange: function _onChange(name, value) {
+ // Set tooltip to label value if we don't have tooltip defined
+ if (name == 'tooltip' && !value) {
+ // we need to change tooltip again in order to change the value of the
+ // attribute itself
+ this.tooltip = this.label;
+ return;
+ }
+
+ // Forward attributes changes to WidgetViews
+ if (['width', 'tooltip', 'content', 'contentURL'].indexOf(name) != -1) {
+ this._views.forEach(function(v) v[name] = value);
+ }
+ },
+
+ _onEvent: function _onEvent(type, eventData) {
+ this._emit(type, eventData);
+ },
+
+ _createView: function _createView() {
+ // Create a new WidgetView instance
+ let view = WidgetView(this);
+
+ // Keep a reference to it
+ this._views.push(view);
+
+ // Emit an `attach` event with a WidgetView instance without private attrs
+ this._emit("attach", view._public);
+
+ return view;
+ },
+
+ // a WidgetView instance is destroyed
+ _onViewDestroyed: function _onViewDestroyed(view) {
+ let idx = this._views.indexOf(view);
+ this._views.splice(idx, 1);
+ },
+
+ /**
+ * Called on browser window closed, to destroy related WidgetViews
+ * @params {ChromeWindow} window
+ * Window that has been closed
+ */
+ _onWindowClosed: function _onWindowClosed(window) {
+ for each (let view in this._views) {
+ if (view._isInChromeWindow(window)) {
+ view.destroy();
+ break;
+ }
+ }
+ },
+
+ /**
+ * Get the WidgetView instance related to a BrowserWindow instance
+ * @params {BrowserWindow} window
+ * BrowserWindow reference from "windows" module
+ */
+ getView: function getView(window) {
+ for each (let view in this._views) {
+ if (view._isInWindow(window)) {
+ return view._public;
+ }
+ }
+ return null;
+ },
+
+ get port() this._port._public,
+ set port(v) {}, // Work around Cortex failure with getter without setter
+ // See bug 653464
+ _port: null,
+
+ postMessage: function postMessage(message) {
+ this._views.forEach(function(v) v.postMessage(message));
+ },
+
+ destroy: function destroy() {
+ if (this.panel)
+ this.panel.destroy();
+
+ // Dispatch destroy calls to views
+ // we need to go backward as we remove items from this array in
+ // _onViewDestroyed
+ for (let i = this._views.length - 1; i >= 0; i--)
+ this._views[i].destroy();
+
+ // Unregister widget to stop creating it over new windows
+ // and allow creation of new widget with same id
+ browserManager.removeItem(this);
+ }
+
+}));
+
+// Widget constructor
+const Widget = function Widget(options) {
+ let w = WidgetTrait.create(Widget.prototype);
+ w._initWidget(options);
+
+ // Return a Cortex of widget in order to hide private attributes like _onEvent
+ let _public = Cortex(w);
+ unload.ensure(_public, "destroy");
+ return _public;
+}
+exports.Widget = Widget;
+
+
+
+/**
+ * WidgetView is an instance of a widget for a specific window.
+ *
+ * This is an external API that can be retrieved by calling Widget.getView or
+ * by watching `attach` event on Widget.
+ */
+const WidgetViewTrait = LightTrait.compose(EventEmitterTrait, LightTrait({
+
+ // Reference to the matching WidgetChrome
+ // set right after constructor call
+ _chrome: null,
+
+ // Public interface of the WidgetView, passed in `attach` event or in
+ // Widget.getView
+ _public: null,
+
+ _initWidgetView: function WidgetView__initWidgetView(baseWidget) {
+ this._baseWidget = baseWidget;
+
+ model.setAttributes(this, widgetAttributes, baseWidget);
+
+ this.on('change', this._onChange.bind(this));
+
+ let self = this;
+ this._port = EventEmitterTrait.create({
+ emit: function () {
+ if (!self._chrome)
+ throw new Error(ERR_DESTROYED);
+ self._chrome.update(self._baseWidget, "emit", arguments);
+ }
+ });
+ // expose wrapped port, that exposes only public properties.
+ this._port._public = Cortex(this._port);
+
+ this._public = Cortex(this);
+ },
+
+ _onChange: function WidgetView__onChange(name, value) {
+ if (name == 'tooltip' && !value) {
+ this.tooltip = this.label;
+ return;
+ }
+
+ // Forward attributes changes to WidgetChrome instance
+ if (['width', 'tooltip', 'content', 'contentURL'].indexOf(name) != -1) {
+ this._chrome.update(this._baseWidget, name, value);
+ }
+ },
+
+ _onEvent: function WidgetView__onEvent(type, eventData, domNode) {
+ // Dispatch event in view
+ this._emit(type, eventData);
+
+ // And forward it to the main Widget object
+ if ("click" == type || type.indexOf("mouse") == 0)
+ this._baseWidget._onEvent(type, this._public);
+ else
+ this._baseWidget._onEvent(type, eventData);
+
+ // Special case for click events: if the widget doesn't have a click
+ // handler, but it does have a panel, display the panel.
+ if ("click" == type && !this._listeners("click").length && this.panel)
+ this.panel.show(domNode);
+ },
+
+ _isInWindow: function WidgetView__isInWindow(window) {
+ return windowsAPI.BrowserWindow({
+ window: this._chrome.window
+ }) == window;
+ },
+
+ _isInChromeWindow: function WidgetView__isInChromeWindow(window) {
+ return this._chrome.window == window;
+ },
+
+ _onPortEvent: function WidgetView__onPortEvent(args) {
+ let port = this._port;
+ port._emit.apply(port, args);
+ let basePort = this._baseWidget._port;
+ basePort._emit.apply(basePort, args);
+ },
+
+ get port() this._port._public,
+ set port(v) {}, // Work around Cortex failure with getter without setter
+ // See bug 653464
+ _port: null,
+
+ postMessage: function WidgetView_postMessage(message) {
+ if (!this._chrome)
+ throw new Error(ERR_DESTROYED);
+ this._chrome.update(this._baseWidget, "postMessage", message);
+ },
+
+ destroy: function WidgetView_destroy() {
+ this._chrome.destroy();
+ delete this._chrome;
+ this._baseWidget._onViewDestroyed(this);
+ this._emit("detach");
+ }
+
+}));
+
+const WidgetView = function WidgetView(baseWidget) {
+ let w = WidgetViewTrait.create(WidgetView.prototype);
+ w._initWidgetView(baseWidget);
+ return w;
+}
+
+
+
+/**
+ * Keeps track of all browser windows.
+ * Exposes methods for adding/removing widgets
+ * across all open windows (and future ones).
+ * Create a new instance of BrowserWindow per window.
+ */
+let browserManager = {
+ items: [],
+ windows: [],
+
+ // Registers the manager to listen for window openings and closings. Note
+ // that calling this method can cause onTrack to be called immediately if
+ // there are open windows.
+ init: function () {
+ let windowTracker = new (require("api-utils/window-utils").WindowTracker)(this);
+ unload.ensure(windowTracker);
+ },
+
+ // Registers a window with the manager. This is a WindowTracker callback.
+ onTrack: function browserManager_onTrack(window) {
+ if (this._isBrowserWindow(window)) {
+ let win = new BrowserWindow(window);
+ win.addItems(this.items);
+ this.windows.push(win);
+ }
+ },
+
+ // Unregisters a window from the manager. It's told to undo all
+ // modifications. This is a WindowTracker callback. Note that when
+ // WindowTracker is unloaded, it calls onUntrack for every currently opened
+ // window. The browserManager therefore doesn't need to specially handle
+ // unload itself, since unloading the browserManager means untracking all
+ // currently opened windows.
+ onUntrack: function browserManager_onUntrack(window) {
+ if (this._isBrowserWindow(window)) {
+ this.items.forEach(function(i) i._onWindowClosed(window));
+ for (let i = 0; i < this.windows.length; i++) {
+ if (this.windows[i].window == window) {
+ this.windows.splice(i, 1)[0];
+ return;
+ }
+ }
+
+ }
+ },
+
+ // Used to validate widget by browserManager before adding it,
+ // in order to check input very early in widget constructor
+ validate : function (item) {
+ let idx = this.items.indexOf(item);
+ if (idx > -1)
+ throw new Error("The widget " + item + " has already been added.");
+ if (item.id) {
+ let sameId = this.items.filter(function(i) i.id == item.id);
+ if (sameId.length > 0)
+ throw new Error("This widget ID is already used: " + item.id);
+ } else {
+ item.id = this.items.length;
+ }
+ },
+
+ // Registers an item with the manager. It's added to all currently registered
+ // windows, and when new windows are registered it will be added to them, too.
+ addItem: function browserManager_addItem(item) {
+ this.items.push(item);
+ this.windows.forEach(function (w) w.addItems([item]));
+ },
+
+ // Unregisters an item from the manager. It's removed from all windows that
+ // are currently registered.
+ removeItem: function browserManager_removeItem(item) {
+ let idx = this.items.indexOf(item);
+ if (idx > -1)
+ this.items.splice(idx, 1);
+ },
+
+ _isBrowserWindow: function browserManager__isBrowserWindow(win) {
+ let winType = win.document.documentElement.getAttribute("windowtype");
+ return winType === "navigator:browser";
+ }
+};
+
+
+
+/**
+ * Keeps track of a single browser window.
+ *
+ * This is where the core of how a widget's content is added to a window lives.
+ */
+function BrowserWindow(window) {
+ this.window = window;
+ this.doc = window.document;
+}
+
+BrowserWindow.prototype = {
+
+ // Adds an array of items to the window.
+ addItems: function BW_addItems(items) {
+ items.forEach(this._addItemToWindow, this);
+ },
+
+ _addItemToWindow: function BW__addItemToWindow(baseWidget) {
+ // Create a WidgetView instance
+ let widget = baseWidget._createView();
+
+ // Create a WidgetChrome instance
+ let item = new WidgetChrome({
+ widget: widget,
+ doc: this.doc,
+ window: this.window
+ });
+
+ widget._chrome = item;
+
+ this._insertNodeInToolbar(item.node);
+
+ // We need to insert Widget DOM Node before finishing widget view creation
+ // (because fill creates an iframe and tries to access its docShell)
+ item.fill();
+ },
+
+ _insertNodeInToolbar: function BW__insertNodeInToolbar(node) {
+ // Add to the customization palette
+ let toolbox = this.doc.getElementById("navigator-toolbox");
+ let palette = toolbox.palette;
+ palette.appendChild(node);
+
+ // Search for widget toolbar by reading toolbar's currentset attribute
+ let container = null;
+ let toolbars = this.doc.getElementsByTagName("toolbar");
+ let id = node.getAttribute("id");
+ for (let i = 0, l = toolbars.length; i < l; i++) {
+ let toolbar = toolbars[i];
+ if (toolbar.getAttribute("currentset").indexOf(id) == -1)
+ continue;
+ container = toolbar;
+ }
+
+ // if widget isn't in any toolbar, add it to the addon-bar
+ // TODO: we may want some "first-launch" module to do this only on very
+ // first execution
+ if (!container) {
+ container = this.doc.getElementById("addon-bar");
+ // TODO: find a way to make the following code work when we use "cfx run":
+ // http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#8586
+ // until then, force display of addon bar directly from sdk code
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=627484
+ if (container.collapsed)
+ this.window.toggleAddonBar();
+ }
+
+ // Now retrieve a reference to the next toolbar item
+ // by reading currentset attribute on the toolbar
+ let nextNode = null;
+ let currentSet = container.getAttribute("currentset");
+ let ids = (currentSet == "__empty") ? [] : currentSet.split(",");
+ let idx = ids.indexOf(id);
+ if (idx != -1) {
+ for (let i = idx; i < ids.length; i++) {
+ nextNode = this.doc.getElementById(ids[i]);
+ if (nextNode)
+ break;
+ }
+ }
+
+ // Finally insert our widget in the right toolbar and in the right position
+ container.insertItem(id, nextNode, null, false);
+
+ // Update DOM in order to save position if we remove/readd the widget
+ container.setAttribute("currentset", container.currentSet);
+ // Save DOM attribute in order to save position on new window opened
+ this.window.document.persist(container.id, "currentset");
+ }
+}
+
+
+/**
+ * Final Widget class that handles chrome DOM Node:
+ * - create initial DOM nodes
+ * - receive instruction from WidgetView through update method and update DOM
+ * - watch for DOM events and forward them to WidgetView
+ */
+function WidgetChrome(options) {
+ this.window = options.window;
+ this._doc = options.doc;
+ this._widget = options.widget;
+ this._symbiont = null; // set later
+ this.node = null; // set later
+
+ this._createNode();
+}
+
+// Update a property of a widget.
+WidgetChrome.prototype.update = function WC_update(updatedItem, property, value) {
+ switch(property) {
+ case "contentURL":
+ case "content":
+ this.setContent();
+ break;
+ case "width":
+ this.node.style.minWidth = value + "px";
+ this.node.querySelector("iframe").style.width = value + "px";
+ break;
+ case "tooltip":
+ this.node.setAttribute("tooltiptext", value);
+ break;
+ case "postMessage":
+ this._symbiont.postMessage(value);
+ break;
+ case "emit":
+ let port = this._symbiont.port;
+ port.emit.apply(port, value);
+ break;
+ }
+}
+
+// Add a widget to this window.
+WidgetChrome.prototype._createNode = function WC__createNode() {
+ // XUL element container for widget
+ let node = this._doc.createElement("toolbaritem");
+ let guid = require("api-utils/xpcom").makeUuid().toString();
+
+ // Temporary work around require("self") failing on unit-test execution ...
+ let jetpackID = "testID";
+ try {
+ jetpackID = require("self").id;
+ } catch(e) {}
+
+ // Compute an unique and stable widget id with jetpack id and widget.id
+ let id = "widget:" + jetpackID + "-" + this._widget.id;
+ node.setAttribute("id", id);
+ node.setAttribute("label", this._widget.label);
+ node.setAttribute("tooltiptext", this._widget.tooltip);
+ node.setAttribute("align", "center");
+
+ // TODO move into a stylesheet, configurable by consumers.
+ // Either widget.style, exposing the style object, or a URL
+ // (eg, can load local stylesheet file).
+ node.setAttribute("style", [
+ "overflow: hidden; margin: 1px 2px 1px 2px; padding: 0px;",
+ "min-height: 16px;",
+ ].join(""));
+
+ node.style.minWidth = this._widget.width + "px";
+
+ this.node = node;
+}
+
+// Initial population of a widget's content.
+WidgetChrome.prototype.fill = function WC_fill() {
+ // Create element
+ var iframe = this._doc.createElement("iframe");
+ iframe.setAttribute("type", "content");
+ iframe.setAttribute("transparent", "transparent");
+ iframe.style.overflow = "hidden";
+ iframe.style.height = "16px";
+ iframe.style.maxHeight = "16px";
+ iframe.style.width = this._widget.width + "px";
+ iframe.setAttribute("flex", "1");
+ iframe.style.border = "none";
+ iframe.style.padding = "0px";
+
+ // Do this early, because things like contentWindow are null
+ // until the node is attached to a document.
+ this.node.appendChild(iframe);
+
+ // add event handlers
+ this.addEventHandlers();
+
+ // set content
+ this.setContent();
+}
+
+// Get widget content type.
+WidgetChrome.prototype.getContentType = function WC_getContentType() {
+ if (this._widget.content)
+ return CONTENT_TYPE_HTML;
+ return (this._widget.contentURL && /\.(jpg|gif|png|ico)$/.test(this._widget.contentURL))
+ ? CONTENT_TYPE_IMAGE : CONTENT_TYPE_URI;
+}
+
+// Set widget content.
+WidgetChrome.prototype.setContent = function WC_setContent() {
+ let type = this.getContentType();
+ let contentURL = null;
+
+ switch (type) {
+ case CONTENT_TYPE_HTML:
+ contentURL = "data:text/html," + encodeURIComponent(this._widget.content);
+ break;
+ case CONTENT_TYPE_URI:
+ contentURL = this._widget.contentURL;
+ break;
+ case CONTENT_TYPE_IMAGE:
+ let imageURL = this._widget.contentURL;
+ contentURL = "data:text/html,<html><body><img src='" +
+ encodeURI(imageURL) + "'></body></html>";
+ break;
+ default:
+ throw new Error("The widget's type cannot be determined.");
+ }
+
+ let iframe = this.node.firstElementChild;
+
+ let self = this;
+ // Cleanup previously created symbiont (in case we are update content)
+ if (this._symbiont)
+ this._symbiont.destroy();
+
+ this._symbiont = Trait.compose(Symbiont.resolve({
+ _onContentScriptEvent: "_onContentScriptEvent-not-used"
+ }), {
+ _onContentScriptEvent: function () {
+ // Redirect events to WidgetView
+ self._widget._onPortEvent(arguments);
+ }
+ })({
+ frame: iframe,
+ contentURL: contentURL,
+ contentScriptFile: this._widget.contentScriptFile,
+ contentScript: this._widget.contentScript,
+ contentScriptWhen: this._widget.contentScriptWhen,
+ allow: this._widget.allow,
+ onMessage: function(message) {
+ timer.setTimeout(function() {
+ self._widget._onEvent("message", message);
+ }, 0);
+ }
+ });
+}
+
+// Detect if document consists of a single image.
+WidgetChrome._isImageDoc = function WC__isImageDoc(doc) {
+ return doc.body.childNodes.length == 1 &&
+ doc.body.firstElementChild &&
+ doc.body.firstElementChild.tagName == "IMG";
+}
+
+// Set up all supported events for a widget.
+WidgetChrome.prototype.addEventHandlers = function WC_addEventHandlers() {
+ let contentType = this.getContentType();
+
+ let self = this;
+ let listener = function(e) {
+ // Ignore event firings that target the iframe.
+ if (e.target == self.node.firstElementChild)
+ return;
+
+ // The widget only supports left-click for now,
+ // so ignore right-clicks.
+ if (e.type == "click" && e.button == 2)
+ return;
+
+ // Proxy event to the widget
+ timer.setTimeout(function() {
+ self._widget._onEvent(EVENTS[e.type], null, self.node);
+ }, 0);
+ };
+
+ this.eventListeners = {};
+ let iframe = this.node.firstElementChild;
+ for (let [type, method] in Iterator(EVENTS)) {
+ iframe.addEventListener(type, listener, true, true);
+
+ // Store listeners for later removal
+ this.eventListeners[type] = listener;
+ }
+
+ // On document load, make modifications required for nice default
+ // presentation.
+ let self = this;
+ function loadListener(e) {
+ // Ignore event firings that target the iframe
+ if (e.target == iframe)
+ return;
+ // Ignore about:blank loads
+ if (e.type == "load" && e.target.location == "about:blank")
+ return;
+
+ // We may have had an unload event before that cleaned up the symbiont
+ if (!self._symbiont)
+ self.setContent();
+
+ let doc = e.target;
+ if (contentType == CONTENT_TYPE_IMAGE || WidgetChrome._isImageDoc(doc)) {
+ // Force image content to size.
+ // Add-on authors must size their images correctly.
+ doc.body.firstElementChild.style.width = self._widget.width + "px";
+ doc.body.firstElementChild.style.height = "16px";
+ }
+
+ // Allow all content to fill the box by default.
+ doc.body.style.margin = "0";
+ }
+ iframe.addEventListener("load", loadListener, true);
+ this.eventListeners["load"] = loadListener;
+
+ // Register a listener to unload symbiont if the toolbaritem is moved
+ // on user toolbars customization
+ function unloadListener(e) {
+ if (e.target.location == "about:blank")
+ return;
+ self._symbiont.destroy();
+ self._symbiont = null;
+ // This may fail but not always, it depends on how the node is
+ // moved or removed
+ try {
+ self.setContent();
+ } catch(e) {}
+
+ }
+
+ iframe.addEventListener("unload", unloadListener, true);
+ this.eventListeners["unload"] = unloadListener;
+}
+
+// Remove and unregister the widget from everything
+WidgetChrome.prototype.destroy = function WC_destroy(removedItems) {
+ // remove event listeners
+ for (let [type, listener] in Iterator(this.eventListeners))
+ this.node.firstElementChild.removeEventListener(type, listener, true);
+ // remove dom node
+ this.node.parentNode.removeChild(this.node);
+ // cleanup symbiont
+ this._symbiont.destroy();
+ // cleanup itself
+ this.eventListeners = null;
+ this._widget = null;
+ this._symbiont = null;
+}
+
+// Init the browserManager only after setting prototypes and such above, because
+// it will cause browserManager.onTrack to be called immediately if there are
+// open windows.
+browserManager.init();
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/lib/windows.js b/tools/addon-sdk-1.3/packages/addon-kit/lib/windows.js
new file mode 100644
index 0000000..e9e097b
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/lib/windows.js
@@ -0,0 +1,238 @@
+/* ***** 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):
+ * Felipe Gomes <felipc@gmail.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";
+
+if (!require("api-utils/xul-app").is("Firefox")) {
+ throw new Error([
+ "The windows module currently supports only Firefox. In the future",
+ " we would like it to support other applications, however. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=571449 for more information."
+ ].join(""));
+}
+
+const { Cc, Ci } = require('chrome'),
+ { Trait } = require('api-utils/traits'),
+ { List } = require('api-utils/list'),
+ { EventEmitter } = require('api-utils/events'),
+ { WindowTabs, WindowTabTracker } = require('api-utils/windows/tabs'),
+ { WindowDom } = require('api-utils/windows/dom'),
+ { WindowLoader } = require('api-utils/windows/loader'),
+ { WindowTrackerTrait } = require('api-utils/window-utils'),
+ { Options } = require('api-utils/tabs/tab'),
+ { utils } = require('api-utils/xpcom'),
+ apiUtils = require('api-utils/api-utils'),
+ unload = require('api-utils/unload'),
+
+ WM = Cc['@mozilla.org/appshell/window-mediator;1'].
+ getService(Ci.nsIWindowMediator),
+
+ BROWSER = 'navigator:browser';
+
+/**
+ * Window trait composes safe wrappers for browser window that are E10S
+ * compatible.
+ */
+const BrowserWindowTrait = Trait.compose(
+ EventEmitter,
+ WindowDom.resolve({ close: '_close' }),
+ WindowTabs,
+ WindowTabTracker,
+ WindowLoader,
+ /* WindowSidebars, */
+ Trait.compose({
+ _emit: Trait.required,
+ _close: Trait.required,
+ _load: Trait.required,
+ /**
+ * Constructor returns wrapper of the specified chrome window.
+ * @param {nsIWindow} window
+ */
+ constructor: function BrowserWindow(options) {
+ // Register this window ASAP, in order to avoid loop that would try
+ // to create this window instance over and over (see bug 648244)
+ windows.push(this);
+
+ // make sure we don't have unhandled errors
+ this.on('error', console.exception.bind(console));
+
+ if ('onOpen' in options)
+ this.on('open', options.onOpen);
+ if ('onClose' in options)
+ this.on('close', options.onClose);
+ if ('window' in options)
+ this._window = options.window;
+ if ('tabs' in options) {
+ this._tabOptions = Array.isArray(options.tabs) ?
+ options.tabs.map(Options) :
+ [ Options(options.tabs) ];
+ }
+ else if ('url' in options) {
+ this._tabOptions = [ Options(options.url) ];
+ }
+ this._load();
+ return this;
+ },
+ _tabOptions: [],
+ _onLoad: function() {
+ try {
+ this._initWindowTabTracker();
+ } catch(e) {
+ this._emit('error', e)
+ }
+ this._emitOnObject(browserWindows, 'open', this._public);
+ },
+ _onUnload: function() {
+ this._destroyWindowTabTracker();
+ this._emitOnObject(browserWindows, 'close', this._public);
+ this._window = null;
+ // Removing reference from the windows array.
+ windows.splice(windows.indexOf(this), 1);
+ this._removeAllListeners('close');
+ this._removeAllListeners('open');
+ this._removeAllListeners('ready');
+ },
+ close: function close(callback) {
+ // maybe we should deprecate this with message ?
+ if (callback) this.on('close', callback);
+ return this._close();
+ }
+ })
+);
+/**
+ * Wrapper for `BrowserWindowTrait`. Creates new instance if wrapper for
+ * window doesn't exists yet. If wrapper already exists then returns it
+ * instead.
+ * @params {Object} options
+ * Options that are passed to the the `BrowserWindowTrait`
+ * @returns {BrowserWindow}
+ * @see BrowserWindowTrait
+ */
+function BrowserWindow(options) {
+ let chromeWindow = options.window;
+ for each (let window in windows) {
+ if (chromeWindow == window._window)
+ return window._public
+ }
+ let window = BrowserWindowTrait(options);
+ return window._public;
+}
+// to have proper `instanceof` behavior will go away when #596248 is fixed.
+BrowserWindow.prototype = BrowserWindowTrait.prototype;
+exports.BrowserWindow = BrowserWindow
+const windows = [];
+/**
+ * `BrowserWindows` trait is composed out of `List` trait and it represents
+ * "live" list of currently open browser windows. Instance mutates itself
+ * whenever new browser window gets opened / closed.
+ */
+// Very stupid to resolve all `toStrings` but this will be fixed by #596248
+const browserWindows = Trait.resolve({ toString: null }).compose(
+ List.resolve({ constructor: '_initList' }),
+ EventEmitter.resolve({ toString: null }),
+ WindowTrackerTrait.resolve({ constructor: '_initTracker', toString: null }),
+ Trait.compose({
+ _emit: Trait.required,
+ _add: Trait.required,
+ _remove: Trait.required,
+
+ // public API
+
+ /**
+ * Constructor creates instance of `Windows` that represents live list of open
+ * windows.
+ */
+ constructor: function BrowserWindows() {
+ this._trackedWindows = [];
+ this._initList();
+ this._initTracker();
+ unload.ensure(this, "_destructor");
+ },
+ _destructor: function _destructor() {
+ this._removeAllListeners('open');
+ this._removeAllListeners('close');
+ },
+ /**
+ * This property represents currently active window.
+ * Property is non-enumerable, in order to preserve array like enumeration.
+ * @type {Window|null}
+ */
+ get activeWindow() {
+ let window = WM.getMostRecentWindow(BROWSER);
+ return this._isBrowser(window) ? BrowserWindow({ window: window }) : null;
+ },
+ open: function open(options) {
+ if (typeof options === "string")
+ // `tabs` option is under review and may be removed.
+ options = { tabs: [Options(options)] };
+ return BrowserWindow(options);
+ },
+ /**
+ * Returns true if specified window is a browser window.
+ * @param {nsIWindow} window
+ * @returns {Boolean}
+ */
+ _isBrowser: function _isBrowser(window)
+ BROWSER === window.document.documentElement.getAttribute("windowtype")
+ ,
+ /**
+ * Internal listener which is called whenever new window gets open.
+ * Creates wrapper and adds to this list.
+ * @param {nsIWindow} chromeWindow
+ */
+ _onTrack: function _onTrack(chromeWindow) {
+ if (!this._isBrowser(chromeWindow)) return;
+ let window = BrowserWindow({ window: chromeWindow });
+ this._add(window);
+ this._emit('open', window);
+ },
+ /**
+ * Internal listener which is called whenever window gets closed.
+ * Cleans up references and removes wrapper from this list.
+ * @param {nsIWindow} window
+ */
+ _onUntrack: function _onUntrack(chromeWindow) {
+ if (!this._isBrowser(chromeWindow)) return;
+ let window = BrowserWindow({ window: chromeWindow });
+ // `_onUnload` method of the `BrowserWindow` will remove `chromeWindow`
+ // from the `windows` array.
+ this._remove(window);
+ this._emit('close', window);
+ }
+ }).resolve({ toString: null })
+)();
+exports.browserWindows = browserWindows;
+