aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.3/packages/addon-kit
diff options
context:
space:
mode:
Diffstat (limited to 'tools/addon-sdk-1.3/packages/addon-kit')
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/README.md8
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/data/moz_favicon.icobin0 -> 1406 bytes
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/data/test-page-mod.html8
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/data/test-page-worker.html8
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/data/test-page-worker.js25
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/data/test.html8
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/docs/clipboard.md58
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/docs/context-menu.md702
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/docs/hotkeys.md74
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/docs/notifications.md60
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/docs/page-mod.md366
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/docs/page-worker.md218
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/docs/panel.md301
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/docs/passwords.md564
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/docs/private-browsing.md46
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/docs/request.md192
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/docs/selection.md86
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/docs/self.md73
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/docs/simple-storage.md125
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/docs/tabs.md381
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/docs/timers.md48
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/docs/widget.md693
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/docs/windows.md187
-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
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/package.json13
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/pagemod-test-helpers.js62
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/test-clipboard.js60
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/test-context-menu.html78
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/test-context-menu.js2074
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/test-hotkeys.js155
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/test-module.js33
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/test-notifications.js76
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/test-page-mod.js380
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/test-page-worker.js361
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/test-panel.js436
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/test-passwords.js277
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/test-private-browsing.js237
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/test-request.js394
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/test-selection.js490
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/test-simple-storage.js346
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/test-tabs.js904
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/test-timers.js44
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/test-widget.js909
-rw-r--r--tools/addon-sdk-1.3/packages/addon-kit/tests/test-windows.js354
59 files changed, 17116 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/README.md b/tools/addon-sdk-1.3/packages/addon-kit/README.md
new file mode 100644
index 0000000..9a68554
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/README.md
@@ -0,0 +1,8 @@
+The addon-kit package provides high-level APIs for add-on developers.
+Most of the needs of most add-on developers should be served by the modules
+found here. Modules in this packages don't require any special privileges to
+run.
+
+The modules in the addon-kit package are relatively stable. We intend to add
+new APIs here and extend existing ones, but will avoid making incompatible
+changes to them unless absolutely necessary.
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/data/moz_favicon.ico b/tools/addon-sdk-1.3/packages/addon-kit/data/moz_favicon.ico
new file mode 100644
index 0000000..d444389
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/data/moz_favicon.ico
Binary files differ
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/data/test-page-mod.html b/tools/addon-sdk-1.3/packages/addon-kit/data/test-page-mod.html
new file mode 100644
index 0000000..9211698
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/data/test-page-mod.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <title>Page Mod test</title>
+</head>
+<body>
+ <p id="paragraph">Lorem ipsum dolor sit amet.</p>
+</body>
+</html>
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/data/test-page-worker.html b/tools/addon-sdk-1.3/packages/addon-kit/data/test-page-worker.html
new file mode 100644
index 0000000..e89a283
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/data/test-page-worker.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <title>Page Worker test</title>
+</head>
+<body>
+ <p id="paragraph">Lorem ipsum dolor sit amet.</p>
+</body>
+</html>
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/data/test-page-worker.js b/tools/addon-sdk-1.3/packages/addon-kit/data/test-page-worker.js
new file mode 100644
index 0000000..d8effb9
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/data/test-page-worker.js
@@ -0,0 +1,25 @@
+
+// get title directly
+self.postMessage(["assertEqual", document.title, "Page Worker test",
+ "Correct page title accessed directly"]);
+
+// get <p> directly
+let p = document.getElementById("paragraph");
+self.postMessage(["assert", !!p, "<p> can be accessed directly"]);
+self.postMessage(["assertEqual", p.firstChild.nodeValue,
+ "Lorem ipsum dolor sit amet.",
+ "Correct text node expected"]);
+
+// Modify page
+let div = document.createElement("div");
+div.setAttribute("id", "block");
+div.appendChild(document.createTextNode("Test text created"));
+document.body.appendChild(div);
+
+// Check back the modification
+div = document.getElementById("block");
+self.postMessage(["assert", !!div, "<div> can be accessed directly"]);
+self.postMessage(["assertEqual", div.firstChild.nodeValue,
+ "Test text created", "Correct text node expected"]);
+self.postMessage(["done"]);
+
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/data/test.html b/tools/addon-sdk-1.3/packages/addon-kit/data/test.html
new file mode 100644
index 0000000..39de477
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/data/test.html
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>foo</title>
+ </head>
+ <body>
+ <p>bar</p>
+ </body>
+</html>
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/docs/clipboard.md b/tools/addon-sdk-1.3/packages/addon-kit/docs/clipboard.md
new file mode 100644
index 0000000..7292017
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/docs/clipboard.md
@@ -0,0 +1,58 @@
+<!-- contributed by Dietrich Ayala [dietrich@mozilla.com] -->
+
+The `clipboard` module allows callers to interact with the system clipboard,
+setting and retrieving its contents.
+
+You can optionally specify the type of the data to set and retrieve.
+The following types are supported:
+
+* `text` (plain text)
+* `html` (a string of HTML)
+
+If no data type is provided, then the module will detect it for you.
+
+Examples
+--------
+
+Set and get the contents of the clipboard.
+
+ let clipboard = require("clipboard");
+ clipboard.set("Lorem ipsum dolor sit amet");
+ let contents = clipboard.get();
+
+Set the clipboard contents to some HTML.
+
+ let clipboard = require("clipboard");
+ clipboard.set("<blink>Lorem ipsum dolor sit amet</blink>", "html");
+
+If the clipboard contains HTML content, open it in a new tab.
+
+ let clipboard = require("clipboard");
+ if (clipboard.currentFlavors.indexOf("html") != -1)
+ require("tabs").open("data:text/html," + clipboard.get("html"));
+
+<api name="set">
+@function
+ Replace the contents of the user's clipboard with the provided data.
+@param data {string}
+ The data to put on the clipboard.
+@param [datatype] {string}
+ The type of the data (optional).
+</api>
+
+<api name="get">
+@function
+ Get the contents of the user's clipboard.
+@param [datatype] {string}
+ Retrieve the clipboard contents only if matching this type (optional).
+ The function will return null if the contents of the clipboard do not match
+ the supplied type.
+</api>
+
+<api name="currentFlavors">
+@property {array}
+ Data on the clipboard is sometimes available in multiple types. For example,
+ HTML data might be available as both a string of HTML (the `html` type)
+ and a string of plain text (the `text` type). This function returns an array
+ of all types in which the data currently on the clipboard is available.
+</api>
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/docs/context-menu.md b/tools/addon-sdk-1.3/packages/addon-kit/docs/context-menu.md
new file mode 100644
index 0000000..f2bb5b3
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/docs/context-menu.md
@@ -0,0 +1,702 @@
+<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+
+The `context-menu` module lets you add items to Firefox's page context menu.
+
+
+Introduction
+------------
+
+The `context-menu` API provides a simple, declarative way to add items to the
+page's context menu. You can add items that perform an action when clicked,
+submenus, and menu separators.
+
+Instead of manually adding items when particular contexts occur and then
+removing them when those contexts go away, you *bind* items to contexts, and the
+adding and removing is automatically handled for you. Items are bound to
+contexts in much the same way that event listeners are bound to events. When
+the user invokes the context menu, all of the items bound to the current context
+are automatically added to the menu. If no items are bound, none are added.
+Likewise, any items that were previously in the menu but are not bound to the
+current context are automatically removed from the menu. You never need to
+manually remove your items from the menu unless you want them to never appear
+again.
+
+For example, if your add-on needs to add a context menu item whenever the
+user visits a certain page, don't create the item when that page loads, and
+don't remove it when the page unloads. Rather, create your item only once and
+supply a context that matches the target URL.
+
+
+Specifying Contexts
+-------------------
+
+As its name implies, the context menu should be reserved for the occurrence of
+specific contexts. Contexts can be related to page content or the page itself,
+but they should never be external to the page.
+
+For example, a good use of the menu would be to show an "Edit Image" item when
+the user right-clicks an image in the page. A bad use would be to show a
+submenu that listed all the user's tabs, since tabs aren't related to the page
+or the node the user clicked to open the menu.
+
+### The Page Context
+
+First of all, you may not need to specify a context at all. When an item does
+not specify a context, the page context applies.
+
+The *page context* occurs when the user invokes the context menu on a
+non-interactive portion of the page. Try right-clicking a blank spot in this
+page, or on text. Make sure that no text is selected. The menu that appears
+should contain the items "Back", "Forward", "Reload", "Stop", and so on. This
+is the page context.
+
+The page context is appropriate when your item acts on the page as a whole. It
+does not occur when the user invokes the context menu on a link, image, or other
+non-text node, or while a selection exists.
+
+### Declarative Contexts
+
+You can specify some simple, declarative contexts when you create a menu item by
+setting the `context` property of the options object passed to its constructor,
+like this:
+
+ var cm = require("context-menu");
+ cm.Item({
+ label: "My Menu Item",
+ context: cm.URLContext("*.mozilla.org")
+ });
+
+These contexts may be specified by calling the following constructors. Each is
+exported by the `context-menu` module.
+
+<table>
+ <tr>
+ <th>Constructor</th>
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td><code>
+ PageContext()
+ </code></td>
+ <td>
+ The page context.
+ </td>
+ </tr>
+ <tr>
+ <td><code>
+ SelectionContext()
+ </code></td>
+ <td>
+ This context occurs when the menu is invoked on a page in which the user
+ has made a selection.
+ </td>
+ </tr>
+ <tr>
+ <td><code>
+ SelectorContext(selector)
+ </code></td>
+ <td>
+ This context occurs when the menu is invoked on a node that either matches
+ <code>selector</code>, a CSS selector, or has an ancestor that matches.
+ <code>selector</code> may include multiple selectors separated by commas,
+ e.g., <code>"a[href], img"</code>.
+ </td>
+ </tr>
+ <tr>
+ <td><code>
+ URLContext(matchPattern)
+ </code></td>
+ <td>
+ This context occurs when the menu is invoked on pages with particular
+ URLs. <code>matchPattern</code> is a match pattern string or an array of
+ match pattern strings. When <code>matchPattern</code> is an array, the
+ context occurs when the menu is invoked on a page whose URL matches any of
+ the patterns. These are the same match pattern strings that you use with
+ the <a href="packages/addon-kit/docs/page-mod.html"><code>page-mod</code></a>
+ <code>include</code> property.
+ <a href="packages/api-utils/docs/match-pattern.html">Read more about patterns</a>.
+ </td>
+ </tr>
+ <tr>
+ <td>
+ array
+ </td>
+ <td>
+ An array of any of the other types. This context occurs when all contexts
+ in the array occur.
+ </td>
+ </tr>
+</table>
+
+Menu items also have a `context` property that can be used to add and remove
+declarative contexts after construction. For example:
+
+ var context = require("context-menu").SelectorContext("img");
+ myMenuItem.context.add(context);
+ myMenuItem.context.remove(context);
+
+When a menu item is bound to more than one context, it appears in the menu when
+all of those contexts occur.
+
+### In Content Scripts
+
+The declarative contexts are handy but not very powerful. For instance, you
+might want your menu item to appear for any page that has at least one image,
+but declarative contexts won't help you there.
+
+When you need more control control over the context in which your menu items are
+shown, you can use content scripts. Like other APIs in the SDK, the
+`context-menu` API uses
+[content scripts](dev-guide/addon-development/web-content.html) to let your
+add-on interact with pages in the browser. Each menu item you create in the
+top-level context menu can have a content script.
+
+A special event named `"context"` is emitted in your content scripts whenever
+the context menu is about to be shown. If you register a listener function for
+this event and it returns true, the menu item associated with the listener's
+content script is shown in the menu.
+
+For example, this item appears whenever the context menu is invoked on a page
+that contains at least one image:
+
+ require("context-menu").Item({
+ label: "This Page Has Images",
+ contentScript: 'self.on("context", function (node) {' +
+ ' return !!document.querySelector("img");' +
+ '});'
+ });
+
+Note that the listener function has a parameter called `node`. This is the node
+in the page that the user context-clicked to invoke the menu. You can use it to
+determine whether your item should be shown.
+
+You can both specify declarative contexts and listen for contexts in a content
+script. In that case, the declarative contexts are evaluated first. If they
+are not current, then your context listener is never called.
+
+This example takes advantage of that fact. The listener can be assured that
+`node` will always be an image:
+
+ require("context-menu").Item({
+ label: "A Mozilla Image",
+ context: contextMenu.SelectorContext("img"),
+ contentScript: 'self.on("context", function (node) {' +
+ ' return /mozilla/.test(node.src);' +
+ '});'
+ });
+
+Your item is shown only when all declarative contexts are current and your
+context listener returns true.
+
+
+Handling Menu Item Clicks
+-------------------------
+
+In addition to using content scripts to listen for the `"context"` event as
+described above, you can use content scripts to handle item clicks. When the
+user clicks your menu item, an event named `"click"` is emitted in the item's
+content script.
+
+Therefore, to handle an item click, listen for the `"click"` event in that
+item's content script like so:
+
+ require("context-menu").Item({
+ label: "My Item",
+ contentScript: 'self.on("click", function (node, data) {' +
+ ' console.log("Item clicked!");' +
+ '});'
+ });
+
+Note that the listener function has parameters called `node` and `data`. `node`
+is the node that the user context-clicked to invoke the menu. You can use it
+when performing some action. `data` is the `data` property of the menu item
+that was clicked. Since only top-level menu items have content scripts, this
+comes in handy for determining which item in a `Menu` was clicked:
+
+ var cm = require("context-menu");
+ cm.Menu({
+ label: "My Menu",
+ contentScript: 'self.on("click", function (node, data) {' +
+ ' console.log("You clicked " + data);' +
+ '});',
+ items: [
+ cm.Item({ label: "Item 1", data: "item1" }),
+ cm.Item({ label: "Item 2", data: "item2" }),
+ cm.Item({ label: "Item 3", data: "item3" })
+ ]
+ });
+
+Often you will need to collect some kind of information in the click listener
+and perform an action unrelated to content. To communicate to the menu item
+associated with the content script, the content script can call the
+`postMessage` function attached to the global `self` object, passing it some
+JSON-able data. The menu item's `"message"` event listener will be called with
+that data.
+
+ require("context-menu").Item({
+ label: "Edit Image",
+ context: contextMenu.SelectorContext("img"),
+ contentScript: 'self.on("click", function (node, data) {' +
+ ' self.postMessage(node.src);' +
+ '});',
+ onMessage: function (imgSrc) {
+ openImageEditor(imgSrc);
+ }
+ });
+
+
+Updating a Menu Item's Label
+----------------------------
+
+Each menu item must be created with a label, but you can change its label later
+using a couple of methods.
+
+The simplest method is to set the menu item's `label` property. This example
+updates the item's label based on the number of times it's been clicked:
+
+ var numClicks = 0;
+ var myItem = require("context-menu").Item({
+ label: "Click Me: " + numClicks,
+ contentScript: 'self.on("click", self.postMessage);',
+ onMessage: function () {
+ numClicks++;
+ this.label = "Click Me: " + numClicks;
+ // Setting myItem.label is equivalent.
+ }
+ });
+
+Sometimes you might want to update the label based on the context. For
+instance, if your item performs a search with the user's selected text, it would
+be nice to display the text in the item to provide feedback to the user. In
+these cases you can use the second method. Recall that your content scripts can
+listen for the `"context"` event and if your listeners return true, the items
+associated with the content scripts are shown in the menu. In addition to
+returning true, your `"context"` listeners can also return strings. When a
+`"context"` listener returns a string, it becomes the item's new label.
+
+This item implements the aforementioned search example:
+
+ var cm = require("context-menu");
+ cm.Item({
+ label: "Search Google",
+ context: cm.SelectionContext(),
+ contentScript: 'self.on("context", function () {' +
+ ' var text = window.getSelection().toString();' +
+ ' if (text.length > 20)' +
+ ' text = text.substr(0, 20) + "...";' +
+ ' return "Search Google for " + text;' +
+ '});'
+ });
+
+The `"context"` listener gets the window's current selection, truncating it if
+it's too long, and includes it in the returned string. When the item is shown,
+its label will be "Search Google for `text`", where `text` is the truncated
+selection.
+
+
+More Examples
+-------------
+
+For conciseness, these examples create their content scripts as strings and use
+the `contentScript` property. In your own add-on, you will probably want to
+create your content scripts in separate files and pass their URLs using the
+`contentScriptFile` property. See
+[Working with Content Scripts](dev-guide/addon-development/web-content.html)
+for more information.
+
+Show an "Edit Page Source" item when the user right-clicks a non-interactive
+part of the page:
+
+ require("context-menu").Item({
+ label: "Edit Page Source",
+ contentScript: 'self.on("click", function (node, data) {' +
+ ' self.postMessage(document.URL);' +
+ '});',
+ onMessage: function (pageURL) {
+ editSource(pageURL);
+ }
+ });
+
+Show an "Edit Image" item when the menu is invoked on an image:
+
+ require("context-menu").Item({
+ label: "Edit Image",
+ context: contextMenu.SelectorContext("img"),
+ contentScript: 'self.on("click", function (node, data) {' +
+ ' self.postMessage(node.src);' +
+ '});',
+ onMessage: function (imgSrc) {
+ openImageEditor(imgSrc);
+ }
+ });
+
+Show an "Edit Mozilla Image" item when the menu is invoked on an image in a
+mozilla.org or mozilla.com page:
+
+ var cm = require("context-menu");
+ cm.Item({
+ label: "Edit Mozilla Image",
+ context: [
+ cm.URLContext(["*.mozilla.org", "*.mozilla.com"]),
+ cm.SelectorContext("img")
+ ],
+ contentScript: 'self.on("click", function (node, data) {' +
+ ' self.postMessage(node.src);' +
+ '});',
+ onMessage: function (imgSrc) {
+ openImageEditor(imgSrc);
+ }
+ });
+
+Show an "Edit Page Images" item when the page contains at least one image:
+
+ require("context-menu").Item({
+ label: "Edit Page Images",
+ // This ensures the item only appears during the page context.
+ context: contextMenu.PageContext(),
+ contentScript: 'self.on("context", function (node) {' +
+ ' var pageHasImgs = !!document.querySelector("img");' +
+ ' return pageHasImgs;' +
+ '});' +
+ 'self.on("click", function (node, data) {' +
+ ' var imgs = document.querySelectorAll("img");' +
+ ' var imgSrcs = [];' +
+ ' for (var i = 0 ; i < imgs.length; i++)' +
+ ' imgSrcs.push(imgs[i].src);' +
+ ' self.postMessage(imgSrcs);' +
+ '});',
+ onMessage: function (imgSrcs) {
+ openImageEditor(imgSrcs);
+ }
+ });
+
+Show a "Search With" menu when the user right-clicks an anchor that searches
+Google or Wikipedia with the text contained in the anchor:
+
+ var cm = require("context-menu");
+ var googleItem = cm.Item({
+ label: "Google",
+ data: "http://www.google.com/search?q="
+ });
+ var wikipediaItem = cm.Item({
+ label: "Wikipedia",
+ data: "http://en.wikipedia.org/wiki/Special:Search?search="
+ });
+ var searchMenu = cm.Menu({
+ label: "Search With",
+ context: contextMenu.SelectorContext("a[href]"),
+ contentScript: 'self.on("click", function (node, data) {' +
+ ' var searchURL = data + node.textContent;' +
+ ' window.location.href = searchURL;' +
+ '});',
+ items: [googleItem, wikipediaItem]
+ });
+
+
+<api name="Item">
+@class
+A labeled menu item that can perform an action when clicked.
+<api name="Item">
+@constructor
+ Creates a labeled menu item that can perform an action when clicked.
+@param options {object}
+ An object with the following keys:
+ @prop label {string}
+ The item's label. It must either be a string or an object that implements
+ `toString()`.
+ @prop [image] {string}
+ The item's icon, a string URL. The URL can be remote, a reference to an
+ image in the add-on's `data` directory, or a data URI.
+ @prop [data] {string}
+ An optional arbitrary value to associate with the item. It must be either a
+ string or an object that implements `toString()`. It will be passed to
+ click listeners.
+ @prop [context] {value}
+ If the item is contained in the top-level context menu, this declaratively
+ specifies the context under which the item will appear; see Specifying
+ Contexts above. Ignored if the item is contained in a submenu.
+ @prop [contentScript] {string,array}
+ If the item is contained in the top-level context menu, this is the content
+ script or an array of content scripts that the item can use to interact with
+ the page. Ignored if the item is contained in a submenu.
+ @prop [contentScriptFile] {string,array}
+ If the item is contained in the top-level context menu, this is the local
+ file URL of the content script or an array of such URLs that the item can
+ use to interact with the page. Ignored if the item is contained in a
+ submenu.
+ @prop [onMessage] {function}
+ If the item is contained in the top-level context menu, this function will
+ be called when the content script calls `self.postMessage`. It will be
+ passed the data that was passed to `postMessage`. Ignored if the item is
+ contained in a submenu.
+</api>
+
+<api name="label">
+@property {string}
+ The menu item's label. You can set this after creating the item to update its
+ label later.
+</api>
+
+<api name="image">
+@property {string}
+ The item's icon, a string URL. The URL can be remote, a reference to an image
+ in the add-on's `data` directory, or a data URI. You can set this after
+ creating the item to update its image later. To remove the item's image, set
+ it to `null`.
+</api>
+
+<api name="data">
+@property {string}
+ An optional arbitrary value to associate with the item. It must be either a
+ string or an object that implements `toString()`. It will be passed to
+ click listeners. You can set this after creating the item to update its data
+ later.
+</api>
+
+<api name="context">
+@property {list}
+ A list of declarative contexts for which the menu item will appear in the
+ context menu. Contexts can be added by calling `context.add()` and removed by
+ called `context.remove()`. This property is meaningful only for items
+ contained in the top-level context menu.
+</api>
+
+<api name="parentMenu">
+@property {Menu}
+ The item's parent `Menu`, or `null` if the item is contained in the top-level
+ context menu. This property is read-only. To add the item to a new menu,
+ call that menu's `addItem()` method.
+</api>
+
+<api name="contentScript">
+@property {string,array}
+ The content script or the array of content scripts associated with the menu
+ item during creation. This property is meaningful only for items contained in
+ the top-level context menu.
+</api>
+
+<api name="contentScriptFile">
+@property {string,array}
+ The URL of a content script or the array of such URLs associated with the menu
+ item during creation. This property is meaningful only for items contained in
+ the top-level context menu.
+</api>
+
+<api name="destroy">
+@method
+ Permanently removes the item from its parent menu and frees its resources.
+ The item must not be used afterward. If you need to remove the item from its
+ parent menu but use it afterward, call `removeItem()` on the parent menu
+ instead.
+</api>
+
+<api name="message">
+@event
+If you listen to this event you can receive message events from content
+scripts associated with this menu item. When a content script posts a
+message using `self.postMessage()`, the message is delivered to the add-on
+code in the menu item's `message` event.
+
+@argument {value}
+Listeners are passed a single argument which is the message posted
+from the content script. The message can be any
+<a href = "dev-guide/addon-development/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
+</api>
+
+</api>
+
+<api name="Menu">
+@class
+A labeled menu item that expands into a submenu.
+
+<api name="Menu">
+@constructor
+ Creates a labeled menu item that expands into a submenu.
+@param options {object}
+ An object with the following keys:
+ @prop label {string}
+ The item's label. It must either be a string or an object that implements
+ `toString()`.
+ @prop items {array}
+ An array of menu items that the menu will contain. Each must be an `Item`,
+ `Menu`, or `Separator`.
+ @prop [image] {string}
+ The menu's icon, a string URL. The URL can be remote, a reference to an
+ image in the add-on's `data` directory, or a data URI.
+ @prop [context] {value}
+ If the menu is contained in the top-level context menu, this declaratively
+ specifies the context under which the menu will appear; see Specifying
+ Contexts above. Ignored if the menu is contained in a submenu.
+ @prop [contentScript] {string,array}
+ If the menu is contained in the top-level context menu, this is the content
+ script or an array of content scripts that the menu can use to interact with
+ the page. Ignored if the menu is contained in a submenu.
+ @prop [contentScriptFile] {string,array}
+ If the menu is contained in the top-level context menu, this is the local
+ file URL of the content script or an array of such URLs that the menu can
+ use to interact with the page. Ignored if the menu is contained in a
+ submenu.
+ @prop [onMessage] {function}
+ If the menu is contained in the top-level context menu, this function will
+ be called when the content script calls `self.postMessage`. It will be
+ passed the data that was passed to `postMessage`. Ignored if the item is
+ contained in a submenu.
+</api>
+
+<api name="label">
+@property {string}
+ The menu's label. You can set this after creating the menu to update its
+ label later.
+</api>
+
+<api name="items">
+@property {array}
+ An array containing the items in the menu. The array is read-only, meaning
+ that modifications to it will not affect the menu. However, setting this
+ property to a new array will replace all the items currently in the menu with
+ the items in the new array.
+</api>
+
+<api name="image">
+@property {string}
+ The menu's icon, a string URL. The URL can be remote, a reference to an image
+ in the add-on's `data` directory, or a data URI. You can set this after
+ creating the menu to update its image later. To remove the menu's image, set
+ it to `null`.
+</api>
+
+<api name="context">
+@property {list}
+ A list of declarative contexts for which the menu will appear in the context
+ menu. Contexts can be added by calling `context.add()` and removed by called
+ `context.remove()`. This property is meaningful only for menus contained in
+ the top-level context menu.
+</api>
+
+<api name="parentMenu">
+@property {Menu}
+ The menu's parent `Menu`, or `null` if the menu is contained in the top-level
+ context menu. This property is read-only. To add the menu to a new menu,
+ call that menu's `addItem()` method.
+</api>
+
+<api name="contentScript">
+@property {string,array}
+ The content script or the array of content scripts associated with the menu
+ during creation. This property is meaningful only for menus contained in the
+ top-level context menu.
+</api>
+
+<api name="contentScriptFile">
+@property {string,array}
+ The URL of a content script or the array of such URLs associated with the menu
+ during creation. This property is meaningful only for menus contained in the
+ top-level context menu.
+</api>
+
+<api name="addItem">
+@method
+ Appends a menu item to the end of the menu. If the item is already contained
+ in another menu or in the top-level context menu, it's automatically removed
+ first.
+@param item {Item,Menu,Separator}
+ The `Item`, `Menu`, or `Separator` to add to the menu.
+</api>
+
+<api name="removeItem">
+@method
+ Removes the given menu item from the menu. If the menu does not contain the
+ item, this method does nothing.
+@param item {Item,Menu,Separator}
+ The menu item to remove from the menu.
+</api>
+
+<api name="destroy">
+@method
+ Permanently removes the menu from its parent menu and frees its resources.
+ The menu must not be used afterward. If you need to remove the menu from its
+ parent menu but use it afterward, call `removeItem()` on the parent menu
+ instead.
+</api>
+
+<api name="message">
+@event
+If you listen to this event you can receive message events from content
+scripts associated with this menu item. When a content script posts a
+message using `self.postMessage()`, the message is delivered to the add-on
+code in the menu item's `message` event.
+
+@argument {value}
+Listeners are passed a single argument which is the message posted
+from the content script. The message can be any
+<a href = "dev-guide/addon-development/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
+</api>
+
+</api>
+
+<api name="Separator">
+@class
+A menu separator. Separators can be contained only in `Menu`s, not in the
+top-level context menu.
+
+<api name="Separator">
+@constructor
+ Creates a menu separator.
+</api>
+
+<api name="parentMenu">
+@property {Menu}
+ The separator's parent `Menu`. This property is read-only. To add the
+ separator to a new menu, call that menu's `addItem()` method.
+</api>
+
+<api name="destroy">
+@method
+ Permanently removes the separator from its parent menu and frees its
+ resources. The separator must not be used afterward. If you need to remove
+ the separator from its parent menu but use it afterward, call `removeItem()`
+ on the parent menu instead.
+</api>
+
+</api>
+
+<api name="PageContext">
+@class
+<api name="PageContext">
+@constructor
+ Creates a page context. See Specifying Contexts above.
+</api>
+</api>
+
+<api name="SelectionContext">
+@class
+<api name="SelectionContext">
+@constructor
+ Creates a context that occurs when a page contains a selection. See
+ Specifying Contexts above.
+</api>
+</api>
+
+<api name="SelectorContext">
+@class
+<api name="SelectorContext">
+@constructor
+ Creates a context that matches a given CSS selector. See Specifying Contexts
+ above.
+@param selector {string}
+ A CSS selector.
+</api>
+</api>
+
+<api name="URLContext">
+@class
+<api name="URLContext">
+@constructor
+ Creates a context that matches pages with particular URLs. See Specifying
+ Contexts above.
+@param matchPattern {string,array}
+ A [match pattern](packages/api-utils/docs/match-pattern.html) string or an array of
+ match pattern strings.
+</api>
+</api>
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/docs/hotkeys.md b/tools/addon-sdk-1.3/packages/addon-kit/docs/hotkeys.md
new file mode 100644
index 0000000..dfbe572
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/docs/hotkeys.md
@@ -0,0 +1,74 @@
+<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
+
+Some add-ons may wish to define keyboard shortcuts for certain operations. This
+module exposes an API to create those.
+
+<api name="Hotkey">
+@class
+
+Module exports `Hotkey` constructor allowing users to create a `hotkey` for the
+host application.
+
+<api name="Hotkey">
+@constructor
+Creates a hotkey who's `onPress` listener method is invoked when key combination
+defined by `hotkey` is pressed.
+
+Please note: If more than one `hotkey` is created for the same key
+combination, the listener is executed only on the last one created
+
+@param options {Object}
+ Options for the hotkey, with the following keys:
+
+@prop combo {String}
+Any function key: `"f1, f2, ..., f24"` or key combination in the format
+of `'modifier-key'`:
+
+ "accel-s"
+ "meta-shift-i"
+ "control-alt-d"
+
+Modifier keynames:
+
+- **shift**: The Shift key.
+- **alt**: The Alt key. On the Macintosh, this is the Option key. On
+ Macintosh this can only be used in conjunction with another modifier,
+ since `Alt-Letter` combinations are reserved for entering special
+ characters in text.
+- **meta**: The Meta key. On the Macintosh, this is the Command key.
+- **control**: The Control key.
+- **accel**: The key used for keyboard shortcuts on the user's platform,
+ which is Control on Windows and Linux, and Command on Mac. Usually, this
+ would be the value you would use.
+
+@prop onPress {Function}
+Function that is invoked when the key combination `hotkey` is pressed.
+
+</api>
+<api name="destroy">
+@method
+Stops this instance of `Hotkey` from reacting on the key combinations. Once
+destroyed it can no longer be used.
+</api>
+</api>
+
+## Example ##
+
+ // Define keyboard shortcuts for showing and hiding a custom panel.
+ var { Hotkey } = require("hotkeys");
+
+ var showHotKey = Hotkey({
+ combo: "accel-shift-o",
+ onPress: function() {
+ showMyPanel();
+ }
+ });
+ var hideHotKey = Hotkey({
+ combo: "accel-alt-shift-o",
+ onPress: function() {
+ hideMyPanel();
+ }
+ });
+
+[Mozilla keyboard planning FAQ]:http://www.mozilla.org/access/keyboard/
+[keyboard shortcuts]:https://developer.mozilla.org/en/XUL_Tutorial/Keyboard_Shortcuts
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/docs/notifications.md b/tools/addon-sdk-1.3/packages/addon-kit/docs/notifications.md
new file mode 100644
index 0000000..5e76c1b
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/docs/notifications.md
@@ -0,0 +1,60 @@
+<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
+
+The `notifications` module allows you to display transient,
+[toaster](http://en.wikipedia.org/wiki/Toast_%28computing%29)-style
+desktop messages to the user.
+
+This API supports desktop notifications on Windows, OS X using
+[Growl](http://growl.info/), and Linux using libnotify. If the user's system
+does not support desktop notifications or if its notifications service is not
+running, then notifications made with this API are logged to Firefox's error
+console and, if the user launched Firefox from the command line, the terminal.
+
+Examples
+--------
+
+Here's a typical example. When the message is clicked, a string is logged to
+the console.
+
+ var notifications = require("notifications");
+ notifications.notify({
+ title: "Jabberwocky",
+ text: "'Twas brillig, and the slithy toves",
+ data: "did gyre and gimble in the wabe",
+ onClick: function (data) {
+ console.log(data);
+ // console.log(this.data) would produce the same result.
+ }
+ });
+
+This one displays an icon that's stored in the add-on's `data` directory. (See
+the [`self`](packages/addon-kit/docs/self.html) module documentation for more information.)
+
+ var notifications = require("notifications");
+ var self = require("self");
+ var myIconURL = self.data.url("myIcon.png");
+ notifications.notify({
+ text: "I have an icon!",
+ iconURL: myIconURL
+ });
+
+
+<api name="notify">
+@function
+ Displays a transient notification to the user.
+@param options {object}
+ An object with the following keys. Each is optional.
+ @prop [title] {string}
+ A string to display as the message's title.
+ @prop [text] {string}
+ A string to display as the body of the message.
+ @prop [iconURL] {string}
+ The URL of an icon to display inside the message. It may be a remote URL,
+ a data URI, or a URL returned by the [`self`](packages/addon-kit/docs/self.html)
+ module.
+ @prop [onClick] {function}
+ A function to be called when the user clicks the message. It will be passed
+ the value of `data`.
+ @prop [data] {string}
+ A string that will be passed to `onClick`.
+</api>
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/docs/page-mod.md b/tools/addon-sdk-1.3/packages/addon-kit/docs/page-mod.md
new file mode 100644
index 0000000..aa046d4
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/docs/page-mod.md
@@ -0,0 +1,366 @@
+<!-- contributed by Nickolay Ponomarev [asqueella@gmail.com] -->
+<!-- contributed by Myk Melez [myk@mozilla.org] -->
+<!-- contributed by Irakli Gozalishvil [gozala@mozilla.com] -->
+
+Overview
+--------
+The page-mod module enables add-on developers to execute scripts in the context
+of specific web pages. Most obviously you could use page-mod to dynamically
+modify the content of certain pages.
+
+The module exports a constructor function `PageMod` which creates a new page
+modification (or "mod" for short).
+
+A page mod does not modify its pages until those pages are loaded or reloaded.
+In other words, if your add-on is loaded while the user's browser is open, the
+user will have to reload any open pages that match the mod for the mod to affect
+them.
+
+To stop a page mod from making any more modifications, call its `destroy`
+method.
+
+Like all modules that interact with web content, page-mod uses content
+scripts that execute in the content process and defines a messaging API to
+communicate between the content scripts and the main add-on script. For more
+details on content scripting see the tutorial on [interacting with web
+content](dev-guide/addon-development/web-content.html).
+
+To create a PageMod the add-on developer supplies:
+
+* a set of rules to select the desired subset of web pages based on their URL.
+Each rule is specified using the
+[match-pattern](packages/api-utils/docs/match-pattern.html) syntax.
+
+* a set of content scripts to execute in the context of the desired pages.
+
+* a value for the onAttach option: this value is a function which will be
+called when a page is loaded that matches the ruleset. This is used to set up a
+communication channel between the add-on code and the content script.
+
+All these parameters are optional except for the ruleset, which must include
+at least one rule.
+
+The following add-on displays an alert whenever a page matching the ruleset is
+loaded:
+
+ var pageMod = require("page-mod");
+ pageMod.PageMod({
+ include: "*.org",
+ contentScript: 'window.alert("Page matches ruleset");'
+ });
+
+If you specify a value of "ready" or "end" for `contentScriptWhen`,
+then the content script can interact with the DOM itself:
+
+ var pageMod = require("page-mod");
+ pageMod.PageMod({
+ include: "*.org",
+ contentScriptWhen: 'end',
+ contentScript: 'document.body.innerHTML = ' +
+ ' "<h1>Page matches ruleset</h1>";'
+ });
+
+### Using `contentScriptFile` ###
+
+Most of the examples in this page define content scripts as strings,
+and use the `contentScript` option to assign them to page mods.
+
+In your code you will more often create content scripts in separate files
+under your add-on's `data` directory. Then you can use the
+[`self`](packages/addon-kit/docs/self.html) module to retrieve a URL pointing
+to the file, and assign this to the page-mod's `contentScriptFile`
+property.
+
+For example, if you save the content script
+file in your `data` directory as "myScript.js", you would assign it using
+code like:
+
+ var data = require("self").data;
+
+ var pageMod = require("page-mod");
+ pageMod.PageMod({
+ include: "*.org",
+ contentScriptWhen: 'end',
+ contentScriptFile: data.url("myScript.js")
+ });
+
+## Communicating With Content Scripts ##
+
+When a matching page is loaded the `PageMod` will call the function that the
+add-on code supplied to `onAttach`. The `PageMod` supplies one argument to
+this function: a `worker` object.
+
+The worker can be thought of as the add-on's end of
+a communication channel between the add-on code and the content scripts that
+have been attached to this page.
+
+Thus the add-on can pass messages to the content scripts by calling the
+worker's `postMessage` function and can receive messages from the content
+scripts by registering a function as a listener to the worker's `on` function.
+
+Note that if multiple matching pages are loaded simultaneously then each page
+is loaded into its own execution context with its own copy of the content
+scripts. In this case `onAttach` is called once for each loaded page, and the
+add-on code will have a separate worker for each page:
+
+![Multiple workers](static-files/media/multiple-workers.jpg)
+
+This is demonstrated in the following example:
+
+ var pageMod = require("page-mod");
+ var tabs = require("tabs");
+
+ var workers = [];
+
+ pageMod.PageMod({
+ include: ["http://www.mozilla*"],
+ contentScriptWhen: 'end',
+ contentScript: "onMessage = function onMessage(message) {" +
+ " window.alert(message);};",
+ onAttach: function onAttach(worker) {
+ if (workers.push(worker) == 3) {
+ workers[0].postMessage("The first worker!");
+ workers[1].postMessage("The second worker!");
+ workers[2].postMessage("The third worker!");
+ }
+ }
+ });
+
+ tabs.open("http://www.mozilla.com");
+ tabs.open("http://www.mozilla.org");
+ tabs.open("http://www.mozilla-europe.org");
+
+Here we specify a ruleset to match any URLs starting with
+"http://www.mozilla". When a page matches we add the supplied worker to
+an array, and when we have three workers in the array we send a message to
+each worker in turn, telling it the order in which it was attached. The
+worker just displays the message in an alert box.
+
+This shows that separate pages execute in separate contexts and that each
+context has its own communication channel with the add-on script.
+
+Note though that while there is a separate worker for each execution context,
+the worker is shared across all the content scripts associated with a single
+execution context. In the following example we pass two content scripts into
+the `PageMod`: these content scripts will share a worker instance.
+
+In the example each content script identifies itself to the add-on script
+by sending it a message using the global `postMessage` function. In the
+`onAttach` function the add-on code logs the fact that a new page is
+attached and registers a listener function that simply logs the message:
+
+
+ var pageMod = require("page-mod");
+ var data = require("self").data;
+ var tabs = require("tabs");
+
+ pageMod.PageMod({
+ include: ["http://www.mozilla*"],
+ contentScriptWhen: 'end',
+ contentScript: ["postMessage('Content script 1 is attached to '+ " +
+ "document.URL);",
+ "postMessage('Content script 2 is attached to '+ " +
+ "document.URL);"],
+ onAttach: function onAttach(worker) {
+ console.log("Attaching content scripts")
+ worker.on('message', function(data) {
+ console.log(data);
+ });
+ }
+ });
+
+ tabs.open("http://www.mozilla.com");
+
+The console output of this add-on is:
+
+<pre>
+ info: Attaching content scripts
+ info: Content script 1 is attached to http://www.mozilla.com/en-US/
+ info: Content script 2 is attached to http://www.mozilla.com/en-US/
+</pre>
+
+### Mapping workers to tabs ###
+
+The [`worker`](packages/api-utils/docs/content/worker.html) has a `tab`
+property which returns the tab associated with this worker. You can use this
+to access the [`tabs API`](packages/addon-kit/docs/tabs.html) for the tab
+associated with a specific page:
+
+ var pageMod = require("page-mod");
+ var tabs = require("tabs");
+
+ pageMod.PageMod({
+ include: ["*"],
+ onAttach: function onAttach(worker) {
+ console.log(worker.tab.title);
+ }
+ });
+
+### Attaching content scripts to tabs ###
+
+We've seen that the page mod API attaches content scripts to pages based on
+their URL. Sometimes, though, we don't care about the URL: we just want
+to execute a script on demand in the context of a particular tab.
+
+For example, we might want to run a script in the context of the currently
+active tab when the user clicks a widget: to block certain content, to
+change the font style, or to display the page's DOM structure.
+
+Using the `attach` method of the [`tab`](packages/addon-kit/docs/tabs.html)
+object, you can attach a set of content scripts to a particular tab. The
+scripts are executed immediately.
+
+The following add-on creates a widget which, when clicked, highlights all the
+`div` elements in the page loaded into the active tab:
+
+ var widgets = require("widget");
+ var tabs = require("tabs");
+
+ var widget = widgets.Widget({
+ id: "div-show",
+ label: "Show divs",
+ contentURL: "http://www.mozilla.org/favicon.ico",
+ onClick: function() {
+ tabs.activeTab.attach({
+ contentScript:
+ 'var divs = document.getElementsByTagName("div");' +
+ 'for (var i = 0; i < divs.length; ++i) {' +
+ 'divs[i].setAttribute("style", "border: solid red 1px;");' +
+ '}'
+ });
+ }
+ });
+
+## Destroying Workers ##
+
+Workers generate a `detach` event when their associated page is closed: that
+is, when the tab is closed or the tab's location changes. If
+you are maintaining a list of workers belonging to a page mod, you can use
+this event to remove workers that are no longer valid.
+
+For example, if you maintain a list of workers attached to a page mod:
+
+ var workers = [];
+
+ var pageMod = require("page-mod").PageMod({
+ include: ['*'],
+ contentScriptWhen: 'ready',
+ contentScriptFile: data.url('pagemod.js'),
+ onAttach: function(worker) {
+ workers.push(worker);
+ }
+ });
+
+You can remove workers when they are no longer valid by listening to `detach`:
+
+ var workers = [];
+
+ function detachWorker(worker, workerArray) {
+ var index = workerArray.indexOf(worker);
+ if(index != -1) {
+ workerArray.splice(index, 1);
+ }
+ }
+
+ var pageMod = require("page-mod").PageMod({
+ include: ['*'],
+ contentScriptWhen: 'ready',
+ contentScriptFile: data.url('pagemod.js'),
+ onAttach: function(worker) {
+ workers.push(worker);
+ worker.on('detach', function () {
+ detachWorker(this, workers);
+ });
+ }
+ });
+
+<api name="PageMod">
+@class
+A PageMod object. Once activated a page mod will execute the supplied content
+scripts in the context of any pages matching the pattern specified by the
+'include' property.
+<api name="PageMod">
+@constructor
+Creates a PageMod.
+@param options {object}
+ Options for the PageMod, with the following keys:
+ @prop include {string,array}
+ A match pattern string or an array of match pattern strings. These define
+ the pages to which the PageMod applies. See the
+ [match-pattern](packages/api-utils/docs/match-pattern.html) module for
+ a description of match pattern syntax.
+ At least one match pattern must be supplied.
+
+ @prop [contentScriptFile] {string,array}
+ The local file URLs of content scripts to load. Content scripts specified
+ by this option are loaded *before* those specified by the `contentScript`
+ option. Optional.
+ @prop [contentScript] {string,array}
+ The texts of content scripts to load. Content scripts specified by this
+ option are loaded *after* those specified by the `contentScriptFile` option.
+ Optional.
+ @prop [contentScriptWhen="end"] {string}
+ When to load the content scripts. This may take one of the following
+ values:
+
+ * "start": load content scripts immediately after the document
+ element for the page is inserted into the DOM, but before the DOM content
+ itself has been loaded
+ * "ready": load content scripts once DOM content has been loaded,
+ corresponding to the
+ [DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+ event
+ * "end": load content scripts once all the content (DOM, JS, CSS,
+ images) for the page has been loaded, at the time the
+ [window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+ fires
+
+ This property is optional and defaults to "end".
+
+ @prop [onAttach] {function}
+A function to call when the PageMod attaches content scripts to
+a matching page. The function will be called with one argument, a `worker`
+object which the add-on script can use to communicate with the content scripts
+attached to the page in question.
+
+</api>
+
+<api name="include">
+@property {List}
+A [list](packages/api-utils/docs/list.html) of match pattern strings. These
+define the pages to which the page mod applies. See the
+[match-pattern](packages/api-utils/docs/match-pattern.html) module for a
+description of match patterns. Rules can be added to the list by calling its
+`add` method and removed by calling its `remove` method.
+
+</api>
+
+<api name="destroy">
+@method
+Stops the page mod from making any more modifications. Once destroyed the page
+mod can no longer be used. Note that modifications already made to open pages
+will not be undone.
+</api>
+
+<api name="attach">
+@event
+This event is emitted this event when the page-mod's content scripts are
+attached to a page whose URL matches the page-mod's `include` filter.
+
+@argument {Worker}
+The listener function is passed a [`Worker`](packages/api-utils/docs/content/worker.html) object that can be used to communicate
+with any content scripts attached to this page.
+</api>
+
+<api name="error">
+@event
+This event is emitted when an uncaught runtime error occurs in one of the page
+mod's content scripts.
+
+@argument {Error}
+Listeners are passed a single argument, the
+[Error](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error)
+object.
+</api>
+
+</api>
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/docs/page-worker.md b/tools/addon-sdk-1.3/packages/addon-kit/docs/page-worker.md
new file mode 100644
index 0000000..06097ae
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/docs/page-worker.md
@@ -0,0 +1,218 @@
+<!-- contributed by Felipe Gomes [felipc@gmail.com] -->
+
+The `page-worker` module provides a way to create a permanent, invisible page
+and access its DOM.
+
+Introduction
+------------
+
+The module exports a constructor function `Page`, which constructs a new page
+worker. A page worker may be destroyed, after which its memory is freed, and
+you must create a new instance to load another page.
+
+Page workers have associated content scripts, which are JavaScript scripts that
+have access to the content loaded into the pages. You can specify scripts to
+load for a page worker, and you communicate with those scripts over an
+asynchronous JSON pipe. For more information on content scripts, see
+[Working with Content Scripts](dev-guide/addon-development/web-content.html).
+
+Examples
+--------
+
+For conciseness, these examples create their content scripts as strings and use
+the `contentScript` property. In your own add-ons, you will probably want to
+create your content scripts in separate files and pass their URLs using the
+`contentScriptFile` property. See
+[Working with Content Scripts](dev-guide/addon-development/web-content.html)
+for more information.
+
+### Print all header titles from a Wikipedia article ###
+
+ var pageWorkers = require("page-worker");
+
+ // This content script sends header titles from the page to the add-on:
+ var script = "var elements = document.querySelectorAll('h2 > span'); " +
+ "for (var i = 0; i < elements.length; i++) { " +
+ " postMessage(elements[i].textContent) " +
+ "}";
+
+ // Create a page worker that loads Wikipedia:
+ pageWorkers.Page({
+ contentURL: "http://en.wikipedia.org/wiki/Internet",
+ contentScript: script,
+ contentScriptWhen: "ready",
+ onMessage: function(message) {
+ console.log(message);
+ }
+ });
+
+The page worker's "message" event listener, specified by `onMessage`, will print
+all the titles it receives from the content script.
+
+<api name="Page">
+@class
+A `Page` object loads the page specified by its `contentURL` option and
+executes any content scripts that have been supplied to it in the
+`contentScript` and `contentScriptFile` options.
+
+The page is not displayed to the user.
+
+The page worker is loaded as soon as the `Page` object is created and stays
+loaded until its `destroy` method is called or the add-on is unloaded.
+
+<api name="Page">
+@constructor
+ Creates an uninitialized page worker instance.
+@param [options] {object}
+ The *`options`* parameter is optional, and if given it should be an object
+ with any of the following keys:
+ @prop [contentURL] {string}
+ The URL of the content to load in the panel.
+ @prop [allow] {object}
+ An object with keys to configure the permissions on the page worker. The
+ boolean key `script` controls if scripts from the page are allowed to run.
+ `script` defaults to true.
+ @prop [contentScriptFile] {string,array}
+ A local file URL or an array of local file URLs of content scripts to load.
+ Content scripts specified by this option are loaded *before* those specified
+ by the `contentScript` option. See
+ [Working with Content Scripts](dev-guide/addon-development/web-content.html)
+ for help on setting this property.
+ @prop [contentScript] {string,array}
+ A string or an array of strings containing the texts of content scripts to
+ load. Content scripts specified by this option are loaded *after* those
+ specified by the `contentScriptFile` option.
+ @prop [contentScriptWhen="end"] {string}
+ When to load the content scripts. This may take one of the following
+ values:
+
+ * "start": load content scripts immediately after the document
+ element for the page is inserted into the DOM, but before the DOM content
+ itself has been loaded
+ * "ready": load content scripts once DOM content has been loaded,
+ corresponding to the
+ [DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+ event
+ * "end": load content scripts once all the content (DOM, JS, CSS,
+ images) for the page has been loaded, at the time the
+ [window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+ fires
+
+ This property is optional and defaults to "end".
+
+ @prop [onMessage] {function}
+ Use this to add a listener to the page worker's `message` event.
+</api>
+
+<api name="port">
+@property {EventEmitter}
+[EventEmitter](packages/api-utils/docs/events.html) object that allows you to:
+
+* send events to the content script using the `port.emit` function
+* receive events from the content script using the `port.on` function
+
+See the guide to
+<a href="dev-guide/addon-development/content-scripts/using-port.html">
+communicating using <code>port</code></a> for details.
+</api>
+
+<api name="contentURL">
+@property {string}
+The URL of the content loaded.
+</api>
+
+<api name="allow">
+@property {object}
+ A object describing permissions for the content. It contains a single key
+ named `script` whose value is a boolean that indicates whether or not to
+ execute script in the content. `script` defaults to true.
+</api>
+
+<api name="contentScriptFile">
+@property {string,array}
+A local file URL or an array of local file URLs of content scripts to load.
+</api>
+
+<api name="contentScript">
+@property {string,array}
+A string or an array of strings containing the texts of content scripts to
+load.
+</api>
+
+<api name="contentScriptWhen">
+@property {string}
+ When to load the content scripts. This may have one of the following
+ values:
+
+ * "start": load content scripts immediately after the document
+ element for the page is inserted into the DOM, but before the DOM content
+ itself has been loaded
+ * "ready": load content scripts once DOM content has been loaded,
+ corresponding to the
+ [DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+ event
+ * "end": load content scripts once all the content (DOM, JS, CSS,
+ images) for the page has been loaded, at the time the
+ [window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+ fires
+
+</api>
+
+<api name="destroy">
+@method
+Unloads the page worker. After you destroy a page worker, its memory is freed
+and you must create a new instance if you need to load another page.
+</api>
+
+<api name="postMessage">
+@method
+Sends a message to the content scripts.
+@param message {value}
+The message to send. Must be JSON-able.
+</api>
+
+<api name="on">
+@method
+Registers an event listener with the page worker. See
+[Working with Events](dev-guide/addon-development/events.html) for help with
+events.
+@param type {string}
+The type of event to listen for.
+@param listener {function}
+The listener function that handles the event.
+</api>
+
+<api name="removeListener">
+@method
+Unregisters an event listener from the page worker.
+@param type {string}
+The type of event for which `listener` was registered.
+@param listener {function}
+The listener function that was registered.
+</api>
+
+<api name="message">
+@event
+If you listen to this event you can receive message events from content
+scripts associated with this page worker. When a content script posts a
+message using `self.postMessage()`, the message is delivered to the add-on
+code in the page worker's `message` event.
+
+@argument {value}
+Listeners are passed a single argument which is the message posted
+from the content script. The message can be any
+<a href = "dev-guide/addon-development/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>
+</api>
+
+<api name="error">
+@event
+This event is emitted when an uncaught runtime error occurs in one of the
+page worker's content scripts.
+
+@argument {Error}
+Listeners are passed a single argument, the
+[Error](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error)
+object.
+</api>
+
+</api>
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/docs/panel.md b/tools/addon-sdk-1.3/packages/addon-kit/docs/panel.md
new file mode 100644
index 0000000..45eea2f
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/docs/panel.md
@@ -0,0 +1,301 @@
+<!-- contributed by Myk Melez [myk@mozilla.org] -->
+<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
+
+The `panel` module creates floating modal "popup dialogs" that appear on top of
+web content and browser chrome and persist until dismissed by users or programs.
+Panels are useful for presenting temporary interfaces to users in a way that is
+easier for users to ignore and dismiss than a modal dialog, since panels are
+hidden the moment users interact with parts of the application interface outside
+them.
+
+The module exports a single constructor function `Panel` which constructs a
+new panel.
+
+A panel's content is loaded as soon as it is created, before the panel is shown,
+and the content remains loaded when a panel is hidden, so it is possible
+to keep a panel around in the background, updating its content as appropriate
+in preparation for the next time it is shown.
+
+Your add-on can receive notifications when a panel is shown or hidden by
+listening to its `show` and `hide` events.
+
+Panels have associated content scripts, which are JavaScript scripts that have
+access to the content loaded into the panels. An add-on can specify one or
+more content scripts to load for a panel, and the add-on can communicate with
+those scripts either using the `message` event or by using user-defined
+events. See
+[Working with Content Scripts](dev-guide/addon-development/web-content.html)
+for more information.
+
+The panel's default style is different for each operating system.
+For example, suppose a panel's content is specified with the following HTML:
+
+<script type="syntaxhighlighter" class="brush: html"><![CDATA[
+<h1>Default Style</h1>
+
+This is what a panel with no custom styling looks like.
+]]>
+</script>
+
+On OS X it will look like this:
+
+<img class="image-center" src="static-files/media/screenshots/default-panel-osx.png"
+alt="OS X panel default style">
+<br>
+
+On Windows 7 it will look like this:
+
+<img class="image-center" src="static-files/media/screenshots/default-panel-windows.png"
+alt="Windows 7 panel default style">
+<br>
+
+On Ubuntu it will look like this:
+
+<img class="image-center" src="static-files/media/screenshots/default-panel-ubuntu.png"
+alt="Ubuntu panel default style">
+<br>
+
+This helps to ensure that the panel's style is consistent with the dialogs
+displayed by Firefox and other applications, but means you need to take care
+when applying your own styles. For example, if you set the panel's
+`background-color` property to `white` and do not set the `color` property,
+then the panel's text will be invisible on OS X although it looks fine on Ubuntu.
+
+Examples
+--------
+
+Create and show a simple panel with content from the `data/` directory:
+
+ var data = require("self").data;
+ var panel = require("panel").Panel({
+ contentURL: data.url("foo.html")
+ });
+
+ panel.show();
+
+The tutorial section on
+[web content](dev-guide/addon-development/web-content.html) has
+a more complex example using panels.
+
+<api name="Panel">
+@class
+The Panel object represents a floating modal dialog that can by an add-on to
+present user interface content.
+
+Once a panel object has been created it can be shown and hidden using its
+`show()` and `hide()` methods. Once a panel is no longer needed it can be
+deactivated using `destroy()`.
+
+The content of a panel is specified using the `contentURL` option. An add-on
+can interact with the content of a panel using content scripts which it
+supplies in the `contentScript` and/or `contentScriptFile` options. For example,
+a content script could create a menu and send the user's selection to the
+add-on.
+
+<api name="Panel">
+@constructor
+Creates a panel.
+@param options {object}
+ Options for the panel, with the following keys:
+ @prop [width] {number}
+ The width of the panel in pixels. Optional.
+ @prop [height] {number}
+ The height of the panel in pixels. Optional.
+ @prop [contentURL] {string}
+ The URL of the content to load in the panel.
+ @prop [allow] {object}
+ An optional object describing permissions for the content. It should
+ contain a single key named `script` whose value is a boolean that indicates
+ whether or not to execute script in the content. `script` defaults to true.
+ @prop [contentScriptFile] {string,array}
+ A local file URL or an array of local file URLs of content scripts to load.
+ Content scripts specified by this property are loaded *before* those
+ specified by the `contentScript` property.
+ @prop [contentScript] {string,array}
+ A string or an array of strings containing the texts of content scripts to
+ load. Content scripts specified by this property are loaded *after* those
+ specified by the `contentScriptFile` property.
+ @prop [contentScriptWhen="end"] {string}
+ When to load the content scripts. This may take one of the following
+ values:
+
+ * "start": load content scripts immediately after the document
+ element for the panel is inserted into the DOM, but before the DOM content
+ itself has been loaded
+ * "ready": load content scripts once DOM content has been loaded,
+ corresponding to the
+ [DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+ event
+ * "end": load content scripts once all the content (DOM, JS, CSS,
+ images) for the panel has been loaded, at the time the
+ [window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+ fires
+
+ This property is optional and defaults to "end".
+
+ @prop [onMessage] {function}
+ Include this to listen to the panel's `message` event.
+ @prop [onShow] {function}
+ Include this to listen to the panel's `show` event.
+ @prop [onHide] {function}
+ Include this to listen to the panel's `hide` event.
+</api>
+
+<api name="port">
+@property {EventEmitter}
+[EventEmitter](packages/api-utils/docs/events.html) object that allows you to:
+
+* send events to the content script using the `port.emit` function
+* receive events from the content script using the `port.on` function
+
+See the guide to
+<a href="dev-guide/addon-development/content-scripts/using-port.html">
+communicating using <code>port</code></a> for details.
+</api>
+
+<api name="isShowing">
+@property {boolean}
+Tells if the panel is currently shown or not. This property is read-only.
+</api>
+
+<api name="height">
+@property {number}
+The height of the panel in pixels.
+</api>
+
+<api name="width">
+@property {number}
+The width of the panel in pixels.
+</api>
+
+<api name="contentURL">
+@property {string}
+The URL of the content loaded in the panel.
+</api>
+
+<api name="allow">
+@property {object}
+An object describing permissions for the content. It contains a single key
+named `script` whose value is a boolean that indicates whether or not to execute
+script in the content.
+</api>
+
+<api name="contentScriptFile">
+@property {string,array}
+A local file URL or an array of local file URLs of content scripts to load.
+Content scripts specified by this property are loaded *before* those
+specified by the `contentScript` property.
+</api>
+
+<api name="contentScript">
+@property {string,array}
+A string or an array of strings containing the texts of content scripts to
+load. Content scripts specified by this property are loaded *after* those
+specified by the `contentScriptFile` property.
+</api>
+
+<api name="contentScriptWhen">
+@property {string}
+When to load the content scripts. This may have one of the following
+values:
+
+* "start": load content scripts immediately after the document
+element for the panel is inserted into the DOM, but before the DOM content
+itself has been loaded
+* "ready": load content scripts once DOM content has been loaded,
+corresponding to the
+[DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+event
+* "end": load content scripts once all the content (DOM, JS, CSS,
+images) for the panel has been loaded, at the time the
+[window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+fires
+
+</api>
+
+<api name="destroy">
+@method
+Destroys the panel, unloading any content that was loaded in it. Once
+destroyed, the panel can no longer be used. If you just want to hide
+the panel and might show it later, use `hide` instead.
+</api>
+
+<api name="postMessage">
+@method
+Sends a message to the content scripts.
+@param message {value}
+The message to send. Must be stringifiable to JSON.
+</api>
+
+<api name="show">
+@method
+Displays the panel.
+</api>
+
+<api name="hide">
+@method
+Stops displaying the panel.
+</api>
+
+<api name="resize">
+@method
+Resizes the panel.
+@param width {number}
+The new width of the panel in pixels.
+@param height {number}
+The new height of the panel in pixels.
+</api>
+
+<api name="on">
+@method
+ Registers an event listener with the panel.
+@param type {string}
+ The type of event to listen for.
+@param listener {function}
+ The listener function that handles the event.
+</api>
+
+<api name="removeListener">
+@method
+ Unregisters an event listener from the panel.
+@param type {string}
+ The type of event for which `listener` was registered.
+@param listener {function}
+ The listener function that was registered.
+</api>
+
+<api name="show">
+@event
+This event is emitted when the panel is shown.
+</api>
+
+<api name="hide">
+@event
+This event is emitted when the panel is hidden.
+</api>
+
+<api name="message">
+@event
+If you listen to this event you can receive message events from content
+scripts associated with this panel. When a content script posts a
+message using `self.postMessage()`, the message is delivered to the add-on
+code in the panel's `message` event.
+
+@argument {value}
+Listeners are passed a single argument which is the message posted
+from the content script. The message can be any
+<a href = "dev-guide/addon-development/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
+</api>
+
+<api name="error">
+@event
+This event is emitted when an uncaught runtime error occurs in one of the
+panel's content scripts.
+
+@argument {Error}
+Listeners are passed a single argument, the
+[Error](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error)
+object.
+</api>
+
+</api>
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/docs/passwords.md b/tools/addon-sdk-1.3/packages/addon-kit/docs/passwords.md
new file mode 100644
index 0000000..6a4b246
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/docs/passwords.md
@@ -0,0 +1,564 @@
+<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
+
+The `passwords` module allows add-ons to interact with Firefox's
+[Password Manager](http://support.mozilla.com/en-US/kb/Remembering%20passwords)
+to add, retrieve and remove stored credentials.
+
+A _credential_ is the set of information a user supplies to authenticate
+herself with a service. Typically a credential consists of a username and a
+password.
+
+Using this module you can:
+
+1. **Search** for credentials which have been stored in the Password Manager.
+ You can then use the credentials to access their related service (for
+ example, by logging into a web site).
+
+2. **Store** credentials in the Password Manager. You can store different sorts
+ of credentials, as outlined in the "Credentials" section below.
+
+3. **Remove** stored credentials from the Password Manager.
+
+## Credentials ##
+
+In this API, credentials are represented by objects.
+
+You create credential objects to pass into the API, and the API also returns
+credential objects to you. The sections below explain both the properties you
+should define on credential objects and the properties you can expect on
+credential objects returned by the API.
+
+All credential objects include `username` and `password` properties. Different
+sorts of stored credentials include various additional properties, as
+outlined in this section.
+
+You can use the Passwords API with three sorts of credentials:
+
+* Add-on credentials
+* HTML form credentials
+* HTTP Authentication credentials
+
+### Add-on Credential ###
+
+These are associated with your add-on rather than a particular web site.
+They contain the following properties:
+
+<table>
+<colgroup>
+<col width="25%">
+</colgroup>
+<tr>
+ <td>
+ <code>username</code>
+ </td>
+ <td>
+ The username.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>password</code>
+ </td>
+ <td>
+ The password.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>url</code>
+ </td>
+ <td>
+ <p>For an add-on credential, this property is of the form:<br><code>
+ addon:&lt;addon-id&gt;</code>, where <code>&lt;addon-id&gt;</code>
+ is the add-on's
+ <a href="dev-guide/addon-development/program-id.html">
+ Program ID</a>.</p>
+ <p>You don't supply this value when storing an add-on credential: it is
+ automatically generated for you. However, you can use it to work out
+ which stored credentials belong to your add-on by comparing it with the
+ <code>uri</code> property of the
+ <a href="packages/addon-kit/docs/self.html"><code>self</code></a>
+ module.</p>
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>realm</code>
+ </td>
+ <td>
+ <p>You can use this as a name for the credential, to distinguish
+ it from any other credentials you've stored.</p>
+ <p>The realm is displayed
+ in Firefox's Password Manager, under "Site", in brackets after the URL.
+ For example, if the realm for a credential is "User Registration", then
+ its "Site" field will look something like:</p>
+ <code>addon:jid0-01mBBFyu0ZAXCFuB1JYKooSTKIc (User Registration)</code>
+ </td>
+</tr>
+
+</table>
+
+### HTML Form Credential ###
+
+If a web service uses HTML forms to authenticate its users, then the
+corresponding credential is an HTML Form credential.
+
+It contains the following properties:
+
+<table>
+<colgroup>
+<col width="25%">
+</colgroup>
+<tr>
+ <td>
+ <code>username</code>
+ </td>
+ <td>
+ The username.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>password</code>
+ </td>
+ <td>
+ The password.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>url</code>
+ </td>
+ <td>
+ The URL for the web service which requires the credential.
+ You should omit anything after the hostname and (optional) port.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>formSubmitURL</code>
+ </td>
+ <td>
+ The value of the form's "action" attribute.
+ You should omit anything after the hostname and (optional) port.
+ If the form doesn't contain an "action" attribute, this property should
+ match the <code>url</code> property.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>usernameField</code>
+ </td>
+ <td>
+ The value of the "name" attribute for the form's username field.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>passwordField</code>
+ </td>
+ <td>
+ The value of the "name" attribute for the form's password field.
+ </td>
+</tr>
+
+</table>
+
+So: given a form at `http://www.example.com/login`
+with the following HTML:
+
+<script type="syntaxhighlighter" class="brush: html"><![CDATA[
+<form action="http://login.example.com/foo/authenticate.cgi">
+ <div>Please log in.</div>
+ <label>Username:</label> <input type="text" name="uname">
+ <label>Password:</label> <input type="password" name="pword">
+</form>
+]]>
+</script>
+
+The corresponding values for the credential (excluding username and password)
+should be:
+
+<pre>
+ url: "http://www.example.com"
+ formSubmitURL: "http://login.example.com"
+ usernameField: "uname"
+ passwordField: "pword"
+</pre>
+
+Note that for both `url` and `formSubmitURL`, the portion of the URL after the
+hostname is omitted.
+
+### HTTP Authentication Credential ###
+
+These are used to authenticate the user to a web site
+which uses HTTP Authentication, as detailed in
+[RFC 2617](http://tools.ietf.org/html/rfc2617).
+They contain the following properties:
+
+<table>
+<colgroup>
+<col width="25%">
+</colgroup>
+<tr>
+ <td>
+ <code>username</code>
+ </td>
+ <td>
+ The username.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>password</code>
+ </td>
+ <td>
+ The password.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>url</code>
+ </td>
+ <td>
+ The URL for the web service which requires the credential.
+ You should omit anything after the hostname and (optional) port.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>realm</code>
+ </td>
+ <td>
+ <p>The WWW-Authenticate response header sent by the server may include a
+ "realm" field as detailed in
+ <a href="http://tools.ietf.org/html/rfc2617">RFC 2617</a>. If it does,
+ this property contains the value for the "realm" field. Otherwise, it is
+ omitted.</p>
+ <p>The realm is displayed in Firefox's Password Manager, under "Site",
+ in brackets after the URL.</p>
+ </td>
+</tr>
+
+</table>
+
+So: if a web server at `http://www.example.com` requested authentication with
+a response code like this:
+
+<pre>
+ HTTP/1.0 401 Authorization Required
+ Server: Apache/1.3.27
+ WWW-Authenticate: Basic realm="ExampleCo Login"
+</pre>
+
+The corresponding values for the credential (excluding username and password)
+should be:
+
+<pre>
+ url: "http://www.example.com"
+ realm: "ExampleCo Login"
+</pre>
+
+## onComplete and onError ##
+
+This API is explicitly asynchronous, so all its functions take two callback
+functions as additional options: `onComplete` and `onError`.
+
+`onComplete` is called when the operation has completed successfully and
+`onError` is called when the function encounters an error.
+
+Because the `search` function is expected to return a list of matching
+credentials, its `onComplete` option is mandatory. Because the other functions
+don't return a value in case of success their `onComplete` options are
+optional.
+
+For all functions, `onError` is optional.
+
+<api name="search">
+@function
+
+This function is used to retrieve a credential, or a list of credentials,
+stored in the Password Manager.
+
+You pass it any subset of the possible properties a credential can contain.
+Credentials which match all the properties you supplied are returned as an
+argument to the `onComplete` callback.
+
+So if you pass in an empty set of properties, all stored credentials are
+returned:
+
+ function show_all_passwords() {
+ require("passwords").search({
+ onComplete: function onComplete(credentials) {
+ credentials.forEach(function(credential) {
+ console.log(credential.username);
+ console.log(credential.password);
+ });
+ }
+ });
+ }
+
+If you pass it a single property, only credentials matching that property are
+returned:
+
+ function show_passwords_for_joe() {
+ require("passwords").search({
+ username: "joe",
+ onComplete: function onComplete(credentials) {
+ credentials.forEach(function(credential) {
+ console.log(credential.username);
+ console.log(credential.password);
+ });
+ }
+ });
+ }
+
+If you pass more than one property, returned credentials must match all of
+them:
+
+ function show_google_password_for_joe() {
+ require("passwords").search({
+ username: "joe",
+ url: "https://www.google.com",
+ onComplete: function onComplete(credentials) {
+ credentials.forEach(function(credential) {
+ console.log(credential.username);
+ console.log(credential.password);
+ });
+ }
+ });
+ }
+
+To retrieve only credentials associated with your add-on, use the `url`
+property, initialized from `self.uri`:
+
+ function show_my_addon_passwords() {
+ require("passwords").search({
+ url: require("self").uri,
+ onComplete: function onComplete(credentials) {
+ credentials.forEach(function(credential) {
+ console.log(credential.username);
+ console.log(credential.password);
+ });
+ }
+ });
+ }
+
+@param options {object}
+The `options` object may contain any credential properties. It is used to
+restrict the set of credentials returned by the `search` function.
+
+See "Credentials" above for details on what these properties should be.
+
+Additionally, `options` must contain a function assigned to its `onComplete`
+property: this is called when the function completes and is passed the set of
+credentials retrieved.
+
+`options` may contain a function assigned to its `onError` property, which is
+called if the function encounters an error. `onError` is passed the error as an
+[nsIException](https://developer.mozilla.org/en/nsIException) object.
+
+@prop [username] {string}
+The username for the credential.
+
+@prop [password] {string}
+The password for the credential.
+
+@prop [url] {string}
+The URL associated with the credential.
+
+@prop [formSubmitURL] {string}
+The URL an HTML form credential is submitted to.
+
+@prop [realm] {string}
+For HTTP Authentication credentials, the realm for which the credential was
+requested. For add-on credentials, a name for the credential.
+
+@prop [usernameField] {string}
+The value of the `name` attribute for the user name input field in a form.
+
+@prop [passwordField] {string}
+The value of the `name` attribute for the password input field in a form.
+
+@prop onComplete {function}
+The callback function that is called once the function completes successfully.
+It is passed all the matching credentials as a list. This is the only
+mandatory option.
+
+@prop [onError] {function}
+The callback function that is called if the function failed. The
+callback is passed an `error` containing a reason of a failure: this is an
+[nsIException](https://developer.mozilla.org/en/nsIException) object.
+
+</api>
+
+<api name="store">
+@function
+
+This function is used to store a credential in the Password Manager.
+
+It takes an `options` object as an argument: this contains all the properties
+for the new credential.
+
+As different sorts of credentials contain different properties, the
+appropriate options differ depending on the sort of credential being stored.
+
+To store an add-on credential:
+
+ require("passwords").store({
+ realm: "User Registration",
+ username: "joe",
+ password: "SeCrEt123",
+ });
+
+To store an HTML form credential:
+
+ require("passwords").store({
+ url: "http://www.example.com",
+ formSubmitURL: "http://login.example.com",
+ username: "joe",
+ usernameField: "uname",
+ password: "SeCrEt123",
+ passwordField: "pword"
+ });
+
+To store an HTTP Authentication credential:
+
+ require("passwords").store({
+ url: "http://www.example.com",
+ realm: "ExampleCo Login",
+ username: "joe",
+ password: "SeCrEt123",
+ });
+
+See "Credentials" above for more details on how to set these properties.
+
+The options parameter may also include `onComplete` and `onError`
+callback functions, which are called when the function has completed
+successfully and when it encounters an error, respectively. These options
+are both optional.
+
+@param options {object}
+An object containing the properties of the credential to be stored, and
+optional `onComplete` and `onError` callback functions.
+
+@prop username {string}
+The username for the credential.
+
+@prop password {string}
+The password for the credential.
+
+@prop [url] {string}
+The URL to which the credential applies. Omitted for add-on
+credentials.
+
+@prop [formSubmitURL] {string}
+The URL a form-based credential was submitted to. Omitted for add-on
+credentials and HTTP Authentication credentials.
+
+@prop [realm] {string}
+For HTTP Authentication credentials, the realm for which the credential was
+requested. For add-on credentials, a name for the credential.
+
+@prop [usernameField] {string}
+The value of the `name` attribute for the username input in a form.
+Omitted for add-on credentials and HTTP Authentication credentials.
+
+@prop [passwordField] {string}
+The value of the `name` attribute for the password input in a form.
+Omitted for add-on credentials and HTTP Authentication credentials.
+
+@prop [onComplete] {function}
+The callback function that is called once the function completes successfully.
+
+@prop [onError] {function}
+The callback function that is called if the function failed. The
+callback is passed an `error` argument: this is an
+[nsIException](https://developer.mozilla.org/en/nsIException) object.
+
+</api>
+
+<api name="remove">
+@function
+
+Removes a stored credential. You supply it all the properties of the credential
+to remove, along with optional `onComplete` and `onError` callbacks.
+
+Because you must supply all the credential's properties, it may be convenient
+to call `search` first, and use its output as the input to `remove`. For
+example, to remove all of joe's stored credentials:
+
+ require("passwords").search({
+ username: "joe",
+ onComplete: function onComplete(credentials) {
+ credentials.forEach(require("passwords").remove);
+ })
+ });
+
+To change an existing credential just call `store` after `remove` succeeds:
+
+ require("passwords").remove({
+ realm: "User Registration",
+ username: "joe",
+ password: "SeCrEt123"
+ onComplete: function onComplete() {
+ require("passwords").store({
+ realm: "User Registration",
+ username: "joe",
+ password: "{{new password}}"
+ })
+ }
+ });
+
+@param options {object}
+
+An object containing all the properties of the credential to be removed,
+and optional `onComplete` and `onError` callback functions.
+
+@prop username {string}
+The username for the credential.
+
+@prop password {string}
+The password for the credential.
+
+@prop [url] {string}
+The URL to which the credential applies. Omitted for add-on
+credentials.
+
+@prop [formSubmitURL] {string}
+The URL a form-based credential was submitted to. Omitted for add-on
+credentials and HTTP Authentication credentials.
+
+@prop [realm] {string}
+For HTTP Authentication credentials, the realm for which the credential was
+requested. For add-on credentials, a name for the credential.
+
+@prop [usernameField] {string}
+The value of the `name` attribute for the username input in a form.
+Omitted for add-on credentials and HTTP Authentication credentials.
+
+@prop [passwordField] {string}
+The value of the `name` attribute for the password input in a form.
+Omitted for add-on credentials and HTTP Authentication credentials.
+
+@prop [onComplete] {function}
+The callback function that is called once the function has completed
+successfully.
+
+@prop [onError] {function}
+The callback function that is called if the function failed. The
+callback is passed an `error` argument: this is an
+[nsIException](https://developer.mozilla.org/en/nsIException) object.
+
+</api>
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/docs/private-browsing.md b/tools/addon-sdk-1.3/packages/addon-kit/docs/private-browsing.md
new file mode 100644
index 0000000..cad6166
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/docs/private-browsing.md
@@ -0,0 +1,46 @@
+<!-- contributed by Paul O'Shannessy [paul@oshannessy.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
+
+The `private-browsing` module allows you to access Firefox's private browsing
+mode, detecting if it is active and when its state changes.
+
+This module is available in all applications. However, only Firefox will ever
+transition into or out of private browsing mode. For all other applications,
+`pb.isActive` will always be `false`, and none of the events will be emitted.
+
+<api name="isActive">
+@property {boolean}
+ This read-only boolean is true if private browsing mode is turned on.
+</api>
+
+<api name="activate">
+@function
+ Turns on private browsing mode.
+</api>
+
+<api name="deactivate">
+@function
+ Turns off private browsing mode.
+</api>
+
+<api name="start">
+@event
+Emitted immediately after the browser enters private browsing mode.
+
+ var pb = require("private-browsing");
+ pb.on("start", function() {
+ // Do something when the browser starts private browsing mode.
+ });
+
+</api>
+
+<api name="stop">
+@event
+Emitted immediately after the browser exits private browsing mode.
+
+ var pb = require("private-browsing");
+ pb.on("stop", function() {
+ // Do something when the browser stops private browsing mode.
+ });
+</api> \ No newline at end of file
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/docs/request.md b/tools/addon-sdk-1.3/packages/addon-kit/docs/request.md
new file mode 100644
index 0000000..77bea43
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/docs/request.md
@@ -0,0 +1,192 @@
+The `request` module lets you make simple yet powerful network requests.
+
+<api name="Request">
+@class
+The `Request` object is used to make `GET` or `POST` network requests. It is
+constructed with a URL to which the request is sent. Optionally the user may
+specify a collection of headers and content to send alongside the request and
+a callback which will be executed once the request completes.
+
+Once a `Request` object has been created a `GET` request can be executed by
+calling its `get()` method, or a `POST` request by calling its `post()` method.
+
+When the server completes the request, the `Request` object emits a "complete"
+event. Registered event listeners are passed a `Response` object.
+
+Each `Request` object is designed to be used once. Once `GET` or `POST` are
+called, attempting to call either will throw an error.
+
+Since the request is not being made by any particular website, requests made
+here are not subject to the same-domain restriction that requests made in web
+pages are subject to.
+
+With the exception of `response`, all of a `Request` object's properties
+correspond with the options in the constructor. Each can be set by simply
+performing an assignment. However, keep in mind that the same validation rules
+that apply to `options` in the constructor will apply during assignment. Thus,
+each can throw if given an invalid value.
+
+The example below shows how to use Request to get the most recent public tweet.
+
+ var Request = require("request").Request;
+ var latestTweetRequest = Request({
+ url: "http://api.twitter.com/1/statuses/public_timeline.json",
+ onComplete: function (response) {
+ var tweet = response.json[0];
+ console.log("User: " + tweet.user.screen_name);
+ console.log("Tweet: " + tweet.text);
+ }
+ });
+
+ // Be a good consumer and check for rate limiting before doing more.
+ Request({
+ url: "http://api.twitter.com/1/account/rate_limit_status.json",
+ onComplete: function (response) {
+ if (response.json.remaining_hits) {
+ latestTweetRequest.get();
+ } else {
+ console.log("You have been rate limited!");
+ }
+ }
+ }).get();
+
+<api name="Request">
+@constructor
+This constructor creates a request object that can be used to make network
+requests. The constructor takes a single parameter `options` which is used to
+set several properties on the resulting `Request`.
+@param options {object}
+ @prop url {string}
+ This is the url to which the request will be made.
+
+ @prop [onComplete] {function}
+ This function will be called when the request has received a response (or in
+ terms of XHR, when `readyState == 4`). The function is passed a `Response`
+ object.
+
+ @prop [headers] {object}
+ An unordered collection of name/value pairs representing headers to send
+ with the request.
+
+ @prop [content] {string,object}
+ The content to send to the server. If `content` is a string, it should be
+ URL-encoded (use `encodeURIComponent`). If `content` is an object, it
+ should be a collection of name/value pairs. Nested objects & arrays should
+ encode safely.
+
+ For `GET` requests, the query string (`content`) will be appended to the
+ URL. For `POST` requests, the query string will be sent as the body of the
+ request.
+
+ @prop [contentType] {string}
+ The type of content to send to the server. This explicitly sets the
+ `Content-Type` header. The default value is `application/x-www-form-urlencoded`.
+
+ @prop [overrideMimeType] {string}
+ Use this string to override the MIME type returned by the server in the
+ response's Content-Type header. You can use this to treat the content as a
+ different MIME type, or to force text to be interpreted using a specific
+ character.
+
+ For example, if you're retrieving text content which was encoded as
+ ISO-8859-1 (Latin 1), it will be given a content type of "utf-8" and
+ certain characters will not display correctly. To force the response to
+ be interpreted as Latin-1, use `overrideMimeType`:
+
+ var Request = require("request").Request;
+ var quijote = Request({
+ url: "http://www.latin1files.org/quijote.txt",
+ overrideMimeType: "text/plain; charset=latin1",
+ onComplete: function (response) {
+ console.log(response.text);
+ }
+ });
+
+ quijote.get();
+
+</api>
+
+<api name="url">
+@property {string}
+</api>
+
+<api name="headers">
+@property {object}
+</api>
+
+<api name="content">
+@property {string,object}
+</api>
+
+<api name="contentType">
+@property {string}
+</api>
+
+<api name="response">
+@property {Response}
+</api>
+
+<api name="get">
+@method
+Make a `GET` request.
+@returns {Request}
+</api>
+
+<api name="post">
+@method
+Make a `POST` request.
+@returns {Request}
+</api>
+
+<api name="complete">
+@event
+The `Request` object emits this event when the request has completed and a
+response has been received.
+
+@argument {Response}
+Listener functions are passed the response to the request as a `Response` object.
+</api>
+
+</api>
+
+
+<api name="Response">
+@class
+The Response object contains the response to a network request issued using a
+`Request` object. It is returned by the `get()` or `post()` method of a
+`Request` object.
+
+All members of a `Response` object are read-only.
+<api name="text">
+@property {string}
+The content of the response as plain text.
+</api>
+
+<api name="json">
+@property {object}
+The content of the response as a JavaScript object. The value will be `null`
+if the document cannot be processed by `JSON.parse`.
+</api>
+
+<api name="status">
+@property {string}
+The HTTP response status code (e.g. *200*).
+</api>
+
+<api name="statusText">
+@property {string}
+The HTTP response status line (e.g. *OK*).
+</api>
+
+<api name="headers">
+@property {object}
+The HTTP response headers represented as key/value pairs.
+
+To print all the headers you can do something like this:
+
+ for (var headerName in response.headers) {
+ console.log(headerName + " : " + response.headers[headerName]);
+ }
+
+</api>
+</api>
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/docs/selection.md b/tools/addon-sdk-1.3/packages/addon-kit/docs/selection.md
new file mode 100644
index 0000000..62a3d64
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/docs/selection.md
@@ -0,0 +1,86 @@
+<!-- contributed by Eric H. Jung [eric.jung@yahoo.com] -->
+<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
+
+The `selection` module provides a means to get and set text and HTML selections
+in the current Firefox page. It can also observe new selections.
+
+Registering for Selection Notifications
+---------------------------------------
+
+To be notified when the user makes a selection, register a listener for the
+"select" event. Each listener will be called after a selection is made.
+
+ function myListener() {
+ console.log("A selection has been made.");
+ }
+ var selection = require("selection");
+ selection.on('select', myListener);
+
+ // You can remove listeners too.
+ selection.removeListener('select', myListener);
+
+Iterating Over Discontiguous Selections
+---------------------------------------
+
+Discontiguous selections can be accessed by iterating over the `selection`
+module itself. Each iteration yields a `Selection` object from which `text`,
+`html`, and `isContiguous` properties can be accessed.
+
+
+Examples
+--------
+
+Log the current contiguous selection as text:
+
+ var selection = require("selection");
+ if (selection.text)
+ console.log(selection.text);
+
+Log the current discontiguous selections as HTML:
+
+ var selection = require("selection");
+ if (!selection.isContiguous) {
+ for (var subselection in selection) {
+ console.log(subselection.html);
+ }
+ }
+
+Surround HTML selections with delimiters:
+
+ var selection = require("selection");
+ selection.on('select', function () {
+ selection.html = "\\\" + selection.html + "///";
+ });
+
+<api name="text">
+@property {string}
+ Gets or sets the current selection as plain text. Setting the selection
+ removes all current selections, inserts the specified text at the location of
+ the first selection, and selects the new text. Getting the selection when
+ there is no current selection returns `null`. Setting the selection when there
+ is no current selection throws an exception. Getting the selection when
+ `isContiguous` is `true` returns the text of the first selection.
+</api>
+
+<api name="html">
+@property {string}
+ Gets or sets the current selection as HTML. Setting the selection removes all
+ current selections, inserts the specified text at the location of the first
+ selection, and selects the new text. Getting the selection when there is no
+ current selection returns `null`. Setting the selection when there is no
+ current selection throws an exception. Getting the selection when
+ `isContiguous` is `true` returns the text of the first selection.
+</api>
+
+<api name="isContiguous">
+@property {boolean}
+ `true` if the current selection is a single, contiguous selection, and `false`
+ if there are two or more discrete selections, each of which may or may not be
+ spatially adjacent. (Discontiguous selections can be created by the user with
+ Ctrl+click-and-drag.)
+</api>
+
+<api name="select">
+@event
+ This event is emitted whenever the user makes a new selection in a page.
+</api>
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/docs/self.md b/tools/addon-sdk-1.3/packages/addon-kit/docs/self.md
new file mode 100644
index 0000000..e76ad50
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/docs/self.md
@@ -0,0 +1,73 @@
+The `self` module provides access to data that is bundled with the add-on
+as a whole. It also provides access to the
+[Program ID](dev-guide/addon-development/program-id.html), a value which is
+unique for each add-on.
+
+Note that the `self` module is completely different from the global `self`
+object accessible to content scripts, which is used by a content script to
+[communicate with the add-on code](dev-guide/addon-development/content-scripts/using-port.html).
+
+<api name="id">
+@property {string}
+This property is a printable string that is unique for each add-on. It comes
+from the `id` property set in the `package.json` file in the main package
+(i.e. the package in which you run `cfx xpi`). While not generally of use to
+add-on code directly, it can be used by internal API code to index local
+storage and other resources that are associated with a particular add-on.
+Eventually, this ID will be unspoofable (see
+[JEP 118](https://wiki.mozilla.org/Labs/Jetpack/Reboot/JEP/118) for details).
+</api>
+
+<api name="name">
+@property {string}
+This property contains the add-on's short name. It comes from the `name`
+property in the main package's `package.json` file.
+</api>
+
+<api name="version">
+@property {string}
+This property contains the add-on's version string. It comes from the
+`version` property set in the `package.json` file in the main package.
+</api>
+
+<api name="data">
+@property {object}
+The `data` object is used to access data that was bundled with the add-on.
+This data lives in the main package's `data/` directory, immediately below
+the `package.json` file. All files in this directory will be copied into the
+XPI and made available through the `data` object.
+
+The [Package Specification](dev-guide/addon-development/package-spec.html)
+section explains the `package.json` file.
+
+<api name="data.load">
+@method
+The `data.load(NAME)` method returns the contents of an embedded data file,
+as a string. It is most useful for data that will be modified or parsed in
+some way, such as JSON, XML, plain text, or perhaps an HTML template. For
+data that can be displayed directly in a content frame, use `data.url(NAME)`.
+@param name {string} The filename to be read, relative to the
+ package's `data` directory. Each package that uses the `self` module
+ will see its own `data` directory.
+@returns {string}
+</api>
+
+<api name="data.url">
+@method
+The `data.url(NAME)` method returns a URL instance that points at an embedded
+data file. It is most useful for data that can be displayed directly in a
+content frame. The URL instance can be passed to a content frame constructor,
+such as the Panel:
+
+ var self = require("self");
+ var myPanel = require("panel").Panel({
+ contentURL: self.data.url("my-panel-content.html")
+ });
+ myPanel.show();
+
+@param name {string} The filename to be read, relative to the
+ package's `data` directory. Each package that uses the `self` module
+ will see its own `data` directory.
+@returns {URL}
+</api>
+</api>
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/docs/simple-storage.md b/tools/addon-sdk-1.3/packages/addon-kit/docs/simple-storage.md
new file mode 100644
index 0000000..e34e333
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/docs/simple-storage.md
@@ -0,0 +1,125 @@
+The `simple-storage` module lets you easily and persistently store data across
+application restarts. If you're familiar with [DOM storage][] on the Web, it's
+kind of like that, but for add-ons.
+
+[DOM storage]: https://developer.mozilla.org/en/DOM/Storage
+
+
+Introduction
+------------
+
+The simple storage module exports an object called `storage` that is persistent
+and private to your add-on. It's a normal JavaScript object, and you can treat
+it as you would any other.
+
+To store a value, just assign it to a property on `storage`:
+
+ var ss = require("simple-storage");
+ ss.storage.myArray = [1, 1, 2, 3, 5, 8, 13];
+ ss.storage.myBoolean = true;
+ ss.storage.myNull = null;
+ ss.storage.myNumber = 3.1337;
+ ss.storage.myObject = { a: "foo", b: { c: true }, d: null };
+ ss.storage.myString = "O frabjous day!";
+
+You can store array, boolean, number, object, null, and string values. If you'd
+like to store other types of values, you'll first have to convert them to
+strings or another one of these types.
+
+Be careful to set properties on the `storage` object and not the module itself:
+
+ // This is no good!
+ var ss = require("simple-storage");
+ ss.foo = "I will not be saved! :(";
+
+
+Quotas
+------
+
+The simple storage available to your add-on is limited. Currently this limit is
+about five megabytes (5,242,880 bytes). You can choose to be notified when you
+go over quota, and you should respond by reducing the amount of data in storage.
+If the user quits the application while you are over quota, all data stored
+since the last time you were under quota will not be persisted. You should not
+let that happen.
+
+To listen for quota notifications, register a listener for the `"OverQuota"`
+event. It will be called when your storage goes over quota.
+
+ function myOnOverQuotaListener() {
+ console.log("Uh oh.");
+ }
+ ss.on("OverQuota", myOnOverQuotaListener);
+
+Listeners can also be removed:
+
+ ss.removeListener("OverQuota", myOnOverQuotaListener);
+
+To find out how much of your quota you're using, check the module's `quotaUsage`
+property. It indicates the percentage of quota your storage occupies. If
+you're within your quota, it's a number from 0 to 1, inclusive, and if you're
+over, it's a number greater than 1.
+
+Therefore, when you're notified that you're over quota, respond by removing
+storage until your `quotaUsage` is less than or equal to 1. Which particular
+data you remove is up to you. For example:
+
+ ss.storage.myList = [ /* some long array */ ];
+ ss.on("OverQuota", function () {
+ while (ss.quotaUsage > 1)
+ ss.storage.myList.pop();
+ });
+
+
+Private Browsing
+----------------
+
+*This section applies only to add-ons running on Firefox.*
+
+If your storage is related to your users' Web history, personal information, or
+other sensitive data, your add-on should respect [private browsing mode][SUMO].
+While private browsing mode is active, you should not store any sensitive data.
+
+Because any kind of data can be placed into simple storage, support for private
+browsing is not built into the module. Instead, use the
+[`private-browsing`](packages/addon-kit/docs/private-browsing.html) module to
+check private browsing status and respond accordingly.
+
+For example, the URLs your users visit should not be stored during private
+browsing. If your add-on records the URL of the selected tab, here's how you
+might handle that:
+
+ ss.storage.history = [];
+ var privateBrowsing = require("private-browsing");
+ if (!privateBrowsing.active) {
+ var url = getSelectedTabURL();
+ ss.storage.history.push(url);
+ }
+
+For more information on supporting private browsing, see its [Mozilla Developer
+Network documentation][MDN]. While that page does not apply specifically to
+SDK-based add-ons (and its code samples don't apply at all), you should follow
+its guidance on best practices and policies.
+
+[SUMO]: http://support.mozilla.com/en-US/kb/Private+Browsing
+[MDN]: https://developer.mozilla.org/En/Supporting_private_browsing_mode
+
+
+<api name="storage">
+@property {object}
+ A persistent object private to your add-on. Properties with array, boolean,
+ number, object, null, and string values will be persisted.
+</api>
+
+<api name="quotaUsage">
+@property {number}
+ A number in the range [0, Infinity) that indicates the percentage of quota
+ occupied by storage. A value in the range [0, 1] indicates that the storage
+ is within quota. A value greater than 1 indicates that the storage exceeds
+ quota.
+</api>
+
+<api name="OverQuota">
+@event
+The module emits this event when your add-on's storage goes over its quota.
+</api>
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/docs/tabs.md b/tools/addon-sdk-1.3/packages/addon-kit/docs/tabs.md
new file mode 100644
index 0000000..a6a858f
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/docs/tabs.md
@@ -0,0 +1,381 @@
+<!-- contributed by Dietrich Ayala [dietrich@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+
+The `tabs` module provides easy access to tabs and tab-related events.
+
+The module itself can be used like a basic list of all opened
+tabs across all windows. In particular, you can enumerate it:
+
+ var tabs = require('tabs');
+ for each (var tab in tabs)
+ console.log(tab.title);
+
+You can also access individual tabs by index:
+
+ var tabs = require('tabs');
+
+ tabs.on('ready', function () {
+ console.log('first: ' + tabs[0].title);
+ console.log('last: ' + tabs[tabs.length-1].title);
+ });
+
+You can open a new tab, specifying various properties including location:
+
+ var tabs = require("tabs");
+ tabs.open("http://www.example.com");
+
+You can register event listeners to be notified when tabs open, close, finish
+loading DOM content, or are made active or inactive:
+
+ var tabs = require("tabs");
+
+ // Listen for tab openings.
+ tabs.on('open', function onOpen(tab) {
+ myOpenTabs.push(tab);
+ });
+
+ // Listen for tab content loads.
+ tabs.on('ready', function(tab) {
+ console.log('tab is loaded', tab.title, tab.url)
+ });
+
+You can get and set various properties of tabs (but note that properties
+ relating to the tab's content, such as the URL, will not contain valid
+values until after the tab's `ready` event fires). By setting the `url`
+property you can load a new page in the tab:
+
+ var tabs = require("tabs");
+ tabs.on('activate', function(tab) {
+ tab.url = "http://www.example.com";
+ });
+
+You can attach a [content script](dev-guide/addon-development/web-content.html)
+to the page hosted in a tab, and use that to access and manipulate the page's
+content:
+
+ var tabs = require("tabs");
+
+ tabs.on('activate', function(tab) {
+ tab.attach({
+ contentScript: 'self.postMessage(document.body.innerHTML);',
+ onMessage: function (message) {
+ console.log(message);
+ }
+ });
+ });
+
+<api name="activeTab">
+@property {Tab}
+
+The currently active tab in the active window. This property is read-only. To
+activate a `Tab` object, call its `activate` method.
+
+**Example**
+
+ // Get the active tab's title.
+ var tabs = require("tabs");
+ console.log("title of active tab is " + tabs.activeTab.title);
+</api>
+
+<api name="length">
+@property {number}
+The number of open tabs across all windows.
+</api>
+
+<api name="open">
+@function
+Opens a new tab. The new tab will open in the active window or in a new window,
+depending on the `inNewWindow` option.
+
+**Example**
+
+ var tabs = require("tabs");
+
+ // Open a new tab on active window and make tab active.
+ tabs.open("http://www.mysite.com");
+
+ // Open a new tab in a new window and make it active.
+ tabs.open({
+ url: "http://www.mysite.com",
+ inNewWindow: true
+ });
+
+ // Open a new tab on active window in the background.
+ tabs.open({
+ url: "http://www.mysite.com",
+ inBackground: true
+ });
+
+ // Open a new tab as an app tab and do something once it's open.
+ tabs.open({
+ url: "http://www.mysite.com",
+ isPinned: true,
+ onOpen: function onOpen(tab) {
+ // do stuff like listen for content
+ // loading.
+ }
+ });
+
+@param options {object}
+An object containing configurable options for how and where the tab will be
+opened, as well as a listeners for the tab events.
+
+If the only option being used is `url`, then a bare string URL can be passed to
+`open` instead of adding at a property of the `options` object.
+
+@prop [url] {string}
+String URL to be opened in the new tab.
+This is a required property.
+
+@prop [inNewWindow] {boolean}
+If present and true, a new browser window will be opened and the URL will be
+opened in the first tab in that window. This is an optional property.
+
+@prop [inBackground] {boolean}
+If present and true, the new tab will be opened to the right of the active tab
+and will not be active. This is an optional property.
+
+@prop [isPinned] {boolean}
+If present and true, then the new tab will be pinned as an
+[app tab](http://support.mozilla.com/en-US/kb/what-are-app-tabs).
+
+@prop [onOpen] {function}
+A callback function that will be registered for 'open' event.
+This is an optional property.
+@prop [onClose] {function}
+A callback function that will be registered for 'close' event.
+This is an optional property.
+@prop [onReady] {function}
+A callback function that will be registered for 'ready' event.
+This is an optional property.
+@prop [onActivate] {function}
+A callback function that will be registered for 'activate' event.
+This is an optional property.
+@prop [onDeactivate] {function}
+A callback function that will be registered for 'deactivate' event.
+This is an optional property.
+</api>
+
+<api name="Tab">
+@class
+A `Tab` instance represents a single open tab. It contains various tab
+properties, several methods for manipulation, as well as per-tab event
+registration.
+
+Tabs emit all the events described in the Events section. Listeners are
+passed the `Tab` object that triggered the event.
+
+<api name="title">
+@property {string}
+The title of the page currently loaded in the tab.
+This property can be set to change the tab title.
+</api>
+
+<api name="url">
+@property {String}
+The URL of the page currently loaded in the tab.
+This property can be set to load a different URL in the tab.
+</api>
+
+<api name="favicon">
+@property {string}
+The URL of the favicon for the page currently loaded in the tab.
+This property is read-only.
+</api>
+
+<api name="index">
+@property {integer}
+The index of the tab relative to other tabs in the application window.
+This property can be set to change its relative position.
+</api>
+
+<api name="isPinned">
+@property {boolean}
+Whether or not tab is pinned as an [app tab][].
+This property is read-only.
+[app tab]:http://support.mozilla.com/en-US/kb/what-are-app-tabs
+</api>
+
+<api name="getThumbnail">
+@property {method}
+Returns thumbnail data URI of the page currently loaded in this tab.
+</api>
+
+<api name="pin">
+@method
+Pins this tab as an [app tab][].
+[app tab]:http://support.mozilla.com/en-US/kb/what-are-app-tabs
+</api>
+
+<api name="unpin">
+@method
+Unpins this tab.
+</api>
+
+<api name="close">
+@method
+Closes this tab.
+
+@param [callback] {function}
+A function to be called when the tab finishes its closing process.
+This is an optional argument.
+</api>
+
+<api name="reload">
+@method
+Reloads this tab.
+</api>
+
+<api name="activate">
+@method
+Makes this tab active, which will bring this tab to the foreground.
+</api>
+
+<api name="attach">
+@method
+ Create a page mod and attach it to the document in the tab.
+
+**Example**
+
+ var tabs = require("tabs");
+
+ tabs.on('ready', function(tab) {
+ tab.attach({
+ contentScript:
+ 'document.body.style.border = "5px solid red";'
+ });
+ });
+
+@param options {object}
+ Options for the page mod, with the following keys:
+
+@prop [contentScriptFile] {string,array}
+ The local file URLs of content scripts to load. Content scripts specified
+ by this option are loaded *before* those specified by the `contentScript`
+ option. Optional.
+@prop [contentScript] {string,array}
+ The texts of content scripts to load. Content scripts specified by this
+ option are loaded *after* those specified by the `contentScriptFile` option.
+ Optional.
+@prop [onMessage] {function}
+ A function called when the page mod receives a message from content scripts.
+ Listeners are passed a single argument, the message posted from the
+ content script.
+
+@returns {Worker}
+ See [Content Scripts guide](dev-guide/addon-development/web-content.html)
+ to learn how to use the `Worker` object to communicate with the content script.
+
+</api>
+
+<api name="close">
+@event
+
+This event is emitted when the tab is closed. It's also emitted when the
+tab's window is closed.
+
+@argument {Tab}
+Listeners are passed the tab object.
+</api>
+
+<api name="ready">
+@event
+
+This event is emitted when the DOM for the tab's content is ready. It is
+equivalent to the `DOMContentLoaded` event for the given content page.
+
+A single tab will emit this event every time the DOM is loaded: so it will be
+emitted again if the tab's location changes or the content is reloaded.
+
+After this event has been emitted, all properties relating to the tab's
+content can be used.
+
+@argument {Tab}
+Listeners are passed the tab object.
+</api>
+
+<api name="activate">
+@event
+
+This event is emitted when the tab is made active.
+
+@argument {Tab}
+Listeners are passed the tab object.
+</api>
+
+<api name="deactivate">
+@event
+
+This event is emitted when the tab is made inactive.
+
+@argument {Tab}
+Listeners are passed the tab object.
+</api>
+
+</api>
+
+<api name="open">
+@event
+
+This event is emitted when a new tab is opened. This does not mean that
+the content has loaded, only that the browser tab itself is fully visible
+to the user.
+
+Properties relating to the tab's content (for example: `title`, `favicon`,
+and `url`) will not be correct at this point. If you need to access these
+properties, listen for the `ready` event:
+
+ var tabs = require("tabs");
+ tabs.on('open', function(tab){
+ tab.on('ready', function(tab){
+ console.log(tab.url);
+ });
+ });
+
+@argument {Tab}
+Listeners are passed the tab object that just opened.
+</api>
+
+<api name="close">
+@event
+
+This event is emitted when a tab is closed. When a window is closed
+this event will be emitted for each of the open tabs in that window.
+
+@argument {Tab}
+Listeners are passed the tab object that has closed.
+</api>
+
+<api name="ready">
+@event
+
+This event is emitted when the DOM for a tab's content is ready.
+It is equivalent to the `DOMContentLoaded` event for the given content page.
+
+A single tab will emit this event every time the DOM is loaded: so it will be
+emitted again if the tab's location changes or the content is reloaded.
+
+After this event has been emitted, all properties relating to the tab's
+content can be used.
+
+@argument {Tab}
+Listeners are passed the tab object that has loaded.
+</api>
+
+<api name="activate">
+@event
+
+This event is emitted when an inactive tab is made active.
+
+@argument {Tab}
+Listeners are passed the tab object that has become active.
+</api>
+
+<api name="deactivate">
+@event
+
+This event is emitted when the active tab is made inactive.
+
+@argument {Tab}
+Listeners are passed the tab object that has become inactive.
+</api>
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/docs/timers.md b/tools/addon-sdk-1.3/packages/addon-kit/docs/timers.md
new file mode 100644
index 0000000..bc36750
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/docs/timers.md
@@ -0,0 +1,48 @@
+<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
+<!-- contributed by Atul Varma [atul@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+<!-- contributed by Irakli Gozalishvil [gozala@mozilla.com] -->
+
+The `timers` module provides access to web-like timing functionality.
+
+<api name="setTimeout">
+@function
+ Schedules `callback` to be called in `ms` milliseconds. Any additional
+ arguments are passed straight through to the callback.
+@returns {integer}
+ An ID that can later be used to undo this scheduling, if `callback` hasn't yet
+ been called.
+@param callback {function}
+ Function to be called.
+@param ms {integer}
+ Interval in milliseconds after which the function will be called.
+</api>
+
+<api name="clearTimeout">
+@function
+ Given an ID returned from `setTimeout()`, prevents the callback with the ID
+ from being called (if it hasn't yet been called).
+@param ID {integer}
+ An ID returned from `setTimeout()`.
+</api>
+
+<api name="setInterval">
+@function
+ Schedules `callback` to be called repeatedly every `ms` milliseconds. Any
+ additional arguments are passed straight through to the callback.
+@returns {integer}
+ An ID that can later be used to unschedule the callback.
+@param callback {function}
+ Function to be called.
+@param ms {integer}
+ Interval in milliseconds at which the function will be called.
+</api>
+
+<api name="clearInterval">
+@function
+ Given an ID returned from `setInterval()`, prevents the callback with the ID
+ from being called again.
+@param ID {integer}
+ An ID returned from `setInterval()`.
+</api>
+
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/docs/widget.md b/tools/addon-sdk-1.3/packages/addon-kit/docs/widget.md
new file mode 100644
index 0000000..852ce55
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/docs/widget.md
@@ -0,0 +1,693 @@
+<!-- contributed by Dietrich Ayala [dietrich@mozilla.com] -->
+<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+
+The `widget` module provides your add-on with a simple user interface that is
+consistent with other add-ons and blends in well with Firefox.
+
+## Introduction ##
+
+"Widgets" are small pieces of content that live in the Firefox 4
+[add-on bar](https://developer.mozilla.org/en/The_add-on_bar).
+They can be simple icons or complex web pages. You can attach
+[panels](packages/addon-kit/docs/panel.html) to them that open when they're
+clicked, or you can define a custom click handler to perform some other action,
+like opening a web page in a tab.
+
+There are a few advantages to using widgets over an ad hoc user interface.
+First, your users will be accustomed to interacting with add-ons via widgets and
+the add-on bar. Second, it allows Firefox to treat your interface as a
+first-class citizen. For example, in the future Firefox may allow the user to
+drag widgets from the add-on bar to other toolbars. By exposing your interface
+as a widget, your add-on would automatically inherit such functionality.
+
+## Creation and Content ##
+
+Widgets can contain images or arbitrary web content. You can include this
+content inline as a string by using the `content` property, or point to content
+using a URL with the `contentURL` property.
+
+For example, this widget contains an image, so it looks like a simple icon:
+
+ require("widget").Widget({
+ id: "mozilla-icon",
+ label: "My Mozilla Widget",
+ contentURL: "http://www.mozilla.org/favicon.ico"
+ });
+
+Upon creation, the widget is automatically added to the add-on bar.
+You can set the width of a widget, but the height is fixed so as to fit in the
+add-on bar. If the content is an image, it is automatically scaled to be 16x16
+pixels.
+
+This widget contains an entire web page:
+
+ require("widget").Widget({
+ id: "hello-display",
+ label: "My Hello Widget",
+ content: "Hello!",
+ width: 50
+ });
+
+Widgets are quite small by default, so this example used the `width` property to
+grow it in order to show all the text.
+
+As with many SDK APIs, communication with the content inside your widgets is
+handled by [content scripts](dev-guide/addon-development/web-content.html).
+So, for example, to be notified when your widget's content has loaded, you can
+make a small script that calls back to the widget when it finishes loading.
+
+## Attaching Panels to Widgets ##
+
+You can supply a [panel](packages/addon-kit/docs/panel.html) to the widget's
+constructor: if you do this, the panel is automatically displayed when the
+user clicks the widget.
+
+ data = require("self").data
+
+ var clockPanel = require("panel").Panel({
+ width:215,
+ height:160,
+ contentURL: data.url("clock.html")
+ });
+
+ require("widget").Widget({
+ id: "open-clock-btn",
+ label: "Clock",
+ contentURL: data.url("History.png"),
+ panel: clockPanel
+ });
+
+<!-- The icon the widget displays, shown in the screenshot, is taken from the
+Nuvola icon set, http://www.icon-king.com/projects/nuvola/ which is made
+available under the LGPL 2.1:
+http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html -->
+
+<img class="image-center" src="static-files/media/screenshots/widget-panel-clock.png"
+alt="Panel attached to a widget">
+<br>
+
+Note that this is, at the moment, the only way you can attach a panel to a widget.
+
+You must supply the panel in the widget's constructor for it to work. If you
+assign the panel to the widget after construction, the panel can still be shown
+but will not be anchored to the widget:
+
+ data = require("self").data
+
+ var clockPanel = require("panel").Panel({
+ width:215,
+ height:160,
+ contentURL: data.url("clock.html")
+ });
+
+ widget = require("widget").Widget({
+ id: "open-clock-btn",
+ label: "Clock",
+ contentURL: data.url("History.png")
+ });
+
+ widget.panel = clockPanel;
+
+ // Will not be anchored
+ widget.panel.show();
+
+Also, if you try to call `panel.show()` inside your widget's `click` event
+listener, the panel will not be anchored:
+
+ data = require("self").data
+
+ var clockPanel = require("panel").Panel({
+ width:215,
+ height:160,
+ contentURL: data.url("clock.html")
+ });
+
+ require("widget").Widget({
+ id: "open-clock-btn",
+ label: "Clock",
+ contentURL: data.url("History.png"),
+ panel: clockPanel,
+ onClick: function() {
+ // Will not be anchored
+ this.panel.show();
+ }
+ });
+
+See [bug 638142](https://bugzilla.mozilla.org/show_bug.cgi?id=638142).
+
+## Examples ##
+
+For conciseness, these examples create their content scripts as strings and use
+the `contentScript` property. In your own add-ons, you will probably want to
+create your content scripts in separate files and pass their URLs using the
+`contentScriptFile` property. See
+[Working with Content Scripts](dev-guide/addon-development/web-content.html) for more
+information.
+
+ var widgets = require("widget");
+
+ // A basic click-able image widget.
+ widgets.Widget({
+ id: "google-link",
+ label: "Widget with an image and a click handler",
+ contentURL: "http://www.google.com/favicon.ico",
+ onClick: function() {
+ require("tabs").activeTab.url = "http://www.google.com/";
+ }
+ });
+<br>
+
+ // A widget that changes display on mouseover.
+ widgets.Widget({
+ id: "mouseover-effect",
+ label: "Widget with changing image on mouseover",
+ contentURL: "http://www.yahoo.com/favicon.ico",
+ onMouseover: function() {
+ this.contentURL = "http://www.bing.com/favicon.ico";
+ },
+ onMouseout: function() {
+ this.contentURL = "http://www.yahoo.com/favicon.ico";
+ }
+ });
+<br>
+
+ // A widget that updates content on a timer.
+ widgets.Widget({
+ id: "auto-update-widget",
+ label: "Widget that updates content on a timer",
+ content: "0",
+ contentScript: 'setTimeout(function() {' +
+ ' document.body.innerHTML++;' +
+ '}, 2000)',
+ contentScriptWhen: "ready"
+ });
+<br>
+
+ // A widget that loads a random Flickr photo every 5 minutes.
+ widgets.Widget({
+ id: "random-flickr",
+ label: "Random Flickr Photo Widget",
+ contentURL: "http://www.flickr.com/explore/",
+ contentScriptWhen: "ready",
+ contentScript: 'postMessage(document.querySelector(".pc_img").src);' +
+ 'setTimeout(function() {' +
+ ' document.location = "http://www.flickr.com/explore/";' +
+ '}, 5 * 60 * 1000);',
+ onMessage: function(imgSrc) {
+ this.contentURL = imgSrc;
+ },
+ onClick: function() {
+ require("tabs").activeTab.url = this.contentURL;
+ }
+ });
+<br>
+
+ // A widget created with a specified width, that grows.
+ let myWidget = widgets.Widget({
+ id: "widget-effect",
+ label: "Wide widget that grows wider on a timer",
+ content: "I'm getting longer.",
+ width: 50,
+ });
+ require("timers").setInterval(function() {
+ myWidget.width += 10;
+ }, 1000);
+<br>
+
+ // A widget communicating bi-directionally with a content script.
+ let widget = widgets.Widget({
+ id: "message-test",
+ label: "Bi-directional communication!",
+ content: "<foo>bar</foo>",
+ contentScriptWhen: "ready",
+ contentScript: 'on("message", function(message) {' +
+ ' alert("Got message: " + message);' +
+ '});' +
+ 'postMessage("ready");',
+ onMessage: function(message) {
+ if (message == "ready")
+ widget.postMessage("me too");
+ }
+ });
+
+<api-name="Widget">
+@class
+Represents a widget object.
+
+<api name="Widget">
+@constructor {options}
+ Creates a new widget. The widget is immediately added to the add-on bar.
+
+@param options {object}
+ An object with the following keys:
+
+ @prop label {string}
+ A required string description of the widget used for accessibility,
+ title bars, and error reporting.
+
+ @prop id {string}
+ Mandatory string used to identify your widget in order to save its
+ location when the user moves it in the browser.
+ This string has to be unique and must not be changed over time.
+
+ @prop [content] {string}
+ An optional string value containing the displayed content of the widget.
+ It may contain HTML. Widgets must have either the `content` property or the
+ `contentURL` property set.
+
+ If the content is an image, it is automatically scaled to be 16x16 pixels.
+
+ @prop [contentURL] {string}
+ An optional string URL to content to load into the widget. This can be
+ [local content](dev-guide/addon-development/web-content.html) or remote
+ content, an image or web content. Widgets must have either the `content`
+ property or the `contentURL` property set.
+
+ If the content is an image, it is automatically scaled to be 16x16 pixels.
+
+ @prop [panel] {Panel}
+ An optional [panel](packages/addon-kit/docs/panel.html) to open when the
+ user clicks on the widget. Note: If you also register a "click" listener,
+ it will be called instead of the panel being opened. However, you can show
+ the panel from the listener by calling `this.panel.show()`.
+
+ @prop [width] {integer}
+ Optional width in pixels of the widget. If not given, a default width is
+ used.
+
+ @prop [onClick] {function}
+ Include this to listen to the widget's `click` event.
+
+ @prop [onMessage] {function}
+ Include this to listen to the widget's `message` event.
+
+ @prop [onMouseover] {function}
+ Include this to listen to the widget's `mouseover` event.
+
+ @prop [onMouseout] {function}
+ Include this to listen to the widget's `mouseout` event.
+
+ @prop [onAttach] {function}
+ Include this to listen to the widget's `attach` event.
+
+ @prop [tooltip] {string}
+ Optional text to show when the user's mouse hovers over the widget. If not
+ given, the `label` is used.
+
+ @prop [allow] {object}
+ An optional object describing permissions for the content. It should
+ contain a single key named `script` whose value is a boolean that indicates
+ whether or not to execute script in the content. `script` defaults to true.
+
+ @prop [contentScriptFile] {string,array}
+ A local file URL or an array of local file URLs of content scripts to load.
+ Content scripts specified by this property are loaded *before* those
+ specified by the `contentScript` property.
+
+ @prop [contentScript] {string,array}
+ A string or an array of strings containing the texts of content scripts to
+ load. Content scripts specified by this property are loaded *after* those
+ specified by the `contentScriptFile` property.
+
+ @prop [contentScriptWhen="end"] {string}
+ When to load the content scripts. This may take one of the following
+ values:
+
+ * "start": load content scripts immediately after the document
+ element for the widget is inserted into the DOM, but before the DOM content
+ itself has been loaded
+ * "ready": load content scripts once DOM content has been loaded,
+ corresponding to the
+ [DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+ event
+ * "end": load content scripts once all the content (DOM, JS, CSS,
+ images) for the widget has been loaded, at the time the
+ [window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+ fires
+
+ This property is optional and defaults to "end".
+
+</api>
+
+<api name="destroy">
+@method
+ Removes the widget from the add-on bar.
+</api>
+
+<api name="postMessage">
+@method
+ Sends a message to the widget's content scripts.
+@param data {value}
+ The message to send.
+ The message can be any
+<a href = "dev-guide/addon-development/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
+</api>
+
+<api name="on">
+@method
+ Registers an event listener with the widget.
+@param type {string}
+ The type of event to listen for.
+@param listener {function}
+ The listener function that handles the event.
+</api>
+
+<api name="removeListener">
+@method
+ Unregisters an event listener from the widget.
+@param type {string}
+ The type of event for which `listener` was registered.
+@param listener {function}
+ The listener function that was registered.
+</api>
+
+<api name="getView">
+@method
+ Retrieve a `WidgetView` instance of this widget relative to a browser window.
+@param window {BrowserWindow}
+ The [BrowserWindow](packages/addon-kit/docs/windows.html) instance to match.
+@returns {WidgetView}
+ A `WidgetView` instance associated with the browser window. Any changes
+ subsequently applied to this object will only be applied to the widget
+ attached to that window.
+</api>
+
+<api name="label">
+@property {string}
+ The widget's label. Read-only.
+</api>
+
+<api name="content">
+@property {string}
+ A string containing the widget's content. It can contain HTML. Setting it
+ updates the widget's appearance immediately. However, if the widget was
+ created using `contentURL`, then this property is meaningless, and setting it
+ has no effect.
+</api>
+
+<api name="contentURL">
+@property {string}
+ The URL of content to load into the widget. This can be
+ [local content](dev-guide/addon-development/web-content.html) or remote
+ content, an image or web content. Setting it updates the widget's appearance
+ immediately. However, if the widget was created using `content`, then this
+ property is meaningless, and setting it has no effect.
+</api>
+
+<api name="panel">
+@property {Panel}
+ A [panel](packages/addon-kit/docs/panel.html) to open when the user clicks on
+ the widget.
+</api>
+
+<api name="width">
+@property {number}
+ The widget's width in pixels. Setting it updates the widget's appearance
+ immediately.
+</api>
+
+<api name="tooltip">
+@property {string}
+ The text of the tooltip that appears when the user hovers over the widget.
+</api>
+
+<api name="allow">
+@property {object}
+ A object describing permissions for the content. It contains a single key
+ named `script` whose value is a boolean that indicates whether or not to
+ execute script in the content.
+</api>
+
+<api name="contentScriptFile">
+@property {string,array}
+ A local file URL or an array of local file URLs of content scripts to load.
+</api>
+
+<api name="contentScript">
+@property {string,array}
+ A string or an array of strings containing the texts of content scripts to
+ load.
+</api>
+
+<api name="contentScriptWhen">
+@property {string}
+ When to load the content scripts. This may have one of the following
+ values:
+
+ * "start": load content scripts immediately after the document
+ element for the widget is inserted into the DOM, but before the DOM content
+ itself has been loaded
+ * "ready": load content scripts once DOM content has been loaded,
+ corresponding to the
+ [DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+ event
+ * "end": load content scripts once all the content (DOM, JS, CSS,
+ images) for the widget has been loaded, at the time the
+ [window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+ fires
+
+</api>
+
+<api name="port">
+@property {EventEmitter}
+[EventEmitter](packages/api-utils/docs/events.html) object that allows you to:
+
+* send events to the content script using the `port.emit` function
+* receive events from the content script using the `port.on` function
+
+See the guide to
+<a href="dev-guide/addon-development/content-scripts/using-port.html">
+communicating using <code>port</code></a> for details.
+</api>
+
+<api name="attach">
+@event
+This event is emitted when a new `WidgetView` object is created using the
+`getView()` function.
+</api>
+
+<api name="click">
+@event
+This event is emitted when the widget is clicked.
+</api>
+
+<api name="message">
+@event
+If you listen to this event you can receive message events from content
+scripts associated with this widget. When a content script posts a
+message using `self.postMessage()`, the message is delivered to the add-on
+code in the widget's `message` event.
+
+@argument {value}
+Listeners are passed a single argument which is the message posted
+from the content script. The message can be any
+<a href = "dev-guide/addon-development/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
+</api>
+
+<api name="mouseover">
+@event
+This event is emitted when the user moves the mouse over the widget.
+</api>
+
+<api name="mouseout">
+@event
+This event is emitted when the user moves the mouse away from the widget.
+</api>
+
+</api>
+
+
+<api-name="WidgetView">
+@class
+Represents a widget instance specific to one browser window.
+
+Anything you do to an instance of this object will only be applied to the
+instance attached to its browser window: widget instances attached to other
+browser windows will be unaffected.
+
+By contrast, any changes you make to an instance of the normal `Widget` class
+will be applied across all browser windows.
+
+This class has all the same methods, attributes and events as the `Widget`
+class except for the `getView` method and the `attach` event.
+
+In this example `WidgetView` is used to display different content for
+`http` and `https` schemes:
+
+ // A widget that update its content specifically to each window.
+ let tabs = require("tabs");
+ let windows = require("windows").browserWindows;
+ let widget = widgets.Widget({
+ id: "window-specific-test",
+ label: "Widget with content specific to each window",
+ content: " ",
+ width: 50
+ });
+ // Observe tab switch or document changes in each existing tab:
+ function updateWidgetState(tab) {
+ let view = widget.getView(tab.window);
+ if (!view) return;
+ // Update widget displayed text:
+ view.content = tab.url.match(/^https/) ? "Secured" : "Unsafe";
+ }
+ tabs.on('ready', updateWidgetState);
+ tabs.on('activate', updateWidgetState);
+
+<api name="destroy">
+@method
+ Removes the widget view from the add-on bar.
+</api>
+
+<api name="postMessage">
+@method
+ Sends a message to the widget view's content scripts.
+@param data {value}
+ The message to send. The message can be any
+<a href = "dev-guide/addon-development/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
+</api>
+
+<api name="on">
+@method
+ Registers an event listener with the widget view.
+@param type {string}
+ The type of event to listen for.
+@param listener {function}
+ The listener function that handles the event.
+</api>
+
+<api name="removeListener">
+@method
+ Unregisters an event listener from the widget view.
+@param type {string}
+ The type of event for which `listener` was registered.
+@param listener {function}
+ The listener function that was registered.
+</api>
+
+<api name="label">
+@property {string}
+ The widget view's label. Read-only.
+</api>
+
+<api name="content">
+@property {string}
+ A string containing the widget view's content. It can contain HTML.
+ Setting it updates the widget view's appearance immediately. However,
+ if the widget view was created using `contentURL`, then this property
+ is meaningless, and setting it has no effect.
+</api>
+
+<api name="contentURL">
+@property {string}
+ The URL of content to load into the widget view. This can be
+ [local content](dev-guide/addon-development/web-content.html) or remote
+ content, an image or web content. Setting it updates the widget view's
+ appearance immediately. However, if the widget view was created using
+ `content`, then this property is meaningless, and setting it has no effect.
+</api>
+
+<api name="panel">
+@property {Panel}
+ A [panel](packages/addon-kit/docs/panel.html) to open when the user clicks on
+ the widget view.
+</api>
+
+<api name="width">
+@property {number}
+ The widget view's width in pixels. Setting it updates the widget view's
+ appearance immediately.
+</api>
+
+<api name="tooltip">
+@property {string}
+ The text of the tooltip that appears when the user hovers over the widget
+ view.
+</api>
+
+<api name="allow">
+@property {object}
+ A object describing permissions for the content. It contains a single key
+ named `script` whose value is a boolean that indicates whether or not to
+ execute script in the content.
+</api>
+
+<api name="contentScriptFile">
+@property {string,array}
+ A local file URL or an array of local file URLs of content scripts to load.
+</api>
+
+<api name="contentScript">
+@property {string,array}
+ A string or an array of strings containing the texts of content scripts to
+ load.
+</api>
+
+<api name="contentScriptWhen">
+@property {string}
+ When to load the content scripts. This may have one of the following
+ values:
+
+ * "start": load content scripts immediately after the document
+ element for the widget view is inserted into the DOM, but before the DOM
+ content itself has been loaded
+ * "ready": load content scripts once DOM content has been loaded,
+ corresponding to the
+ [DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+ event
+ * "end": load content scripts once all the content (DOM, JS, CSS,
+ images) for the widget view has been loaded, at the time the
+ [window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+ fires
+
+</api>
+
+<api name="port">
+@property {EventEmitter}
+[EventEmitter](packages/api-utils/docs/events.html) object that allows you to:
+
+* send events to the content script using the `port.emit` function
+* receive events from the content script using the `port.on`
+
+See the guide to
+<a href="dev-guide/addon-development/content-scripts/using-port.html">
+communicating using <code>port</code></a> for details.
+</api>
+
+<api name="detach">
+@event
+The `detach` event is fired when the widget view is removed from its related
+window.
+This can occur if the window is closed, Firefox exits, or the add-on is
+disabled.
+</api>
+
+<api name="click">
+@event
+This event is emitted when the widget view is clicked.
+</api>
+
+<api name="message">
+@event
+If you listen to this event you can receive message events from content
+scripts associated with this widget view. When a content script posts a
+message using `self.postMessage()`, the message is delivered to the add-on
+code in the widget view's `message` event.
+
+@argument {value}
+Listeners are passed a single argument which is the message posted
+from the content script. The message can be any
+<a href = "dev-guide/addon-development/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
+</api>
+
+<api name="mouseover">
+@event
+This event is emitted when the user moves the mouse over the widget view.
+</api>
+
+<api name="mouseout">
+@event
+This event is emitted when the user moves the mouse away from the widget view.
+</api>
+
+</api>
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/docs/windows.md b/tools/addon-sdk-1.3/packages/addon-kit/docs/windows.md
new file mode 100644
index 0000000..a5bed95
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/docs/windows.md
@@ -0,0 +1,187 @@
+<!-- contributed by Felipe Gomes [felipc@gmail.com] -->
+
+
+The `windows` module provides easy access to browser windows, their
+tabs, and open/close related functions and events.
+
+This module currently only supports browser windows and does not provide
+access to non-browser windows such as the Bookmarks Library, preferences
+or other non-browser windows created via add-ons.
+
+<api name="browserWindows">
+@property {List}
+An object that contains various properties and methods to access
+functionality from browser windows, such as opening new windows, accessing
+their tabs or switching the current active window.
+
+`browserWindows` provides access to all the currently open browser windows:
+
+ var windows = require("windows");
+ for each (var window in windows.browserWindows) {
+ console.log(window.title);
+ }
+
+ console.log(windows.browserWindows.length);
+
+Object emits all the events listed under "Events" section.
+
+####Examples####
+
+ var windows = require("windows").browserWindows;
+
+ // add a listener to the 'open' event
+ windows.on('open', function(window) {
+ myOpenWindows.push(window);
+ });
+
+ // add a listener to the 'close' event
+ windows.on('close', function(window) {
+ console.log("A window was closed.");
+ });
+
+<api name="activeWindow">
+@property {BrowserWindow}
+
+The currently active window. This property is read-only.
+
+**Example**
+
+ // get
+ var windows = require("windows");
+ console.log("title of active window is " +
+ windows.browserWindows.activeWindow.title);
+
+ anotherWindow.activate();
+ // set
+ windows.activeWindow == anotherWindow // true
+</api>
+
+</api>
+
+<api name="open">
+@function
+Open a new window.
+
+ var windows = require("windows").browserWindows;
+
+ // Open a new window.
+ windows.open("http://www.example.com");
+
+ // Open a new window and set a listener for "open" event.
+ windows.open({
+ url: "http://www.example.com",
+ onOpen: function(window) {
+ // do stuff like listen for content
+ // loading.
+ }
+ });
+
+Returns the window that was opened:
+
+ var widgets = require("widget");
+ var windows = require("windows").browserWindows;
+
+ var example = windows.open("http://www.example.com");
+
+ var widget = widgets.Widget({
+ id: "close-window",
+ label: "Close window",
+ contentURL: "http://www.mozilla.org/favicon.ico",
+ onClick: function() {
+ example.close();
+ }
+ });
+
+@param options {object}
+An object containing configurable options for how this window will be opened,
+as well as a callback for being notified when the window has fully opened.
+
+If the only option being used is `url`, then a bare string URL can be passed to
+`open` instead of specifying it as a property of the `options` object.
+
+@prop url {string}
+String URL to be opened in the new window.
+This is a required property.
+
+@prop [onOpen] {function}
+A callback function that is called when the window has opened. This does not
+mean that the URL content has loaded, only that the window itself is fully
+functional and its properties can be accessed. This is an optional property.
+
+@prop [onClose] {function}
+A callback function that is called when the window will be called.
+This is an optional property.
+
+@returns {BrowserWindow}
+</api>
+
+<api name="BrowserWindow">
+@class
+A `BrowserWindow` instance represents a single open window. They can be
+retrieved from the `browserWindows` property exported by this module.
+
+ var windows = require("windows").browserWindows;
+
+ //Print how many tabs the current window has
+ console.log("The active window has " +
+ windows.activeWindow.tabs.length +
+ " tabs.");
+
+ // Print the title of all browser windows
+ for each (var window in windows) {
+ console.log(window.title);
+ }
+
+ // close the active window
+ windows.activeWindow.close();
+
+ windows.activeWindow.close(function() {
+ console.log("The active window was closed");
+ });
+
+<api name="title">
+@property {string}
+The current title of the window. Usually the title of the active tab,
+plus an app identifier.
+This property is read-only.
+</api>
+
+<api name="tabs">
+@property {TabList}
+A live list of tabs in this window. This object has the same interface as the
+[`tabs` API](packages/addon-kit/docs/tabs.html), except it contains only the
+tabs in this window, not all tabs in all windows. This property is read-only.
+</api>
+
+<api name="activate">
+@method
+Makes window active, which will focus that window and bring it to the
+foreground.
+</api>
+
+<api name="close">
+@method
+Close the window.
+
+@param [callback] {function}
+A function to be called when the window finishes its closing process.
+This is an optional argument.
+</api>
+
+</api>
+
+<api name="open">
+@event
+Event emitted when a new window is open.
+This does not mean that the content has loaded, only that the browser window
+itself is fully visible to the user.
+@argument {Window}
+Listeners are passed the `window` object that triggered the event.
+</api>
+
+<api name="close">
+@event
+Event emitted when a window is closed.
+@argument {Window}
+Listeners are passed the `window` object that triggered the event.
+</api>
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;
+
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/package.json b/tools/addon-sdk-1.3/packages/addon-kit/package.json
new file mode 100644
index 0000000..6507ecb
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "addon-kit",
+ "description": "Add-on development made easy.",
+ "keywords": ["javascript", "engine", "platform", "xulrunner"],
+ "author": "Atul Varma (http://toolness.com/) <atul@mozilla.com>",
+ "contributors": [
+ "Myk Melez (http://melez.com/) <myk@mozilla.org>",
+ "Daniel Aquino <mr.danielaquino@gmail.com>"
+ ],
+ "version": "1.3",
+ "license": "MPL 1.1/GPL 2.0/LGPL 2.1",
+ "dependencies": ["api-utils"]
+}
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/pagemod-test-helpers.js b/tools/addon-sdk-1.3/packages/addon-kit/tests/pagemod-test-helpers.js
new file mode 100644
index 0000000..01799ec
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/pagemod-test-helpers.js
@@ -0,0 +1,62 @@
+"use strict";
+
+const {Cc,Ci} = require("chrome");
+const timer = require("timer");
+
+/**
+ * A helper function that creates a PageMod, then opens the specified URL
+ * and checks the effect of the page mod on 'onload' event via testCallback.
+ */
+exports.testPageMod = function testPageMod(test, testURL, pageModOptions,
+ testCallback, timeout) {
+ var xulApp = require("xul-app");
+ if (!xulApp.versionInRange(xulApp.platformVersion, "1.9.3a3", "*") &&
+ !xulApp.versionInRange(xulApp.platformVersion, "1.9.2.7", "1.9.2.*")) {
+ test.pass("Note: not testing PageMod, as it doesn't work on this platform version");
+ return null;
+ }
+
+ var wm = Cc['@mozilla.org/appshell/window-mediator;1']
+ .getService(Ci.nsIWindowMediator);
+ var browserWindow = wm.getMostRecentWindow("navigator:browser");
+ if (!browserWindow) {
+ test.pass("page-mod tests: could not find the browser window, so " +
+ "will not run. Use -a firefox to run the pagemod tests.")
+ return null;
+ }
+
+ if (timeout !== undefined)
+ test.waitUntilDone(timeout);
+ else
+ test.waitUntilDone();
+
+ let loader = test.makeSandboxedLoader();
+ let pageMod = loader.require("page-mod");
+
+ var pageMods = [new pageMod.PageMod(opts) for each(opts in pageModOptions)];
+
+ var tabBrowser = browserWindow.gBrowser;
+ var newTab = tabBrowser.addTab(testURL);
+ tabBrowser.selectedTab = newTab;
+ var b = tabBrowser.getBrowserForTab(newTab);
+
+ function onPageLoad() {
+ b.removeEventListener("load", onPageLoad, true);
+ // Delay callback execute as page-mod content scripts may be executed on
+ // load event. So page-mod actions may not be already done.
+ // If we delay even more contentScriptWhen:'end', we may want to modify
+ // this code again.
+ timer.setTimeout(testCallback, 0,
+ b.contentWindow.wrappedJSObject,
+ function done() {
+ pageMods.forEach(function(mod) mod.destroy());
+ // XXX leaks reported if we don't close the tab?
+ tabBrowser.removeTab(newTab);
+ loader.unload();
+ test.done();
+ });
+ }
+ b.addEventListener("load", onPageLoad, true);
+
+ return pageMods;
+}
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/test-clipboard.js b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-clipboard.js
new file mode 100644
index 0000000..5f61d48
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-clipboard.js
@@ -0,0 +1,60 @@
+
+// Test the typical use case, setting & getting with no flavors specified
+exports.testWithNoFlavor = function(test) {
+ var contents = "hello there";
+ var flavor = "text";
+ var fullFlavor = "text/unicode";
+ var clip = require("clipboard");
+ // Confirm we set the clipboard
+ test.assert(clip.set(contents));
+ // Confirm flavor is set
+ test.assertEqual(clip.currentFlavors[0], flavor);
+ // Confirm we set the clipboard
+ test.assertEqual(clip.get(), contents);
+ // Confirm we can get the clipboard using the flavor
+ test.assertEqual(clip.get(flavor), contents);
+ // Confirm we can still get the clipboard using the full flavor
+ test.assertEqual(clip.get(fullFlavor), contents);
+};
+
+// Test the slightly less common case where we specify the flavor
+exports.testWithFlavor = function(test) {
+ var contents = "<b>hello there</b>";
+ var contentsText = "hello there";
+ var flavor = "html";
+ var fullFlavor = "text/html";
+ var unicodeFlavor = "text";
+ var unicodeFullFlavor = "text/unicode";
+ var clip = require("clipboard");
+ test.assert(clip.set(contents, flavor));
+ test.assertEqual(clip.currentFlavors[0], unicodeFlavor);
+ test.assertEqual(clip.currentFlavors[1], flavor);
+ test.assertEqual(clip.get(), contentsText);
+ test.assertEqual(clip.get(flavor), contents);
+ test.assertEqual(clip.get(fullFlavor), contents);
+ test.assertEqual(clip.get(unicodeFlavor), contentsText);
+ test.assertEqual(clip.get(unicodeFullFlavor), contentsText);
+};
+
+// Test that the typical case still works when we specify the flavor to set
+exports.testWithRedundantFlavor = function(test) {
+ var contents = "<b>hello there</b>";
+ var flavor = "text";
+ var fullFlavor = "text/unicode";
+ var clip = require("clipboard");
+ test.assert(clip.set(contents, flavor));
+ test.assertEqual(clip.currentFlavors[0], flavor);
+ test.assertEqual(clip.get(), contents);
+ test.assertEqual(clip.get(flavor), contents);
+ test.assertEqual(clip.get(fullFlavor), contents);
+};
+
+exports.testNotInFlavor = function(test) {
+ var contents = "hello there";
+ var flavor = "html";
+ var clip = require("clipboard");
+ test.assert(clip.set(contents));
+ // If there's nothing on the clipboard with this flavor, should return null
+ test.assertEqual(clip.get(flavor), null);
+};
+// TODO: Test error cases.
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/test-context-menu.html b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-context-menu.html
new file mode 100644
index 0000000..a0fb5cb
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-context-menu.html
@@ -0,0 +1,78 @@
+<!-- ***** 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 LGPL or the GPL. 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 ***** -->
+
+<html>
+ <head>
+ <title>Context menu test</title>
+ </head>
+ <body>
+ <p>
+ <img id="image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==">
+ </p>
+
+ <p>
+ <a id="link" href="">
+ A simple link.
+ </a>
+ </p>
+
+ <p>
+ <a href="">
+ <span id="span-link">
+ A span inside a link.
+ </span>
+ </a>
+ </p>
+
+ <p id="text">
+ Some text.
+ </p>
+
+ <p>
+ <textarea id="textfield">
+ A text field,
+ with some text.
+ </textarea>
+ </p>
+
+ <p>
+ <iframe id="iframe" src="data:text/html,An iframe."
+ width="200" height="100">
+ </iframe>
+ </p>
+ </body>
+</html>
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/test-context-menu.js b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-context-menu.js
new file mode 100644
index 0000000..e0f7c2f
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-context-menu.js
@@ -0,0 +1,2074 @@
+/* -*- 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)
+ * 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 ***** */
+
+let {Cc,Ci} = require("chrome");
+
+// These should match the same constants in the module.
+const ITEM_CLASS = "jetpack-context-menu-item";
+const SEPARATOR_ID = "jetpack-context-menu-separator";
+const OVERFLOW_THRESH_DEFAULT = 10;
+const OVERFLOW_THRESH_PREF =
+ "extensions.addon-sdk.context-menu.overflowThreshold";
+const OVERFLOW_MENU_ID = "jetpack-content-menu-overflow-menu";
+const OVERFLOW_POPUP_ID = "jetpack-content-menu-overflow-popup";
+
+const TEST_DOC_URL = __url__.replace(/\.js$/, ".html");
+
+
+// Destroying items that were previously created should cause them to be absent
+// from the menu.
+exports.testConstructDestroy = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ // Create an item.
+ let item = new loader.cm.Item({ label: "item" });
+ test.assertEqual(item.parentMenu, null, "item's parent menu should be null");
+
+ test.showMenu(null, function (popup) {
+
+ // It should be present when the menu is shown.
+ test.checkMenu([item], [], []);
+ popup.hidePopup();
+
+ // Destroy the item. Multiple destroys should be harmless.
+ item.destroy();
+ item.destroy();
+ test.showMenu(null, function (popup) {
+
+ // It should be removed from the menu.
+ test.checkMenu([], [], [item]);
+ test.done();
+ });
+ });
+};
+
+
+// Destroying an item twice should not cause an error.
+exports.testDestroyTwice = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({ label: "item" });
+ item.destroy();
+ item.destroy();
+
+ test.pass("Destroying an item twice should not cause an error.");
+ test.done();
+};
+
+
+// CSS selector contexts should cause their items to be present in the menu
+// when the menu is invoked on nodes that match the selectors.
+exports.testSelectorContextMatch = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({
+ label: "item",
+ data: "item",
+ context: loader.cm.SelectorContext("img")
+ });
+
+ test.withTestDoc(function (window, doc) {
+ test.showMenu(doc.getElementById("image"), function (popup) {
+ test.checkMenu([item], [], []);
+ test.done();
+ });
+ });
+};
+
+
+// CSS selector contexts should cause their items to be present in the menu
+// when the menu is invoked on nodes that have ancestors that match the
+// selectors.
+exports.testSelectorAncestorContextMatch = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({
+ label: "item",
+ data: "item",
+ context: loader.cm.SelectorContext("a[href]")
+ });
+
+ test.withTestDoc(function (window, doc) {
+ test.showMenu(doc.getElementById("span-link"), function (popup) {
+ test.checkMenu([item], [], []);
+ test.done();
+ });
+ });
+};
+
+
+// CSS selector contexts should cause their items to be absent from the menu
+// when the menu is not invoked on nodes that match or have ancestors that
+// match the selectors.
+exports.testSelectorContextNoMatch = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({
+ label: "item",
+ data: "item",
+ context: loader.cm.SelectorContext("img")
+ });
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([], [item], []);
+ test.done();
+ });
+};
+
+
+// Page contexts should cause their items to be present in the menu when the
+// menu is not invoked on an active element.
+exports.testPageContextMatch = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let items = [
+ new loader.cm.Item({
+ label: "item 0"
+ }),
+ new loader.cm.Item({
+ label: "item 1",
+ context: undefined
+ }),
+ new loader.cm.Item({
+ label: "item 2",
+ context: loader.cm.PageContext()
+ }),
+ new loader.cm.Item({
+ label: "item 3",
+ context: [loader.cm.PageContext()]
+ })
+ ];
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu(items, [], []);
+ test.done();
+ });
+};
+
+
+// Page contexts should cause their items to be absent from the menu when the
+// menu is invoked on an active element.
+exports.testPageContextNoMatch = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let items = [
+ new loader.cm.Item({
+ label: "item 0"
+ }),
+ new loader.cm.Item({
+ label: "item 1",
+ context: undefined
+ }),
+ new loader.cm.Item({
+ label: "item 2",
+ context: loader.cm.PageContext()
+ }),
+ new loader.cm.Item({
+ label: "item 3",
+ context: [loader.cm.PageContext()]
+ })
+ ];
+
+ test.withTestDoc(function (window, doc) {
+ test.showMenu(doc.getElementById("image"), function (popup) {
+ test.checkMenu([], items, []);
+ test.done();
+ });
+ });
+};
+
+
+// Selection contexts should cause items to appear when a selection exists.
+exports.testSelectionContextMatch = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = loader.cm.Item({
+ label: "item",
+ context: loader.cm.SelectionContext()
+ });
+
+ test.withTestDoc(function (window, doc) {
+ window.getSelection().selectAllChildren(doc.body);
+ test.showMenu(null, function (popup) {
+ test.checkMenu([item], [], []);
+ test.done();
+ });
+ });
+};
+
+
+// Selection contexts should cause items to appear when a selection exists in
+// a text field.
+exports.testSelectionContextMatchInTextField = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = loader.cm.Item({
+ label: "item",
+ context: loader.cm.SelectionContext()
+ });
+
+ test.withTestDoc(function (window, doc) {
+ let textfield = doc.getElementById("textfield");
+ textfield.setSelectionRange(0, textfield.value.length);
+ test.showMenu(textfield, function (popup) {
+ test.checkMenu([item], [], []);
+ test.done();
+ });
+ });
+};
+
+
+// Selection contexts should not cause items to appear when a selection does
+// not exist in a text field.
+exports.testSelectionContextNoMatchInTextField = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = loader.cm.Item({
+ label: "item",
+ context: loader.cm.SelectionContext()
+ });
+
+ test.withTestDoc(function (window, doc) {
+ let textfield = doc.getElementById("textfield");
+ textfield.setSelectionRange(0, 0);
+ test.showMenu(textfield, function (popup) {
+ test.checkMenu([], [item], []);
+ test.done();
+ });
+ });
+};
+
+
+// Selection contexts should not cause items to appear when a selection does
+// not exist.
+exports.testSelectionContextNoMatch = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = loader.cm.Item({
+ label: "item",
+ context: loader.cm.SelectionContext()
+ });
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([], [item], []);
+ test.done();
+ });
+};
+
+
+// URL contexts should cause items to appear on pages that match.
+exports.testURLContextMatch = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let items = [
+ loader.cm.Item({
+ label: "item 0",
+ context: loader.cm.URLContext(TEST_DOC_URL)
+ }),
+ loader.cm.Item({
+ label: "item 1",
+ context: loader.cm.URLContext([TEST_DOC_URL, "*.bogus.com"])
+ })
+ ];
+
+ test.withTestDoc(function (window, doc) {
+ test.showMenu(null, function (popup) {
+ test.checkMenu(items, [], []);
+ test.done();
+ });
+ });
+};
+
+
+// URL contexts should not cause items to appear on pages that do not match.
+exports.testURLContextNoMatch = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let items = [
+ loader.cm.Item({
+ label: "item 0",
+ context: loader.cm.URLContext("*.bogus.com")
+ }),
+ loader.cm.Item({
+ label: "item 1",
+ context: loader.cm.URLContext(["*.bogus.com", "*.gnarly.com"])
+ })
+ ];
+
+ test.withTestDoc(function (window, doc) {
+ test.showMenu(null, function (popup) {
+ test.checkMenu([], items, []);
+ test.done();
+ });
+ });
+};
+
+
+// Removing a non-matching URL context after its item is created and the page is
+// loaded should cause the item's content script to be evaluated.
+exports.testURLContextRemove = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let shouldBeEvaled = false;
+ let context = loader.cm.URLContext("*.bogus.com");
+ let item = loader.cm.Item({
+ label: "item",
+ context: context,
+ contentScript: 'self.postMessage("ok");',
+ onMessage: function (msg) {
+ test.assert(shouldBeEvaled,
+ "content script should be evaluated when expected");
+ shouldBeEvaled = false;
+ test.done();
+ }
+ });
+
+ test.withTestDoc(function (window, doc) {
+ shouldBeEvaled = true;
+ item.context.remove(context);
+ });
+};
+
+
+// Adding a non-matching URL context after its item is created and the page is
+// loaded should cause the item's worker to be destroyed.
+exports.testURLContextAdd = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = loader.cm.Item({ label: "item" });
+
+ test.withTestDoc(function (window, doc) {
+ let privatePropsKey = loader.globalScope.PRIVATE_PROPS_KEY;
+ let workerReg = item.valueOf(privatePropsKey)._workerReg;
+
+ let found = false;
+ for each (let winWorker in workerReg.winWorkers) {
+ if (winWorker.win === window) {
+ found = true;
+ break;
+ }
+ }
+ this.test.assert(found, "window should be present in worker registry");
+
+ item.context.add(loader.cm.URLContext("*.bogus.com"));
+
+ for each (let winWorker in workerReg.winWorkers)
+ this.test.assertNotEqual(winWorker.win, window,
+ "window should not be present in worker registry");
+
+ test.done();
+ });
+};
+
+
+// Content contexts that return true should cause their items to be present
+// in the menu.
+exports.testContentContextMatch = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({
+ label: "item",
+ contentScript: 'self.on("context", function () true);'
+ });
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([item], [], []);
+ test.done();
+ });
+};
+
+
+// Content contexts that return false should cause their items to be absent
+// from the menu.
+exports.testContentContextNoMatch = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({
+ label: "item",
+ contentScript: 'self.on("context", function () false);'
+ });
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([], [item], []);
+ test.done();
+ });
+};
+
+
+// Content contexts that return a string should cause their items to be present
+// in the menu and the items' labels to be updated.
+exports.testContentContextMatchString = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({
+ label: "first label",
+ contentScript: 'self.on("context", function () "second label");'
+ });
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([item], [], []);
+ test.assertEqual(item.label, "second label",
+ "item's label should be updated");
+ test.done();
+ });
+};
+
+
+// The args passed to context listeners should be correct.
+exports.testContentContextArgs = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({
+ label: "item",
+ contentScript: 'self.on("context", function (node) {' +
+ ' let Ci = Components.interfaces;' +
+ ' self.postMessage(node instanceof Ci.nsIDOMHTMLElement);' +
+ ' return false;' +
+ '});',
+ onMessage: function (isElt) {
+ test.assert(isElt, "node should be an HTML element");
+ test.done();
+ }
+ });
+
+ test.showMenu(null, function () {});
+};
+
+
+// Multiple contexts imply intersection, not union, and content context
+// listeners should not be called if all declarative contexts are not current.
+exports.testMultipleContexts = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({
+ label: "item",
+ context: [loader.cm.SelectorContext("a[href]"), loader.cm.PageContext()],
+ contentScript: 'self.on("context", function () self.postMessage());',
+ onMessage: function () {
+ test.fail("Context listener should not be called");
+ }
+ });
+
+ test.withTestDoc(function (window, doc) {
+ test.showMenu(doc.getElementById("span-link"), function (popup) {
+ test.checkMenu([], [item], []);
+ test.done();
+ });
+ });
+};
+
+
+// Once a context is removed, it should no longer cause its item to appear.
+exports.testRemoveContext = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let ctxt = loader.cm.SelectorContext("img");
+ let item = new loader.cm.Item({
+ label: "item",
+ context: ctxt
+ });
+
+ test.withTestDoc(function (window, doc) {
+ test.showMenu(doc.getElementById("image"), function (popup) {
+
+ // The item should be present at first.
+ test.checkMenu([item], [], []);
+ popup.hidePopup();
+
+ // Remove the img context and check again.
+ item.context.remove(ctxt);
+ test.showMenu(doc.getElementById("image"), function (popup) {
+ test.checkMenu([], [item], []);
+ test.done();
+ });
+ });
+ });
+};
+
+
+// Lots of items should overflow into the overflow submenu.
+exports.testOverflow = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let items = [];
+ for (let i = 0; i < OVERFLOW_THRESH_DEFAULT + 1; i++) {
+ let item = new loader.cm.Item({ label: "item " + i });
+ items.push(item);
+ }
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu(items, [], []);
+ test.done();
+ });
+};
+
+
+// Module unload should cause all items to be removed.
+exports.testUnload = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({ label: "item" });
+
+ test.showMenu(null, function (popup) {
+
+ // The menu should contain the item.
+ test.checkMenu([item], [], []);
+ popup.hidePopup();
+
+ // Unload the module.
+ loader.unload();
+ test.showMenu(null, function (popup) {
+
+ // The item should be removed from the menu.
+ test.checkMenu([], [], [item]);
+ test.done();
+ });
+ });
+};
+
+
+// Using multiple module instances to add items without causing overflow should
+// work OK. Assumes OVERFLOW_THRESH_DEFAULT <= 2.
+exports.testMultipleModulesAdd = function (test) {
+ test = new TestHelper(test);
+ let loader0 = test.newLoader();
+ let loader1 = test.newLoader();
+
+ // Use each module to add an item, then unload each module in turn.
+ let item0 = new loader0.cm.Item({ label: "item 0" });
+ let item1 = new loader1.cm.Item({ label: "item 1" });
+
+ test.showMenu(null, function (popup) {
+
+ // The menu should contain both items.
+ test.checkMenu([item0, item1], [], []);
+ popup.hidePopup();
+
+ // Unload the first module.
+ loader0.unload();
+ test.showMenu(null, function (popup) {
+
+ // The first item should be removed from the menu.
+ test.checkMenu([item1], [], [item0]);
+ popup.hidePopup();
+
+ // Unload the second module.
+ loader1.unload();
+ test.showMenu(null, function (popup) {
+
+ // Both items should be removed from the menu.
+ test.checkMenu([], [], [item0, item1]);
+ test.done();
+ });
+ });
+ });
+};
+
+
+// Using multiple module instances to add items causing overflow should work OK.
+exports.testMultipleModulesAddOverflow = function (test) {
+ test = new TestHelper(test);
+ let loader0 = test.newLoader();
+ let loader1 = test.newLoader();
+
+ // Use module 0 to add OVERFLOW_THRESH_DEFAULT items.
+ let items0 = [];
+ for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i++) {
+ let item = new loader0.cm.Item({ label: "item 0 " + i });
+ items0.push(item);
+ }
+
+ // Use module 1 to add one item.
+ let item1 = new loader1.cm.Item({ label: "item 1" });
+
+ let allItems = items0.concat(item1);
+
+ test.showMenu(null, function (popup) {
+
+ // The menu should contain all items in overflow.
+ test.checkMenu(allItems, [], []);
+ popup.hidePopup();
+
+ // Unload the first module.
+ loader0.unload();
+ test.showMenu(null, function (popup) {
+
+ // The first items should be removed from the menu, which should not
+ // overflow.
+ test.checkMenu([item1], [], items0);
+ popup.hidePopup();
+
+ // Unload the second module.
+ loader1.unload();
+ test.showMenu(null, function (popup) {
+
+ // All items should be removed from the menu.
+ test.checkMenu([], [], allItems);
+ test.done();
+ });
+ });
+ });
+};
+
+
+// Using multiple module instances to modify the menu without causing overflow
+// should work OK. This test creates two loaders and:
+// loader0 create item -> loader1 create item -> loader0.unload ->
+// loader1.unload
+exports.testMultipleModulesDiffContexts1 = function (test) {
+ test = new TestHelper(test);
+ let loader0 = test.newLoader();
+ let loader1 = test.newLoader();
+
+ let item0 = new loader0.cm.Item({
+ label: "item 0",
+ context: loader0.cm.SelectorContext("img")
+ });
+
+ let item1 = new loader1.cm.Item({ label: "item 1" });
+
+ test.showMenu(null, function (popup) {
+
+ // The menu should contain item1.
+ test.checkMenu([item1], [item0], []);
+ popup.hidePopup();
+
+ // Unload module 0.
+ loader0.unload();
+ test.showMenu(null, function (popup) {
+
+ // item0 should be removed from the menu.
+ test.checkMenu([item1], [], [item0]);
+ popup.hidePopup();
+
+ // Unload module 1.
+ loader1.unload();
+ test.showMenu(null, function (popup) {
+
+ // Both items should be removed from the menu.
+ test.checkMenu([], [], [item0, item1]);
+ test.done();
+ });
+ });
+ });
+};
+
+
+// Using multiple module instances to modify the menu without causing overflow
+// should work OK. This test creates two loaders and:
+// loader1 create item -> loader0 create item -> loader0.unload ->
+// loader1.unload
+exports.testMultipleModulesDiffContexts2 = function (test) {
+ test = new TestHelper(test);
+ let loader0 = test.newLoader();
+ let loader1 = test.newLoader();
+
+ let item1 = new loader1.cm.Item({ label: "item 1" });
+
+ let item0 = new loader0.cm.Item({
+ label: "item 0",
+ context: loader0.cm.SelectorContext("img")
+ });
+
+ test.showMenu(null, function (popup) {
+
+ // The menu should contain item1.
+ test.checkMenu([item1], [item0], []);
+ popup.hidePopup();
+
+ // Unload module 0.
+ loader0.unload();
+ test.showMenu(null, function (popup) {
+
+ // item0 should be removed from the menu.
+ test.checkMenu([item1], [], [item0]);
+ popup.hidePopup();
+
+ // Unload module 1.
+ loader1.unload();
+ test.showMenu(null, function (popup) {
+
+ // Both items should be removed from the menu.
+ test.checkMenu([], [], [item0, item1]);
+ test.done();
+ });
+ });
+ });
+};
+
+
+// Using multiple module instances to modify the menu without causing overflow
+// should work OK. This test creates two loaders and:
+// loader0 create item -> loader1 create item -> loader1.unload ->
+// loader0.unload
+exports.testMultipleModulesDiffContexts3 = function (test) {
+ test = new TestHelper(test);
+ let loader0 = test.newLoader();
+ let loader1 = test.newLoader();
+
+ let item0 = new loader0.cm.Item({
+ label: "item 0",
+ context: loader0.cm.SelectorContext("img")
+ });
+
+ let item1 = new loader1.cm.Item({ label: "item 1" });
+
+ test.showMenu(null, function (popup) {
+
+ // The menu should contain item1.
+ test.checkMenu([item1], [item0], []);
+ popup.hidePopup();
+
+ // Unload module 1.
+ loader1.unload();
+ test.showMenu(null, function (popup) {
+
+ // item1 should be removed from the menu.
+ test.checkMenu([], [item0], [item1]);
+ popup.hidePopup();
+
+ // Unload module 0.
+ loader0.unload();
+ test.showMenu(null, function (popup) {
+
+ // Both items should be removed from the menu.
+ test.checkMenu([], [], [item0, item1]);
+ test.done();
+ });
+ });
+ });
+};
+
+
+// Using multiple module instances to modify the menu without causing overflow
+// should work OK. This test creates two loaders and:
+// loader1 create item -> loader0 create item -> loader1.unload ->
+// loader0.unload
+exports.testMultipleModulesDiffContexts4 = function (test) {
+ test = new TestHelper(test);
+ let loader0 = test.newLoader();
+ let loader1 = test.newLoader();
+
+ let item1 = new loader1.cm.Item({ label: "item 1" });
+
+ let item0 = new loader0.cm.Item({
+ label: "item 0",
+ context: loader0.cm.SelectorContext("img")
+ });
+
+ test.showMenu(null, function (popup) {
+
+ // The menu should contain item1.
+ test.checkMenu([item1], [item0], []);
+ popup.hidePopup();
+
+ // Unload module 1.
+ loader1.unload();
+ test.showMenu(null, function (popup) {
+
+ // item1 should be removed from the menu.
+ test.checkMenu([], [item0], [item1]);
+ popup.hidePopup();
+
+ // Unload module 0.
+ loader0.unload();
+ test.showMenu(null, function (popup) {
+
+ // Both items should be removed from the menu.
+ test.checkMenu([], [], [item0, item1]);
+ test.done();
+ });
+ });
+ });
+};
+
+
+// Test interactions between a loaded module, unloading another module, and the
+// menu separator and overflow submenu.
+exports.testMultipleModulesAddRemove = function (test) {
+ test = new TestHelper(test);
+ let loader0 = test.newLoader();
+ let loader1 = test.newLoader();
+
+ let item = new loader0.cm.Item({ label: "item" });
+
+ test.showMenu(null, function (popup) {
+
+ // The menu should contain the item.
+ test.checkMenu([item], [], []);
+ popup.hidePopup();
+
+ // Remove the item.
+ item.destroy();
+ test.showMenu(null, function (popup) {
+
+ // The item should be removed from the menu.
+ test.checkMenu([], [], [item]);
+ popup.hidePopup();
+
+ // Unload module 1.
+ loader1.unload();
+ test.showMenu(null, function (popup) {
+
+ // There shouldn't be any errors involving the menu separator or
+ // overflow submenu.
+ test.checkMenu([], [], [item]);
+ test.done();
+ });
+ });
+ });
+};
+
+
+// An item's click listener should work.
+exports.testItemClick = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({
+ label: "item",
+ data: "item data",
+ contentScript: 'self.on("click", function (node, data) {' +
+ ' let Ci = Components.interfaces;' +
+ ' self.postMessage({' +
+ ' isElt: node instanceof Ci.nsIDOMHTMLElement,' +
+ ' data: data' +
+ ' });' +
+ '});',
+ onMessage: function (data) {
+ test.assertEqual(this, item, "`this` inside onMessage should be item");
+ test.assert(data.isElt, "node should be an HTML element");
+ test.assertEqual(data.data, item.data, "data should be item data");
+ test.done();
+ }
+ });
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([item], [], []);
+ let elt = test.getItemElt(popup, item);
+ elt.click();
+ });
+};
+
+
+// A menu's click listener should work and receive bubbling clicks from
+// sub-items appropriately. This also tests menus and ensures that when a CSS
+// selector context matches the clicked node's ancestor, the matching ancestor
+// is passed to listeners as the clicked node.
+exports.testMenuClick = function (test) {
+ // Create a top-level menu, submenu, and item, like this:
+ // topMenu -> submenu -> item
+ // Click the item and make sure the click bubbles.
+ let test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({
+ label: "submenu item",
+ data: "submenu item data"
+ });
+
+ let submenu = new loader.cm.Menu({
+ label: "submenu",
+ items: [item]
+ });
+
+ let topMenu = new loader.cm.Menu({
+ label: "top menu",
+ contentScript: 'self.on("click", function (node, data) {' +
+ ' let Ci = Components.interfaces;' +
+ ' self.postMessage({' +
+ ' isAnchor: node instanceof Ci.nsIDOMHTMLAnchorElement,' +
+ ' data: data' +
+ ' });' +
+ '});',
+ onMessage: function (data) {
+ test.assertEqual(this, topMenu, "`this` inside top menu should be menu");
+ test.assert(data.isAnchor, "Clicked node should be anchor");
+ test.assertEqual(data.data, item.data,
+ "Clicked item data should be correct");
+ test.done();
+ },
+ items: [submenu],
+ context: loader.cm.SelectorContext("a")
+ });
+
+ test.withTestDoc(function (window, doc) {
+ test.showMenu(doc.getElementById("span-link"), function (popup) {
+ test.checkMenu([topMenu], [], []);
+ let topMenuElt = test.getItemElt(popup, topMenu);
+ let topMenuPopup = topMenuElt.firstChild;
+ let submenuElt = test.getItemElt(topMenuPopup, submenu);
+ let submenuPopup = submenuElt.firstChild;
+ let itemElt = test.getItemElt(submenuPopup, item);
+ itemElt.click();
+ });
+ });
+};
+
+
+// Click listeners should work when multiple modules are loaded.
+exports.testItemClickMultipleModules = function (test) {
+ test = new TestHelper(test);
+ let loader0 = test.newLoader();
+ let loader1 = test.newLoader();
+
+ let item0 = loader0.cm.Item({
+ label: "loader 0 item",
+ contentScript: 'self.on("click", self.postMessage);',
+ onMessage: function () {
+ test.fail("loader 0 item should not emit click event");
+ }
+ });
+ let item1 = loader1.cm.Item({
+ label: "loader 1 item",
+ contentScript: 'self.on("click", self.postMessage);',
+ onMessage: function () {
+ test.pass("loader 1 item clicked as expected");
+ test.done();
+ }
+ });
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([item0, item1], [], []);
+ let item1Elt = test.getItemElt(popup, item1);
+ item1Elt.click();
+ });
+};
+
+
+// Adding a separator to a submenu should work OK.
+exports.testSeparator = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let menu = new loader.cm.Menu({
+ label: "submenu",
+ items: [new loader.cm.Separator()]
+ });
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([menu], [], []);
+ test.done();
+ });
+};
+
+
+// Existing context menu modifications should apply to new windows.
+exports.testNewWindow = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({ label: "item" });
+
+ test.withNewWindow(function () {
+ test.showMenu(null, function (popup) {
+ test.checkMenu([item], [], []);
+ test.done();
+ });
+ });
+};
+
+
+// When a new window is opened, items added by an unloaded module should not
+// be present in the menu.
+exports.testNewWindowMultipleModules = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+ let item = new loader.cm.Item({ label: "item" });
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([item], [], []);
+ popup.hidePopup();
+ loader.unload();
+ test.withNewWindow(function () {
+ test.showMenu(null, function (popup) {
+ test.checkMenu([], [], []);
+ test.done();
+ });
+ });
+ });
+};
+
+
+// Items in the context menu should be sorted according to locale.
+exports.testSorting = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ // Make an unsorted items list. It'll look like this:
+ // item 1, item 0, item 3, item 2, item 5, item 4, ...
+ let items = [];
+ for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i += 2) {
+ items.push(new loader.cm.Item({ label: "item " + (i + 1) }));
+ items.push(new loader.cm.Item({ label: "item " + i }));
+ }
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu(items, [], []);
+ test.done();
+ });
+};
+
+
+// Items in the overflow menu should be sorted according to locale.
+exports.testSortingOverflow = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ // Make an unsorted items list. It'll look like this:
+ // item 1, item 0, item 3, item 2, item 5, item 4, ...
+ let items = [];
+ for (let i = 0; i < OVERFLOW_THRESH_DEFAULT * 2; i += 2) {
+ items.push(new loader.cm.Item({ label: "item " + (i + 1) }));
+ items.push(new loader.cm.Item({ label: "item " + i }));
+ }
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu(items, [], []);
+ test.done();
+ });
+};
+
+
+// Multiple modules shouldn't interfere with sorting.
+exports.testSortingMultipleModules = function (test) {
+ test = new TestHelper(test);
+ let loader0 = test.newLoader();
+ let loader1 = test.newLoader();
+
+ let items0 = [];
+ let items1 = [];
+ for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i++) {
+ if (i % 2) {
+ let item = new loader0.cm.Item({ label: "item " + i });
+ items0.push(item);
+ }
+ else {
+ let item = new loader1.cm.Item({ label: "item " + i });
+ items1.push(item);
+ }
+ }
+ let allItems = items0.concat(items1);
+
+ test.showMenu(null, function (popup) {
+
+ // All items should be present and sorted.
+ test.checkMenu(allItems, [], []);
+ popup.hidePopup();
+ loader0.unload();
+ loader1.unload();
+ test.showMenu(null, function (popup) {
+
+ // All items should be removed.
+ test.checkMenu([], [], allItems);
+ test.done();
+ });
+ });
+};
+
+
+// The binary search of insertionPoint should work OK.
+exports.testInsertionPoint = function (test) {
+ function mockElts(labels) {
+ return labels.map(function (label) {
+ return { label: label, getAttribute: function (l) label };
+ });
+ }
+
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+ let insertionPoint = loader.globalScope.insertionPoint;
+
+ let ip = insertionPoint("a", []);
+ test.assertStrictEqual(ip, null, "Insertion point should be null");
+
+ ip = insertionPoint("a", mockElts(["b"]));
+ test.assertEqual(ip.label, "b", "Insertion point should be 'b'");
+
+ ip = insertionPoint("c", mockElts(["b"]));
+ test.assertStrictEqual(ip, null, "Insertion point should be null");
+
+ ip = insertionPoint("b", mockElts(["a", "c"]));
+ test.assertEqual(ip.label, "c", "Insertion point should be 'c'");
+
+ ip = insertionPoint("c", mockElts(["a", "b", "d"]));
+ test.assertEqual(ip.label, "d", "Insertion point should be 'd'");
+
+ ip = insertionPoint("a", mockElts(["b", "c", "d"]));
+ test.assertEqual(ip.label, "b", "Insertion point should be 'b'");
+
+ ip = insertionPoint("d", mockElts(["a", "b", "c"]));
+ test.assertStrictEqual(ip, null, "Insertion point should be null");
+
+ test.done();
+};
+
+
+// Content click handlers and context handlers should be able to communicate,
+// i.e., they're eval'ed in the same worker and sandbox.
+exports.testContentCommunication = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({
+ label: "item",
+ contentScript: 'var potato;' +
+ 'self.on("context", function () {' +
+ ' potato = "potato";' +
+ ' return true;' +
+ '});' +
+ 'self.on("click", function () {' +
+ ' self.postMessage(potato);' +
+ '});',
+ });
+
+ item.on("message", function (data) {
+ test.assertEqual(data, "potato", "That's a lot of potatoes!");
+ test.done();
+ });
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([item], [], []);
+ let elt = test.getItemElt(popup, item);
+ elt.click();
+ });
+};
+
+
+// When the context menu is invoked on a tab that was already open when the
+// module was loaded, it should contain the expected items and content workers
+// should function as expected.
+exports.testLoadWithOpenTab = function (test) {
+ test = new TestHelper(test);
+ test.withTestDoc(function (window, doc) {
+ let loader = test.newLoader();
+ let item = new loader.cm.Item({
+ label: "item",
+ contentScript:
+ 'self.on("click", function () self.postMessage("click"));',
+ onMessage: function (msg) {
+ if (msg === "click")
+ test.done();
+ }
+ });
+ test.showMenu(null, function (popup) {
+ test.checkMenu([item], [], []);
+ test.getItemElt(popup, item).click();
+ });
+ });
+};
+
+
+// Setting an item's label before the menu is ever shown should correctly change
+// its label and, if necessary, its order within the menu.
+exports.testSetLabelBeforeShow = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let items = [
+ new loader.cm.Item({ label: "a" }),
+ new loader.cm.Item({ label: "b" })
+ ]
+ items[0].label = "z";
+ test.assertEqual(items[0].label, "z");
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([items[1], items[0]], [], []);
+ test.done();
+ });
+};
+
+
+// Setting an item's label after the menu is shown should correctly change its
+// label and, if necessary, its order within the menu.
+exports.testSetLabelAfterShow = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let items = [
+ new loader.cm.Item({ label: "a" }),
+ new loader.cm.Item({ label: "b" })
+ ];
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu(items, [], []);
+ popup.hidePopup();
+
+ items[0].label = "z";
+ test.assertEqual(items[0].label, "z");
+ test.showMenu(null, function (popup) {
+ test.checkMenu([items[1], items[0]], [], []);
+ test.done();
+ });
+ });
+};
+
+
+// Setting an item's label before the menu is ever shown should correctly change
+// its label and, if necessary, its order within the menu if the item is in the
+// overflow submenu.
+exports.testSetLabelBeforeShowOverflow = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let prefs = loader.loader.require("preferences-service");
+ prefs.set(OVERFLOW_THRESH_PREF, 0);
+
+ let items = [
+ new loader.cm.Item({ label: "a" }),
+ new loader.cm.Item({ label: "b" })
+ ]
+ items[0].label = "z";
+ test.assertEqual(items[0].label, "z");
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([items[1], items[0]], [], []);
+ prefs.set(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT);
+ test.done();
+ });
+};
+
+
+// Setting an item's label after the menu is shown should correctly change its
+// label and, if necessary, its order within the menu if the item is in the
+// overflow submenu.
+exports.testSetLabelAfterShowOverflow = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let prefs = loader.loader.require("preferences-service");
+ prefs.set(OVERFLOW_THRESH_PREF, 0);
+
+ let items = [
+ new loader.cm.Item({ label: "a" }),
+ new loader.cm.Item({ label: "b" })
+ ];
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu(items, [], []);
+ popup.hidePopup();
+
+ items[0].label = "z";
+ test.assertEqual(items[0].label, "z");
+ test.showMenu(null, function (popup) {
+ test.checkMenu([items[1], items[0]], [], []);
+ prefs.set(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT);
+ test.done();
+ });
+ });
+};
+
+
+// Setting the label of an item in a Menu should work.
+exports.testSetLabelMenuItem = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let menu = loader.cm.Menu({
+ label: "menu",
+ items: [loader.cm.Item({ label: "a" })]
+ });
+ menu.items[0].label = "z";
+
+ test.assertEqual(menu.items[0].label, "z");
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([menu], [], []);
+ test.done();
+ });
+};
+
+
+// Menu.addItem() should work.
+exports.testMenuAddItem = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let menu = loader.cm.Menu({
+ label: "menu",
+ items: [
+ loader.cm.Item({ label: "item 0" })
+ ]
+ });
+ menu.addItem(loader.cm.Item({ label: "item 1" }));
+ menu.addItem(loader.cm.Item({ label: "item 2" }));
+
+ test.assertEqual(menu.items.length, 3,
+ "menu should have correct number of items");
+ for (let i = 0; i < 3; i++) {
+ test.assertEqual(menu.items[i].label, "item " + i,
+ "item label should be correct");
+ test.assertEqual(menu.items[i].parentMenu, menu,
+ "item's parent menu should be correct");
+ }
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([menu], [], []);
+ test.done();
+ });
+};
+
+
+// Adding the same item twice to a menu should work as expected.
+exports.testMenuAddItemTwice = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let menu = loader.cm.Menu({
+ label: "menu",
+ items: []
+ });
+ let subitem = loader.cm.Item({ label: "item 1" })
+ menu.addItem(subitem);
+ menu.addItem(loader.cm.Item({ label: "item 0" }));
+ menu.addItem(subitem);
+
+ test.assertEqual(menu.items.length, 2,
+ "menu should have correct number of items");
+ for (let i = 0; i < 2; i++) {
+ test.assertEqual(menu.items[i].label, "item " + i,
+ "item label should be correct");
+ }
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([menu], [], []);
+ test.done();
+ });
+};
+
+
+// Menu.removeItem() should work.
+exports.testMenuRemoveItem = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let subitem = loader.cm.Item({ label: "item 1" });
+ let menu = loader.cm.Menu({
+ label: "menu",
+ items: [
+ loader.cm.Item({ label: "item 0" }),
+ subitem,
+ loader.cm.Item({ label: "item 2" })
+ ]
+ });
+
+ // Removing twice should be harmless.
+ menu.removeItem(subitem);
+ menu.removeItem(subitem);
+
+ test.assertEqual(subitem.parentMenu, null,
+ "item's parent menu should be correct");
+
+ test.assertEqual(menu.items.length, 2,
+ "menu should have correct number of items");
+ test.assertEqual(menu.items[0].label, "item 0",
+ "item label should be correct");
+ test.assertEqual(menu.items[1].label, "item 2",
+ "item label should be correct");
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([menu], [], []);
+ test.done();
+ });
+};
+
+
+// Adding an item currently contained in one menu to another menu should work.
+exports.testMenuItemSwap = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let subitem = loader.cm.Item({ label: "item" });
+ let menu0 = loader.cm.Menu({
+ label: "menu 0",
+ items: [subitem]
+ });
+ let menu1 = loader.cm.Menu({
+ label: "menu 1",
+ items: []
+ });
+ menu1.addItem(subitem);
+
+ test.assertEqual(menu0.items.length, 0,
+ "menu should have correct number of items");
+
+ test.assertEqual(menu1.items.length, 1,
+ "menu should have correct number of items");
+ test.assertEqual(menu1.items[0].label, "item",
+ "item label should be correct");
+
+ test.assertEqual(subitem.parentMenu, menu1,
+ "item's parent menu should be correct");
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([menu0, menu1], [], []);
+ test.done();
+ });
+};
+
+
+// Destroying an item should remove it from its parent menu.
+exports.testMenuItemDestroy = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let subitem = loader.cm.Item({ label: "item" });
+ let menu = loader.cm.Menu({
+ label: "menu",
+ items: [subitem]
+ });
+ subitem.destroy();
+
+ test.assertEqual(menu.items.length, 0,
+ "menu should have correct number of items");
+ test.assertEqual(subitem.parentMenu, null,
+ "item's parent menu should be correct");
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([menu], [], []);
+ test.done();
+ });
+};
+
+
+// Setting Menu.items should work.
+exports.testMenuItemsSetter = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let menu = loader.cm.Menu({
+ label: "menu",
+ items: [
+ loader.cm.Item({ label: "old item 0" }),
+ loader.cm.Item({ label: "old item 1" })
+ ]
+ });
+ menu.items = [
+ loader.cm.Item({ label: "new item 0" }),
+ loader.cm.Item({ label: "new item 1" }),
+ loader.cm.Item({ label: "new item 2" })
+ ];
+
+ test.assertEqual(menu.items.length, 3,
+ "menu should have correct number of items");
+ for (let i = 0; i < 3; i++) {
+ test.assertEqual(menu.items[i].label, "new item " + i,
+ "item label should be correct");
+ test.assertEqual(menu.items[i].parentMenu, menu,
+ "item's parent menu should be correct");
+ }
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([menu], [], []);
+ test.done();
+ });
+};
+
+
+// Setting Item.data should work.
+exports.testItemDataSetter = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = loader.cm.Item({ label: "old item 0", data: "old" });
+ item.data = "new";
+
+ test.assertEqual(item.data, "new", "item should have correct data");
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([item], [], []);
+ test.done();
+ });
+};
+
+
+// Open the test doc, load the module, make sure items appear when context-
+// clicking the iframe.
+exports.testAlreadyOpenIframe = function (test) {
+ test = new TestHelper(test);
+ test.withTestDoc(function (window, doc) {
+ let loader = test.newLoader();
+ let item = new loader.cm.Item({
+ label: "item"
+ });
+ test.showMenu(doc.getElementById("iframe"), function (popup) {
+ test.checkMenu([item], [], []);
+ test.done();
+ });
+ });
+};
+
+
+// Test image support.
+exports.testItemImage = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let imageURL = require("self").data.url("moz_favicon.ico");
+ let item = new loader.cm.Item({ label: "item", image: imageURL });
+ let menu = new loader.cm.Menu({ label: "menu", image: imageURL, items: [] });
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([item, menu], [], []);
+
+ let imageURL2 = require("self").data.url("dummy.ico");
+ item.image = imageURL2;
+ menu.image = imageURL2;
+ test.checkMenu([item, menu], [], []);
+
+ item.image = null;
+ menu.image = null;
+ test.checkMenu([item, menu], [], []);
+
+ test.done();
+ });
+};
+
+
+// Menu.destroy should destroy the item tree rooted at that menu.
+exports.testMenuDestroy = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let menu = loader.cm.Menu({
+ label: "menu",
+ items: [
+ loader.cm.Item({ label: "item 0" }),
+ loader.cm.Menu({
+ label: "item 1",
+ items: [
+ loader.cm.Item({ label: "subitem 0" }),
+ loader.cm.Item({ label: "subitem 1" }),
+ loader.cm.Item({ label: "subitem 2" })
+ ]
+ }),
+ loader.cm.Item({ label: "item 2" })
+ ]
+ });
+ menu.destroy();
+
+ let numRegistryEntries = 0;
+ loader.globalScope.browserManager.browserWins.forEach(function (bwin) {
+ for (let itemID in bwin.items)
+ numRegistryEntries++;
+ });
+ test.assertEqual(numRegistryEntries, 0, "All items should be unregistered.");
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([], [], [menu]);
+ test.done();
+ });
+};
+
+
+// NO TESTS BELOW THIS LINE! ///////////////////////////////////////////////////
+
+// Run only a dummy test if context-menu doesn't support the host app.
+if (!require("xul-app").is("Firefox")) {
+ for (let [prop, val] in Iterator(exports))
+ if (/^test/.test(prop) && typeof(val) === "function")
+ delete exports[prop];
+ exports.testAppNotSupported = function (test) {
+ test.pass("context-menu does not support this application.");
+ };
+}
+
+
+// This makes it easier to run tests by handling things like opening the menu,
+// opening new windows, making assertions, etc. Methods on |test| can be called
+// on instances of this class. Don't forget to call done() to end the test!
+// WARNING: This looks up items in popups by comparing labels, so don't give two
+// items the same label.
+function TestHelper(test) {
+ // default waitUntilDone timeout is 10s, which is too short on the win7
+ // buildslave
+ test.waitUntilDone(30*1000);
+ this.test = test;
+ this.loaders = [];
+ this.browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator).
+ getMostRecentWindow("navigator:browser");
+}
+
+TestHelper.prototype = {
+ get contextMenuPopup() {
+ return this.browserWindow.document.getElementById("contentAreaContextMenu");
+ },
+
+ get contextMenuSeparator() {
+ return this.browserWindow.document.getElementById(SEPARATOR_ID);
+ },
+
+ get overflowPopup() {
+ return this.browserWindow.document.getElementById(OVERFLOW_POPUP_ID);
+ },
+
+ get overflowSubmenu() {
+ return this.browserWindow.document.getElementById(OVERFLOW_MENU_ID);
+ },
+
+ get tabBrowser() {
+ return this.browserWindow.gBrowser;
+ },
+
+ // Methods on the wrapped test can be called on this object.
+ __noSuchMethod__: function (methodName, args) {
+ this.test[methodName].apply(this.test, args);
+ },
+
+ // Asserts that absentItems -- an array of items that should not match the
+ // current context -- aren't present in the menu.
+ checkAbsentItems: function (presentItems, absentItems) {
+ for (let i = 0; i < absentItems.length; i++) {
+ let item = absentItems[i];
+ let elt = this.getItemElt(this.contextMenuPopup, item);
+
+ // The implementation actually hides items rather than removing or not
+ // adding them in the first place, but that's an implementation detail.
+ this.test.assert(!elt || elt.hidden,
+ "Item should not be present in top-level menu");
+
+ if (this.shouldOverflow(presentItems)) {
+ elt = getItemElt(this.overflowPopup, item);
+ this.test.assert(!elt || elt.hidden,
+ "Item should not be present in overflow submenu");
+ }
+ }
+ },
+
+ // Asserts that elt, a DOM element representing item, looks OK.
+ checkItemElt: function (elt, item) {
+ let itemType = this.getItemType(item);
+
+ switch (itemType) {
+ case "Item":
+ this.test.assertEqual(elt.localName, "menuitem",
+ "Item DOM element should be a xul:menuitem");
+ if (typeof(item.data) === "string") {
+ this.test.assertEqual(elt.getAttribute("value"), item.data,
+ "Item should have correct data");
+ }
+ break
+ case "Menu":
+ this.test.assertEqual(elt.localName, "menu",
+ "Menu DOM element should be a xul:menu");
+ let subPopup = elt.firstChild;
+ this.test.assert(subPopup, "xul:menu should have a child");
+ this.test.assertEqual(subPopup.localName, "menupopup",
+ "xul:menu's first child should be a menupopup");
+ break;
+ case "Separator":
+ this.test.assertEqual(elt.localName, "menuseparator",
+ "Separator DOM element should be a xul:menuseparator");
+ break;
+ }
+
+ if (itemType === "Item" || itemType === "Menu") {
+ this.test.assertEqual(elt.getAttribute("label"), item.label,
+ "Item should have correct title");
+ if (typeof(item.image) === "string")
+ this.test.assertEqual(elt.getAttribute("image"), item.image,
+ "Item should have correct image");
+ else
+ this.test.assert(!elt.hasAttribute("image"),
+ "Item should not have image");
+ }
+ },
+
+ // Asserts that the context menu looks OK given the arguments. presentItems
+ // are items that should match the current context. absentItems are items
+ // that shouldn't. removedItems are items that have been removed from the
+ // menu.
+ checkMenu: function (presentItems, absentItems, removedItems) {
+ this.checkSeparator(presentItems);
+ this.checkOverflow(presentItems);
+ this.checkPresentItems(presentItems);
+ this.checkAbsentItems(presentItems, absentItems);
+ this.checkRemovedItems(removedItems);
+ this.checkSort(presentItems);
+ },
+
+ // Asserts that the overflow submenu is present or absent as appropriate for
+ // presentItems.
+ checkOverflow: function (presentItems) {
+ let submenu = this.overflowSubmenu;
+ if (this.shouldOverflow(presentItems)) {
+ this.test.assert(submenu && !submenu.hidden,
+ "Overflow submenu should be present");
+ this.test.assert(submenu.localName, "menu",
+ "Overflow submenu should be a <menu>");
+ let overflowPopup = this.overflowPopup;
+ this.test.assert(overflowPopup,
+ "Overflow submenu popup should be present");
+ this.test.assert(overflowPopup.localName, "menupopup",
+ "Overflow submenu popup should be a <menupopup>");
+ }
+ else {
+ this.test.assert(!submenu || submenu.hidden,
+ "Overflow submenu should be absent");
+ }
+ },
+
+ // Asserts that the items that are present in the menu because they match the
+ // current context look OK.
+ checkPresentItems: function (presentItems) {
+ function recurse(popup, items, isTopLevel) {
+ items.forEach(function (item) {
+ let elt = this.getItemElt(popup, item);
+
+ if (isTopLevel) {
+ if (this.shouldOverflow(items)) {
+ this.test.assert(!elt || elt.hidden,
+ "Item should not be present in top-level menu");
+
+ let overflowPopup = this.overflowPopup;
+ this.test.assert(overflowPopup,
+ "Overflow submenu should be present");
+
+ elt = this.getItemElt(overflowPopup, item);
+ this.test.assert(elt && !elt.hidden,
+ "Item should be present in overflow submenu");
+ }
+ else {
+ this.test.assert(elt && !elt.hidden,
+ "Item should be present in top-level menu");
+ }
+ }
+ else {
+ this.test.assert(elt && !elt.hidden,
+ "Item should be present in menu");
+ }
+
+ this.checkItemElt(elt, item);
+ if (this.getItemType(item) === "Menu")
+ recurse.call(this, elt.firstChild, item.items, false);
+ }, this);
+ }
+
+ recurse.call(this, this.contextMenuPopup, presentItems, true);
+ },
+
+ // Asserts that items that have been removed from the menu are really removed.
+ checkRemovedItems: function (removedItems) {
+ for (let i = 0; i < removedItems.length; i++) {
+ let item = removedItems[i];
+
+ let elt = this.getItemElt(this.contextMenuPopup, item);
+ this.test.assert(!elt, "Item should be removed from top-level menu");
+
+ let overflowPopup = this.overflowPopup;
+ if (overflowPopup) {
+ elt = this.getItemElt(overflowPopup, item);
+ this.test.assert(!elt, "Item should be removed from overflow submenu");
+ }
+ }
+ },
+
+ // Asserts that the menu separator separating standard items from our items
+ // looks OK.
+ checkSeparator: function (presentItems) {
+ let sep = this.contextMenuSeparator;
+ if (presentItems.length) {
+ this.test.assert(sep && !sep.hidden, "Menu separator should be present");
+ this.test.assertEqual(sep.localName, "menuseparator",
+ "Menu separator should be a <menuseparator>");
+ }
+ else {
+ this.test.assert(!sep || sep.hidden, "Menu separator should be absent");
+ }
+ },
+
+ // Asserts that our items are sorted.
+ checkSort: function (presentItems) {
+ // Get the first item in sorted order, get its elt, walk the nextSibling
+ // chain, making sure each is greater than the previous.
+ if (presentItems.length) {
+ let sorted = presentItems.slice(0).
+ sort(function (a, b) a.label.localeCompare(b.label));
+ let elt = this.shouldOverflow(presentItems) ?
+ this.getItemElt(this.overflowPopup, sorted[0]) :
+ this.getItemElt(this.contextMenuPopup, sorted[0]);
+ let numElts = 1;
+ while (elt.nextSibling &&
+ elt.nextSibling.className.split(/\s+/).indexOf(ITEM_CLASS) >= 0) {
+ let eltLabel = elt.getAttribute("label");
+ let nextLabel = elt.nextSibling.getAttribute("label");
+ this.test.assert(eltLabel.localeCompare(nextLabel) < 0,
+ "Item label should be < next item's label");
+ elt = elt.nextSibling;
+ numElts++;
+ }
+ this.test.assertEqual(numElts, presentItems.length,
+ "The first item in sorted order should have the " +
+ "first element in sorted order");
+ }
+ },
+
+ // Attaches an event listener to node. The listener is automatically removed
+ // when it's fired (so it's assumed it will fire), and callback is called
+ // after a short delay. Since the module we're testing relies on the same
+ // event listeners to do its work, this is to give them a little breathing
+ // room before callback runs. Inside callback |this| is this object.
+ delayedEventListener: function (node, event, callback, useCapture) {
+ const self = this;
+ node.addEventListener(event, function handler(evt) {
+ node.removeEventListener(event, handler, useCapture);
+ require("timer").setTimeout(function () {
+ try {
+ callback.call(self, evt);
+ }
+ catch (err) {
+ self.test.exception(err);
+ self.test.done();
+ }
+ }, 20);
+ }, useCapture);
+ },
+
+ // Call to finish the test.
+ done: function () {
+ function commonDone() {
+ if (this.tab) {
+ this.tabBrowser.removeTab(this.tab);
+ this.tabBrowser.selectedTab = this.oldSelectedTab;
+ }
+ while (this.loaders.length) {
+ let browserManager = this.loaders[0].globalScope.browserManager;
+ let topLevelItems = browserManager.topLevelItems.slice();
+ let privatePropsKey = this.loaders[0].globalScope.PRIVATE_PROPS_KEY;
+ let workerRegs = topLevelItems.map(function (item) {
+ return item.valueOf(privatePropsKey)._workerReg;
+ });
+
+ this.loaders[0].unload();
+
+ // Make sure the browser manager is cleaned up.
+ this.test.assertEqual(browserManager.browserWins.length, 0,
+ "browserManager should have no windows left");
+ this.test.assertEqual(browserManager.topLevelItems.length, 0,
+ "browserManager should have no items left");
+ this.test.assert(!("contentWins" in browserManager),
+ "browserManager should have no content windows left");
+
+ // Make sure the items' worker registries are cleaned up.
+ topLevelItems.forEach(function (item) {
+ this.test.assert(!("_workerReg" in item.valueOf(privatePropsKey)),
+ "item's worker registry should be removed");
+ }, this);
+ workerRegs.forEach(function (workerReg) {
+ this.test.assertEqual(Object.keys(workerReg.winWorkers).length, 0,
+ "worker registry should be empty");
+ this.test.assertEqual(
+ Object.keys(workerReg.winsWithoutWorkers).length, 0,
+ "worker registry list of windows without workers should be empty");
+ }, this);
+ }
+ this.test.done();
+ }
+
+ function closeBrowserWindow() {
+ if (this.oldBrowserWindow) {
+ this.delayedEventListener(this.browserWindow, "unload", commonDone,
+ false);
+ this.browserWindow.close();
+ this.browserWindow = this.oldBrowserWindow;
+ delete this.oldBrowserWindow;
+ }
+ else {
+ commonDone.call(this);
+ }
+ };
+
+ if (this.contextMenuPopup.state == "closed") {
+ closeBrowserWindow.call(this);
+ }
+ else {
+ this.delayedEventListener(this.contextMenuPopup, "popuphidden",
+ function () closeBrowserWindow.call(this),
+ false);
+ this.contextMenuPopup.hidePopup();
+ }
+ },
+
+ // Returns the DOM element in popup corresponding to item.
+ // WARNING: The element is found by comparing labels, so don't give two items
+ // the same label.
+ getItemElt: function (popup, item) {
+ let nodes = popup.childNodes;
+ for (let i = nodes.length - 1; i >= 0; i--) {
+ if (this.getItemType(item) === "Separator") {
+ if (nodes[i].localName === "menuseparator")
+ return nodes[i];
+ }
+ else if (nodes[i].getAttribute("label") === item.label)
+ return nodes[i];
+ }
+ return null;
+ },
+
+ // Returns "Item", "Menu", or "Separator".
+ getItemType: function (item) {
+ // Could use instanceof here, but that would require accessing the loader
+ // that created the item, and I don't want to A) somehow search through the
+ // this.loaders list to find it, and B) assume there are any live loaders at
+ // all.
+ return /^\[object (Item|Menu|Separator)/.exec(item.toString())[1];
+ },
+
+ // Returns a wrapper around a new loader: { loader, cm, unload, globalScope }.
+ // loader is a Cuddlefish sandboxed loader, cm is the context menu module,
+ // globalScope is the context menu module's global scope, and unload is a
+ // function that unloads the loader and associated resources.
+ newLoader: function () {
+ const self = this;
+ let loader = this.test.makeSandboxedLoader({
+ globals: { packaging: packaging }
+ });
+ let wrapper = {
+ loader: loader,
+ cm: loader.require("context-menu"),
+ globalScope: loader.findSandboxForModule("context-menu").globalScope,
+ unload: function () {
+ loader.unload();
+ let idx = self.loaders.indexOf(wrapper);
+ if (idx < 0)
+ throw new Error("Test error: tried to unload nonexistent loader");
+ self.loaders.splice(idx, 1);
+ }
+ };
+ this.loaders.push(wrapper);
+ return wrapper;
+ },
+
+ // Returns true if the number of presentItems crosses the overflow threshold.
+ shouldOverflow: function (presentItems) {
+ return presentItems.length >
+ (this.loaders.length ?
+ this.loaders[0].loader.require("preferences-service").
+ get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT) :
+ OVERFLOW_THRESH_DEFAULT);
+ },
+
+ // Opens the context menu on the current page. If targetNode is null, the
+ // menu is opened in the top-left corner. onShowncallback is passed the
+ // popup.
+ showMenu: function(targetNode, onshownCallback) {
+ function sendEvent() {
+ this.delayedEventListener(this.browserWindow, "popupshowing",
+ function (e) {
+ let popup = e.target;
+ onshownCallback.call(this, popup);
+ }, false);
+
+ let rect = targetNode ?
+ targetNode.getBoundingClientRect() :
+ { left: 0, top: 0, width: 0, height: 0 };
+ let contentWin = this.browserWindow.content;
+ contentWin.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).
+ sendMouseEvent("contextmenu",
+ rect.left + (rect.width / 2),
+ rect.top + (rect.height / 2),
+ 2, 1, 0);
+ }
+
+ // If a new tab or window has not yet been opened, open a new tab now. For
+ // some reason using the tab already opened when the test starts causes
+ // leaks. See bug 566351 for details.
+ if (!targetNode && !this.oldSelectedTab && !this.oldBrowserWindow) {
+ this.oldSelectedTab = this.tabBrowser.selectedTab;
+ this.tab = this.tabBrowser.addTab("about:blank");
+ let browser = this.tabBrowser.getBrowserForTab(this.tab);
+
+ this.delayedEventListener(browser, "load", function () {
+ this.tabBrowser.selectedTab = this.tab;
+ sendEvent.call(this);
+ }, true);
+ }
+ else
+ sendEvent.call(this);
+ },
+
+ // Opens a new browser window. The window will be closed automatically when
+ // done() is called.
+ withNewWindow: function (onloadCallback) {
+ let win = this.browserWindow.OpenBrowserWindow();
+ this.delayedEventListener(win, "load", onloadCallback, true);
+ this.oldBrowserWindow = this.browserWindow;
+ this.browserWindow = win;
+ },
+
+ // Opens a new tab with our test page in the current window. The tab will
+ // be closed automatically when done() is called.
+ withTestDoc: function (onloadCallback) {
+ this.oldSelectedTab = this.tabBrowser.selectedTab;
+ this.tab = this.tabBrowser.addTab(TEST_DOC_URL);
+ let browser = this.tabBrowser.getBrowserForTab(this.tab);
+
+ this.delayedEventListener(browser, "load", function () {
+ this.tabBrowser.selectedTab = this.tab;
+ onloadCallback.call(this, browser.contentWindow, browser.contentDocument);
+ }, true);
+ }
+};
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/test-hotkeys.js b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-hotkeys.js
new file mode 100644
index 0000000..b4c6b37
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-hotkeys.js
@@ -0,0 +1,155 @@
+"use strict";
+
+const { Hotkey } = require("hotkeys");
+const { keyDown } = require("dom/events/keys");
+
+exports["test hotkey: function key"] = function(assert, done) {
+ var element = require("window-utils").activeBrowserWindow.document.documentElement;
+ var showHotKey = Hotkey({
+ combo: "f1",
+ onPress: function() {
+ assert.pass("first callback is called");
+ keyDown(element, "f2");
+ showHotKey.destroy();
+ }
+ });
+
+ var hideHotKey = Hotkey({
+ combo: "f2",
+ onPress: function() {
+ assert.pass("second callback is called");
+ hideHotKey.destroy();
+ done();
+ }
+ });
+
+ keyDown(element, "f1");
+};
+
+exports["test hotkey: accel alt shift"] = function(assert, done) {
+ var element = require("window-utils").activeBrowserWindow.document.documentElement;
+ var showHotKey = Hotkey({
+ combo: "accel-shift-6",
+ onPress: function() {
+ assert.pass("first callback is called");
+ keyDown(element, "accel-alt-shift-6");
+ showHotKey.destroy();
+ }
+ });
+
+ var hideHotKey = Hotkey({
+ combo: "accel-alt-shift-6",
+ onPress: function() {
+ assert.pass("second callback is called");
+ hideHotKey.destroy();
+ done();
+ }
+ });
+
+ keyDown(element, "accel-shift-6");
+};
+
+exports["test hotkey meta & control"] = function(assert, done) {
+ var element = require("window-utils").activeBrowserWindow.document.documentElement;
+ var showHotKey = Hotkey({
+ combo: "meta-3",
+ onPress: function() {
+ assert.pass("first callback is called");
+ keyDown(element, "alt-control-shift-b");
+ showHotKey.destroy();
+ }
+ });
+
+ var hideHotKey = Hotkey({
+ combo: "Ctrl-Alt-Shift-B",
+ onPress: function() {
+ assert.pass("second callback is called");
+ hideHotKey.destroy();
+ done();
+ }
+ });
+
+ keyDown(element, "meta-3");
+};
+
+exports["test hotkey: control-1 / meta--"] = function(assert, done) {
+ var element = require("window-utils").activeBrowserWindow.document.documentElement;
+ var showHotKey = Hotkey({
+ combo: "control-1",
+ onPress: function() {
+ assert.pass("first callback is called");
+ keyDown(element, "meta--");
+ showHotKey.destroy();
+ }
+ });
+
+ var hideHotKey = Hotkey({
+ combo: "meta--",
+ onPress: function() {
+ assert.pass("second callback is called");
+ hideHotKey.destroy();
+ done();
+ }
+ });
+
+ keyDown(element, "control-1");
+};
+
+exports["test invalid combos"] = function(assert) {
+ assert.throws(function() {
+ Hotkey({
+ combo: "d",
+ onPress: function() {}
+ });
+ }, "throws if no modifier is present");
+ assert.throws(function() {
+ Hotkey({
+ combo: "alt",
+ onPress: function() {}
+ });
+ }, "throws if no key is present");
+ assert.throws(function() {
+ Hotkey({
+ combo: "alt p b",
+ onPress: function() {}
+ });
+ }, "throws if more then one key is present");
+};
+
+exports["test no exception on unmodified keypress"] = function(assert) {
+ var element = require("window-utils").activeBrowserWindow.document.documentElement;
+ var someHotkey = Hotkey({
+ combo: "control-alt-1",
+ onPress: function() {
+ }
+ });
+ keyDown(element, "a");
+ assert.pass("No exception throw, unmodified keypress passed");
+};
+
+exports["test hotkey: automatic destroy"] = function(assert, done) {
+ // Hacky way to be able to create unloadable modules via makeSandboxedLoader.
+ let loader = assert._log.makeSandboxedLoader();
+
+ var called = false;
+ var element = loader.require("window-utils").activeBrowserWindow.document.documentElement;
+ var hotkey = loader.require("hotkeys").Hotkey({
+ combo: "accel-shift-x",
+ onPress: function() {
+ called = true;
+ }
+ });
+
+ // Unload the module so that previous hotkey is automatically destroyed
+ loader.unload();
+
+ // Ensure that the hotkey is really destroyed
+ keyDown(element, "accel-shift-x");
+
+ require("timer").setTimeout(function () {
+ assert.ok(!called, "Hotkey is destroyed and not called.");
+ done();
+ }, 0);
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/test-module.js b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-module.js
new file mode 100644
index 0000000..c62f923
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-module.js
@@ -0,0 +1,33 @@
+"use strict";
+
+/** Disabled because of Bug 672199
+exports["test module exports are frozen"] = function(assert) {
+ assert.ok(Object.isFrozen(require("addon-kit/hotkeys")),
+ "module exports are frozen");
+};
+
+exports["test redefine exported property"] = function(assert) {
+ let hotkeys = require("addon-kit/hotkeys");
+ let { Hotkey } = hotkeys;
+ try { Object.defineProperty(hotkeys, 'Hotkey', { value: {} }); } catch(e) {}
+ assert.equal(hotkeys.Hotkey, Hotkey, "exports can't be redefined");
+};
+*/
+
+exports["test can't delete exported property"] = function(assert) {
+ let hotkeys = require("addon-kit/hotkeys");
+ let { Hotkey } = hotkeys;
+
+ try { delete hotkeys.Hotkey; } catch(e) {}
+ assert.equal(hotkeys.Hotkey, Hotkey, "exports can't be deleted");
+};
+
+exports["test can't override exported property"] = function(assert) {
+ let hotkeys = require("addon-kit/hotkeys");
+ let { Hotkey } = hotkeys;
+
+ try { hotkeys.Hotkey = Object } catch(e) {}
+ assert.equal(hotkeys.Hotkey, Hotkey, "exports can't be overriden");
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/test-notifications.js b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-notifications.js
new file mode 100644
index 0000000..9d20822
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-notifications.js
@@ -0,0 +1,76 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et 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 ***** */
+
+exports.testOnClick = function (test) {
+ let [loader, mockAlertServ] = makeLoader(test);
+ let notifs = loader.require("notifications");
+ let data = "test data";
+ let opts = {
+ onClick: function (clickedData) {
+ test.assertEqual(this, notifs, "|this| should be notifications module");
+ test.assertEqual(clickedData, data,
+ "data passed to onClick should be correct");
+ },
+ data: data,
+ title: "test title",
+ text: "test text",
+ iconURL: "test icon URL"
+ };
+ notifs.notify(opts);
+ mockAlertServ.click();
+ loader.unload();
+};
+
+// Returns [loader, mockAlertService].
+function makeLoader(test) {
+ let loader = test.makeSandboxedLoader();
+ let mockAlertServ = {
+ showAlertNotification: function (imageUrl, title, text, textClickable,
+ cookie, alertListener, name) {
+ this._cookie = cookie;
+ this._alertListener = alertListener;
+ },
+ click: function () {
+ this._alertListener.observe(null, "alertclickcallback", this._cookie);
+ }
+ };
+ let scope = loader.findSandboxForModule("notifications").globalScope;
+ scope.notify = mockAlertServ.showAlertNotification.bind(mockAlertServ);
+ return [loader, mockAlertServ];
+};
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/test-page-mod.js b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-page-mod.js
new file mode 100644
index 0000000..68f0a95
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-page-mod.js
@@ -0,0 +1,380 @@
+"use strict";
+
+var pageMod = require("page-mod");
+var testPageMod = require("pagemod-test-helpers").testPageMod;
+
+/* XXX This can be used to delay closing the test Firefox instance for interactive
+ * testing or visual inspection. This test is registered first so that it runs
+ * the last. */
+exports.delay = function(test) {
+ if (false) {
+ test.waitUntilDone(60000);
+ require("timer").setTimeout(function() {test.done();}, 4000);
+ } else
+ test.pass();
+}
+
+/* Tests for the PageMod APIs */
+
+exports.testPageMod1 = function(test) {
+ let mods = testPageMod(test, "about:", [{
+ include: /about:/,
+ contentScriptWhen: 'end',
+ contentScript: 'new ' + function WorkerScope() {
+ window.document.body.setAttribute("JEP-107", "worked");
+ },
+ onAttach: function() {
+ test.assertEqual(this, mods[0], "The 'this' object is the page mod.");
+ }
+ }],
+ function(win, done) {
+ test.assertEqual(
+ win.document.body.getAttribute("JEP-107"),
+ "worked",
+ "PageMod.onReady test"
+ );
+ done();
+ }
+ );
+};
+
+exports.testPageMod2 = function(test) {
+ testPageMod(test, "about:", [{
+ include: "about:*",
+ contentScript: [
+ 'new ' + function contentScript() {
+ window.AUQLUE = function() { return 42; }
+ try {
+ window.AUQLUE()
+ }
+ catch(e) {
+ throw new Error("PageMod scripts executed in order");
+ }
+ document.documentElement.setAttribute("first", "true");
+ },
+ 'new ' + function contentScript() {
+ document.documentElement.setAttribute("second", "true");
+ }
+ ]
+ }], function(win, done) {
+ test.assertEqual(win.document.documentElement.getAttribute("first"),
+ "true",
+ "PageMod test #2: first script has run");
+ test.assertEqual(win.document.documentElement.getAttribute("second"),
+ "true",
+ "PageMod test #2: second script has run");
+ test.assertEqual("AUQLUE" in win, false,
+ "PageMod test #2: scripts get a wrapped window");
+ done();
+ });
+};
+
+exports.testPageModIncludes = function(test) {
+ var asserts = [];
+ function createPageModTest(include, expectedMatch) {
+ // Create an 'onload' test function...
+ asserts.push(function(test, win) {
+ var matches = include in win.localStorage;
+ test.assert(expectedMatch ? matches : !matches,
+ "'" + include + "' match test, expected: " + expectedMatch);
+ });
+ // ...and corresponding PageMod options
+ return {
+ include: include,
+ contentScript: 'new ' + function() {
+ self.on("message", function(msg) {
+ window.localStorage[msg] = true;
+ });
+ },
+ // The testPageMod callback with test assertions is called on 'end',
+ // and we want this page mod to be attached before it gets called,
+ // so we attach it on 'start'.
+ contentScriptWhen: 'start',
+ onAttach: function(worker) {
+ worker.postMessage(this.include[0]);
+ }
+ };
+ }
+
+ testPageMod(test, "about:buildconfig", [
+ createPageModTest("*", false),
+ createPageModTest("*.google.com", false),
+ createPageModTest("about:*", true),
+ createPageModTest("about:", false),
+ createPageModTest("about:buildconfig", true)
+ ],
+ function (win, done) {
+ test.waitUntil(function () win.localStorage["about:buildconfig"],
+ "about:buildconfig page-mod to be executed")
+ .then(function () {
+ asserts.forEach(function(fn) {
+ fn(test, win);
+ });
+ done();
+ });
+ }
+ );
+};
+
+exports.testPageModErrorHandling = function(test) {
+ test.assertRaises(function() {
+ new pageMod.PageMod();
+ },
+ 'pattern is undefined',
+ "PageMod() throws when 'include' option is not specified.");
+};
+
+/* Tests for internal functions. */
+exports.testCommunication1 = function(test) {
+ let workerDone = false,
+ callbackDone = null;
+
+ testPageMod(test, "about:", [{
+ include: "about:*",
+ contentScriptWhen: 'end',
+ contentScript: 'new ' + function WorkerScope() {
+ self.on('message', function(msg) {
+ document.body.setAttribute('JEP-107', 'worked');
+ self.postMessage(document.body.getAttribute('JEP-107'));
+ })
+ },
+ onAttach: function(worker) {
+ worker.on('error', function(e) {
+ test.fail('Errors where reported');
+ });
+ worker.on('message', function(value) {
+ test.assertEqual(
+ "worked",
+ value,
+ "test comunication"
+ );
+ workerDone = true;
+ if (callbackDone)
+ callbackDone();
+ });
+ worker.postMessage('do it!')
+ }
+ }],
+ function(win, done) {
+ (callbackDone = function() {
+ if (workerDone) {
+ test.assertEqual(
+ 'worked',
+ win.document.body.getAttribute('JEP-107'),
+ 'attribute should be modified'
+ );
+ done();
+ }
+ })();
+ }
+ );
+};
+
+exports.testCommunication2 = function(test) {
+ let callbackDone = null,
+ window;
+
+ testPageMod(test, "about:", [{
+ include: "about:*",
+ contentScriptWhen: 'start',
+ contentScript: 'new ' + function WorkerScope() {
+ document.documentElement.setAttribute('AUQLUE', 42);
+ window.addEventListener('load', function listener() {
+ self.postMessage('onload');
+ }, false);
+ self.on("message", function() {
+ self.postMessage(document.documentElement.getAttribute("test"))
+ });
+ },
+ onAttach: function(worker) {
+ worker.on('error', function(e) {
+ test.fail('Errors where reported');
+ });
+ worker.on('message', function(msg) {
+ if ('onload' == msg) {
+ test.assertEqual(
+ '42',
+ window.document.documentElement.getAttribute('AUQLUE'),
+ 'PageMod scripts executed in order'
+ );
+ window.document.documentElement.setAttribute('test', 'changes in window');
+ worker.postMessage('get window.test')
+ } else {
+ test.assertEqual(
+ 'changes in window',
+ msg,
+ 'PageMod test #2: second script has run'
+ )
+ callbackDone();
+ }
+ });
+ }
+ }],
+ function(win, done) {
+ window = win;
+ callbackDone = done;
+ }
+ );
+};
+
+exports.testEventEmitter = function(test) {
+ let workerDone = false,
+ callbackDone = null;
+
+ testPageMod(test, "about:", [{
+ include: "about:*",
+ contentScript: 'new ' + function WorkerScope() {
+ self.port.on('addon-to-content', function(data) {
+ self.port.emit('content-to-addon', data);
+ });
+ },
+ onAttach: function(worker) {
+ worker.on('error', function(e) {
+ test.fail('Errors were reported : '+e);
+ });
+ worker.port.on('content-to-addon', function(value) {
+ test.assertEqual(
+ "worked",
+ value,
+ "EventEmitter API works!"
+ );
+ if (callbackDone)
+ callbackDone();
+ else
+ workerDone = true;
+ });
+ worker.port.emit('addon-to-content', 'worked');
+ }
+ }],
+ function(win, done) {
+ if (workerDone)
+ done();
+ else
+ callbackDone = done;
+ }
+ );
+};
+
+exports.testHistory = function(test) {
+ // We need a valid url in order to have a working History API.
+ // (i.e do not work on data: or about: pages)
+ // Test bug 679054.
+ let url = require("self").data.url("test-page-mod.html");
+ let callbackDone = null;
+ testPageMod(test, url, [{
+ include: url,
+ contentScriptWhen: 'end',
+ contentScript: 'new ' + function WorkerScope() {
+ history.pushState({}, "", "#");
+ history.replaceState({foo: "bar"}, "", "#");
+ self.postMessage(history.state);
+ },
+ onAttach: function(worker) {
+ worker.on('message', function (data) {
+ test.assertEqual(JSON.stringify(data), JSON.stringify({foo: "bar"}),
+ "History API works!");
+ callbackDone();
+ });
+ }
+ }],
+ function(win, done) {
+ callbackDone = done;
+ }
+ );
+};
+
+exports.testRelatedTab = function(test) {
+ test.waitUntilDone();
+
+ let tabs = require("tabs");
+ let tab;
+ let pageMod = new require("page-mod").PageMod({
+ include: "about:*",
+ onAttach: function(worker) {
+ test.assertEqual(tab, worker.tab, "Worker.tab is valid");
+ pageMod.destroy();
+ tab.close();
+ test.done();
+ }
+ });
+
+ tabs.open({
+ url: "about:",
+ onOpen: function onOpen(t) {
+ tab = t;
+ }
+ });
+
+};
+
+exports['test tab worker on message'] = function(test) {
+ test.waitUntilDone();
+
+ let { browserWindows } = require("windows");
+ let tabs = require("tabs");
+ let { PageMod } = require("page-mod");
+
+ let url1 = "data:text/html,<title>tab1</title><h1>worker1.tab</h1>";
+ let url2 = "data:text/html,<title>tab2</title><h1>worker2.tab</h1>";
+ let worker1 = null;
+
+ let mod = PageMod({
+ include: "data:text/html,*",
+ contentScriptWhen: "ready",
+ contentScript: "self.postMessage('#1');",
+ onAttach: function onAttach(worker) {
+ worker.on("message", function onMessage() {
+ this.tab.attach({
+ contentScriptWhen: "ready",
+ contentScript: "self.postMessage({ url: window.location.href, title: document.title });",
+ onMessage: function onMessage(data) {
+ test.assertEqual(this.tab.url, data.url, "location is correct");
+ test.assertEqual(this.tab.title, data.title, "title is correct");
+ if (this.tab.url === url1) {
+ worker1 = this;
+ tabs.open({ url: url2, inBackground: true });
+ }
+ else if (this.tab.url === url2) {
+ mod.destroy();
+ worker1.tab.close();
+ worker1.destroy();
+ worker.tab.close();
+ worker.destroy();
+ test.done();
+ }
+ }
+ });
+ });
+ }
+ });
+
+ tabs.open(url1);
+};
+
+exports.testAutomaticDestroy = function(test) {
+ test.waitUntilDone();
+ let loader = test.makeSandboxedLoader();
+
+ let pageMod = loader.require("page-mod").PageMod({
+ include: "about:*",
+ contentScriptWhen: "start",
+ onAttach: function(w) {
+ test.fail("Page-mod should have been detroyed during module unload");
+ }
+ });
+
+ // Unload the page-mod module so that our page mod is destroyed
+ loader.unload();
+
+ // Then create a second tab to ensure that it is correctly destroyed
+ let tabs = require("tabs");
+ tabs.open({
+ url: "about:",
+ onReady: function onReady(tab) {
+ test.pass("check automatic destroy");
+ tab.close();
+ test.done();
+ }
+ });
+
+}
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/test-page-worker.js b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-page-worker.js
new file mode 100644
index 0000000..8ea0ad4
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-page-worker.js
@@ -0,0 +1,361 @@
+let tests = {}, Pages, Page;
+
+const ERR_DESTROYED =
+ "The page has been destroyed and can no longer be used.";
+
+tests.testSimplePageCreation = function(test) {
+ test.waitUntilDone();
+
+ let page = new Page({
+ contentScript: "self.postMessage(window.location.href)",
+ contentScriptWhen: "end",
+ onMessage: function (message) {
+ test.assertEqual(message, "about:blank",
+ "Page Worker should start with a blank page by default");
+ test.assertEqual(this, page, "The 'this' object is the page itself.");
+ test.done();
+ }
+ });
+}
+
+/*
+ * Tests that we can't be tricked by document overloads as we have access
+ * to wrapped nodes
+ */
+tests.testWrappedDOM = function(test) {
+ test.waitUntilDone();
+
+ let page = Page({
+ allow: { script: true },
+ contentURL: "data:text/html,<script>document.getElementById=3;window.scrollTo=3;</script>",
+ contentScript: "window.addEventListener('load', function () " +
+ "self.postMessage([typeof(document.getElementById), " +
+ "typeof(window.scrollTo)]), true)",
+ onMessage: function (message) {
+ test.assertEqual(message[0],
+ "function",
+ "getElementById from content script is the native one");
+
+ test.assertEqual(message[1],
+ "function",
+ "scrollTo from content script is the native one");
+
+ test.done();
+ }
+ });
+}
+
+/*
+// We do not offer unwrapped access to DOM since bug 601295 landed
+// See 660780 to track progress of unwrap feature
+tests.testUnwrappedDOM = function(test) {
+ test.waitUntilDone();
+
+ let page = Page({
+ allow: { script: true },
+ contentURL: "data:text/html,<script>document.getElementById=3;window.scrollTo=3;</script>",
+ contentScript: "window.addEventListener('load', function () " +
+ "self.postMessage([typeof(unsafeWindow.document.getElementById), " +
+ "typeof(unsafeWindow.scrollTo)]), true)",
+ onMessage: function (message) {
+ test.assertEqual(message[0],
+ "number",
+ "document inside page is free to be changed");
+
+ test.assertEqual(message[1],
+ "number",
+ "window inside page is free to be changed");
+
+ test.done();
+ }
+ });
+}
+*/
+
+tests.testPageProperties = function(test) {
+ let page = new Page();
+
+ for each (let prop in ['contentURL', 'allow', 'contentScriptFile',
+ 'contentScript', 'contentScriptWhen', 'on',
+ 'postMessage', 'removeListener']) {
+ test.assert(prop in page, prop + " property is defined on page.");
+ }
+
+ test.assert(function () page.postMessage("foo") || true,
+ "postMessage doesn't throw exception on page.");
+}
+
+tests.testConstructorAndDestructor = function(test) {
+ test.waitUntilDone();
+
+ let loader = test.makeSandboxedLoader();
+ let Pages = loader.require("page-worker");
+ let global = loader.findSandboxForModule("page-worker").globalScope;
+
+ let pagesReady = 0;
+
+ let page1 = Pages.Page({
+ contentScript: "self.postMessage('')",
+ contentScriptWhen: "end",
+ onMessage: pageReady
+ });
+ let page2 = Pages.Page({
+ contentScript: "self.postMessage('')",
+ contentScriptWhen: "end",
+ onMessage: pageReady
+ });
+
+ test.assertNotEqual(page1, page2,
+ "Page 1 and page 2 should be different objects.");
+
+ function pageReady() {
+ if (++pagesReady == 2) {
+ page1.destroy();
+ page2.destroy();
+
+ test.assert(isDestroyed(page1), "page1 correctly unloaded.");
+ test.assert(isDestroyed(page2), "page2 correctly unloaded.");
+
+ loader.unload();
+ test.done();
+ }
+ }
+}
+
+tests.testAutoDestructor = function(test) {
+ test.waitUntilDone();
+
+ let loader = test.makeSandboxedLoader();
+ let Pages = loader.require("page-worker");
+
+ let page = Pages.Page({
+ contentScript: "self.postMessage('')",
+ contentScriptWhen: "end",
+ onMessage: function() {
+ loader.unload();
+ test.assert(isDestroyed(page), "Page correctly unloaded.");
+ test.done();
+ }
+ });
+}
+
+tests.testValidateOptions = function(test) {
+ test.assertRaises(
+ function () Page({ contentURL: 'home' }),
+ "The `contentURL` option must be a valid URL.",
+ "Validation correctly denied a non-URL contentURL"
+ );
+
+ test.assertRaises(
+ function () Page({ onMessage: "This is not a function."}),
+ "The event listener must be a function.",
+ "Validation correctly denied a non-function onMessage."
+ );
+
+ test.pass("Options validation is working.");
+}
+
+tests.testContentAndAllowGettersAndSetters = function(test) {
+ test.waitUntilDone();
+ let content = "data:text/html,<script>window.localStorage.allowScript=3;</script>";
+ let page = Page({
+ contentURL: content,
+ contentScript: "self.postMessage(window.localStorage.allowScript)",
+ contentScriptWhen: "end",
+ onMessage: step0
+ });
+
+ function step0(message) {
+ test.assertEqual(message, "3",
+ "Correct value expected for allowScript - 3");
+ test.assertEqual(page.contentURL, content,
+ "Correct content expected");
+ page.removeListener('message', step0);
+ page.on('message', step1);
+ page.allow = { script: false };
+ page.contentURL = content =
+ "data:text/html,<script>window.localStorage.allowScript='f'</script>";
+ }
+
+ function step1(message) {
+ test.assertEqual(message, "3",
+ "Correct value expected for allowScript - 3");
+ test.assertEqual(page.contentURL, content, "Correct content expected");
+ page.removeListener('message', step1);
+ page.on('message', step2);
+ page.allow = { script: true };
+ page.contentURL = content =
+ "data:text/html,<script>window.localStorage.allowScript='g'</script>";
+ }
+
+ function step2(message) {
+ test.assertEqual(message, "g",
+ "Correct value expected for allowScript - g");
+ test.assertEqual(page.contentURL, content, "Correct content expected");
+ page.removeListener('message', step2);
+ page.on('message', step3);
+ page.allow.script = false;
+ page.contentURL = content =
+ "data:text/html,<script>window.localStorage.allowScript=3</script>";
+ }
+
+ function step3(message) {
+ test.assertEqual(message, "g",
+ "Correct value expected for allowScript - g");
+ test.assertEqual(page.contentURL, content, "Correct content expected");
+ page.removeListener('message', step3);
+ page.on('message', step4);
+ page.allow.script = true;
+ page.contentURL = content =
+ "data:text/html,<script>window.localStorage.allowScript=4</script>";
+ }
+
+ function step4(message) {
+ test.assertEqual(message, "4",
+ "Correct value expected for allowScript - 4");
+ test.assertEqual(page.contentURL, content, "Correct content expected");
+ test.done();
+ }
+
+}
+
+tests.testOnMessageCallback = function(test) {
+ test.waitUntilDone();
+
+ Page({
+ contentScript: "self.postMessage('')",
+ contentScriptWhen: "end",
+ onMessage: function() {
+ test.pass("onMessage callback called");
+ test.done();
+ }
+ });
+}
+
+tests.testMultipleOnMessageCallbacks = function(test) {
+ test.waitUntilDone();
+
+ let count = 0;
+ let page = Page({
+ contentScript: "self.postMessage('')",
+ contentScriptWhen: "end",
+ onMessage: function() count += 1
+ });
+ page.on('message', function() count += 2);
+ page.on('message', function() count *= 3);
+ page.on('message', function()
+ test.assertEqual(count, 9, "All callbacks were called, in order."));
+ page.on('message', function() test.done());
+
+}
+
+tests.testLoadContentPage = function(test) {
+
+ test.waitUntilDone();
+
+ let page = Page({
+ onMessage: function(message) {
+ // The message is an array whose first item is the test method to call
+ // and the rest of whose items are arguments to pass it.
+ test[message.shift()].apply(test, message);
+ },
+ contentURL: require("self").data.url("test-page-worker.html"),
+ contentScriptFile: require("self").data.url("test-page-worker.js"),
+ contentScriptWhen: "ready"
+ });
+
+}
+
+tests.testAllowScriptDefault = function(test) {
+
+ test.waitUntilDone();
+
+ let page = Page({
+ onMessage: function(message) {
+ test.assert(message, "Script is allowed to run by default.");
+ test.done();
+ },
+ contentURL: "data:text/html,<script>document.documentElement.setAttribute('foo', 3);</script>",
+ contentScript: "self.postMessage(document.documentElement.getAttribute('foo'))",
+ contentScriptWhen: "ready"
+ });
+}
+
+tests.testAllowScript = function(test) {
+
+ test.waitUntilDone();
+
+ let page = Page({
+ onMessage: function(message) {
+ test.assert(message, "Script runs when allowed to do so.");
+ test.done();
+ },
+ allow: { script: true },
+ contentURL: "data:text/html,<script>document.documentElement.setAttribute('foo', 3);</script>",
+ contentScript: "self.postMessage(document.documentElement.hasAttribute('foo') && " +
+ " document.documentElement.getAttribute('foo') == 3)",
+ contentScriptWhen: "ready"
+ });
+}
+
+tests.testPingPong = function(test) {
+ test.waitUntilDone();
+ let page = Page({
+ contentURL: 'data:text/html,ping-pong',
+ contentScript: 'self.on("message", function(message) self.postMessage("pong"));'
+ + 'self.postMessage("ready");',
+ onMessage: function(message) {
+ if ('ready' == message) {
+ return page.postMessage('ping');
+ }
+ else {
+ test.assert(message, 'pong', 'Callback from contentScript');
+ test.done();
+ }
+ }
+ });
+};
+
+tests.testMultipleDestroys = function(test) {
+ let page = Page();
+ page.destroy();
+ page.destroy();
+ test.pass("Multiple destroys should not cause an error");
+};
+
+
+function isDestroyed(page) {
+ try {
+ page.postMessage("foo");
+ }
+ catch (err if err.message == ERR_DESTROYED) {
+ return true;
+ }
+ return false;
+}
+
+
+let pageWorkerSupported = true;
+
+try {
+ Pages = require("page-worker");
+ Page = Pages.Page;
+}
+catch (ex if ex.message == [
+ "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("")) {
+ pageWorkerSupported = false;
+}
+
+if (pageWorkerSupported) {
+ for (let test in tests) {
+ exports[test] = tests[test];
+ }
+} else {
+ exports.testPageWorkerNotSupported = function(test) {
+ test.pass("The page-worker module is not supported on this app.");
+ }
+}
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/test-panel.js b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-panel.js
new file mode 100644
index 0000000..ac58044
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-panel.js
@@ -0,0 +1,436 @@
+let { Cc, Ci } = require("chrome");
+let panels = require('panel');
+let tests = {}, panels, Panel;
+
+tests.testPanel = function(test) {
+ test.waitUntilDone();
+ let panel = Panel({
+ contentURL: "about:buildconfig",
+ contentScript: "self.postMessage(1); self.on('message', function() self.postMessage(2));",
+ onMessage: function (message) {
+ test.assertEqual(this, panel, "The 'this' object is the panel.");
+ switch(message) {
+ case 1:
+ test.pass("The panel was loaded.");
+ panel.postMessage('');
+ break;
+ case 2:
+ test.pass("The panel posted a message and received a response.");
+ panel.destroy();
+ test.done();
+ break;
+ }
+ }
+ });
+};
+
+tests.testPanelEmit = function(test) {
+ test.waitUntilDone();
+ let panel = Panel({
+ contentURL: "about:buildconfig",
+ contentScript: "self.port.emit('loaded');" +
+ "self.port.on('addon-to-content', " +
+ " function() self.port.emit('received'));",
+ });
+ panel.port.on("loaded", function () {
+ test.pass("The panel was loaded and sent a first event.");
+ panel.port.emit("addon-to-content");
+ });
+ panel.port.on("received", function () {
+ test.pass("The panel posted a message and received a response.");
+ panel.destroy();
+ test.done();
+ });
+};
+
+tests.testPanelEmitEarly = function(test) {
+ test.waitUntilDone();
+ let panel = Panel({
+ contentURL: "about:buildconfig",
+ contentScript: "self.port.on('addon-to-content', " +
+ " function() self.port.emit('received'));",
+ });
+ panel.port.on("received", function () {
+ test.pass("The panel posted a message early and received a response.");
+ panel.destroy();
+ test.done();
+ });
+ panel.port.emit("addon-to-content");
+};
+
+tests.testShowHidePanel = function(test) {
+ test.waitUntilDone();
+ let panel = Panel({
+ contentScript: "self.postMessage('')",
+ contentScriptWhen: "end",
+ onMessage: function (message) {
+ panel.show();
+ },
+ onShow: function () {
+ test.pass("The panel was shown.");
+ test.assertEqual(this, panel, "The 'this' object is the panel.");
+ test.assertEqual(this.isShowing, true, "panel.isShowing == true.");
+ panel.hide();
+ },
+ onHide: function () {
+ test.pass("The panel was hidden.");
+ test.assertEqual(this, panel, "The 'this' object is the panel.");
+ test.assertEqual(this.isShowing, false, "panel.isShowing == false.");
+ panel.destroy();
+ test.done();
+ }
+ });
+};
+
+tests.testDocumentReload = function(test) {
+ test.waitUntilDone();
+ let content =
+ "<script>" +
+ "setTimeout(function () {" +
+ " window.location = 'about:blank';" +
+ "}, 250);" +
+ "</script>";
+ let messageCount = 0;
+ let panel = Panel({
+ contentURL: "data:text/html," + encodeURIComponent(content),
+ contentScript: "self.postMessage(window.location.href)",
+ onMessage: function (message) {
+ messageCount++;
+ if (messageCount == 1) {
+ test.assertMatches(message, /data:text\/html,/, "First document had a content script");
+ }
+ else if (messageCount == 2) {
+ test.assertEqual(message, "about:blank", "Second document too");
+ panel.destroy();
+ test.done();
+ }
+ }
+ });
+};
+
+tests.testParentResizeHack = function(test) {
+ let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator).
+ getMostRecentWindow("navigator:browser");
+ let docShell = browserWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+ if (!("allowWindowControl" in docShell)) {
+ // bug 635673 is not fixed in this firefox build
+ test.pass("allowWindowControl attribute that allow to fix browser window " +
+ "resize is not available on this build.");
+ return;
+ }
+
+ test.waitUntilDone(30000);
+
+ let previousWidth = browserWindow.outerWidth, previousHeight = browserWindow.outerHeight;
+
+ let content = "<script>" +
+ "function contentResize() {" +
+ " resizeTo(200,200);" +
+ " resizeBy(200,200);" +
+ "}" +
+ "</script>" +
+ "Try to resize browser window";
+ let panel = Panel({
+ contentURL: "data:text/html," + encodeURIComponent(content),
+ contentScript: "self.on('message', function(message){" +
+ " if (message=='resize') " +
+ " unsafeWindow.contentResize();" +
+ "});",
+ contentScriptWhen: "ready",
+ onMessage: function (message) {
+
+ },
+ onShow: function () {
+ panel.postMessage('resize');
+ require("timer").setTimeout(function () {
+ test.assertEqual(previousWidth,browserWindow.outerWidth,"Size doesn't change by calling resizeTo/By/...");
+ test.assertEqual(previousHeight,browserWindow.outerHeight,"Size doesn't change by calling resizeTo/By/...");
+ panel.destroy();
+ test.done();
+ },0);
+ }
+ });
+ panel.show();
+}
+
+tests.testResizePanel = function(test) {
+ test.waitUntilDone();
+
+ // These tests fail on Linux if the browser window in which the panel
+ // is displayed is not active. And depending on what other tests have run
+ // before this one, it might not be (the untitled window in which the test
+ // runner executes is often active). So we make sure the browser window
+ // is focused by focusing it before running the tests. Then, to be the best
+ // possible test citizen, we refocus whatever window was focused before we
+ // started running these tests.
+
+ let activeWindow = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher).
+ activeWindow;
+ let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator).
+ getMostRecentWindow("navigator:browser");
+
+
+ function onFocus() {
+ browserWindow.removeEventListener("focus", onFocus, true);
+
+ let panel = Panel({
+ contentScript: "self.postMessage('')",
+ contentScriptWhen: "end",
+ height: 10,
+ width: 10,
+ onMessage: function (message) {
+ panel.show();
+ },
+ onShow: function () {
+ panel.resize(100,100);
+ panel.hide();
+ },
+ onHide: function () {
+ test.assert((panel.width == 100) && (panel.height == 100),
+ "The panel was resized.");
+ if (activeWindow)
+ activeWindow.focus();
+ test.done();
+ }
+ });
+ }
+
+ if (browserWindow === activeWindow) {
+ onFocus();
+ }
+ else {
+ browserWindow.addEventListener("focus", onFocus, true);
+ browserWindow.focus();
+ }
+};
+
+tests.testHideBeforeShow = function(test) {
+ test.waitUntilDone();
+ let showCalled = false;
+ let panel = Panel({
+ onShow: function () {
+ showCalled = true;
+ },
+ onHide: function () {
+ test.assert(!showCalled, 'must not emit show if was hidden before');
+ test.done();
+ }
+ });
+ panel.show();
+ panel.hide();
+};
+
+tests.testSeveralShowHides = function(test) {
+ test.waitUntilDone();
+ let hideCalled = 0;
+ let panel = panels.Panel({
+ contentURL: "about:buildconfig",
+ onShow: function () {
+ panel.hide();
+ },
+ onHide: function () {
+ hideCalled++;
+ if (hideCalled < 3)
+ panel.show();
+ else {
+ test.pass("onHide called three times as expected");
+ test.done();
+ }
+ }
+ });
+ panel.on('error', function(e) {
+ test.fail('error was emitted:' + e.message + '\n' + e.stack);
+ });
+ panel.show();
+};
+
+tests.testAnchorAndArrow = function(test) {
+ test.waitUntilDone(20000);
+ let count = 0;
+ function newPanel(tab, anchor) {
+ let panel = panels.Panel({
+ contentURL: "data:text/html,<html><body style='padding: 0; margin: 0; " +
+ "background: gray; text-align: center;'>Anchor: " +
+ anchor.id + "</body></html>",
+ width: 200,
+ height: 100,
+ onShow: function () {
+ count++;
+ panel.destroy();
+ if (count==5) {
+ test.pass("All anchored panel test displayed");
+ tab.close(function () {
+ test.done();
+ });
+ }
+ }
+ });
+ panel.show(anchor);
+ }
+
+ let tabs= require("tabs");
+ let url = 'data:text/html,' +
+ '<html><head><title>foo</title></head><body>' +
+ '<style>div {background: gray; position: absolute; width: 300px; ' +
+ 'border: 2px solid black;}</style>' +
+ '<div id="tl" style="top: 0px; left: 0px;">Top Left</div>' +
+ '<div id="tr" style="top: 0px; right: 0px;">Top Right</div>' +
+ '<div id="bl" style="bottom: 0px; left: 0px;">Bottom Left</div>' +
+ '<div id="br" style="bottom: 0px; right: 0px;">Bottom right</div>' +
+ '</body></html>';
+
+ tabs.open({
+ url: url,
+ onReady: function(tab) {
+ let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator).
+ getMostRecentWindow("navigator:browser");
+ let window = browserWindow.content;
+ newPanel(tab, window.document.getElementById('tl'));
+ newPanel(tab, window.document.getElementById('tr'));
+ newPanel(tab, window.document.getElementById('bl'));
+ newPanel(tab, window.document.getElementById('br'));
+ let anchor = browserWindow.document.getElementById("identity-box");
+ newPanel(tab, anchor);
+ }
+ });
+
+
+
+};
+
+tests.testPanelTextColor = function(test) {
+ test.waitUntilDone();
+ let html = "<html><head><style>body {color: yellow}</style></head>" +
+ "<body><p>Foo</p></body></html>";
+ let panel = Panel({
+ contentURL: "data:text/html," + encodeURI(html),
+ contentScript: "self.port.emit('color', " +
+ "window.getComputedStyle(document.body.firstChild, null). " +
+ " getPropertyValue('color'));"
+ });
+ panel.port.on("color", function (color) {
+ test.assertEqual(color, "rgb(255, 255, 0)",
+ "The panel text color style is preserved when a style exists.");
+ panel.destroy();
+ test.done();
+ });
+};
+
+function makeEventOrderTest(options) {
+ let expectedEvents = [];
+
+ return function(test) {
+ let panel = panels.Panel({ contentURL: "about:buildconfig" });
+
+ function expect(event, cb) {
+ expectedEvents.push(event);
+ panel.on(event, function() {
+ test.assertEqual(event, expectedEvents.shift());
+ if (cb)
+ require("timer").setTimeout(cb, 1);
+ });
+ return {then: expect};
+ }
+
+ test.waitUntilDone();
+ options.test(test, expect, panel);
+ }
+}
+
+tests.testAutomaticDestroy = function(test) {
+ let loader = test.makeSandboxedLoader();
+ let panel = loader.require("panel").Panel({
+ contentURL: "about:buildconfig",
+ contentScript:
+ "self.port.on('event', function() self.port.emit('event-back'));"
+ });
+
+ loader.unload();
+
+ panel.port.on("event-back", function () {
+ test.fail("Panel should have been destroyed on module unload");
+ });
+ panel.port.emit("event");
+ test.pass("check automatic destroy");
+};
+
+tests.testWaitForInitThenShowThenDestroy = makeEventOrderTest({
+ test: function(test, expect, panel) {
+ expect('inited', function() { panel.show(); }).
+ then('show', function() { panel.destroy(); }).
+ then('hide', function() { test.done(); });
+ }
+});
+
+tests.testShowThenWaitForInitThenDestroy = makeEventOrderTest({
+ test: function(test, expect, panel) {
+ panel.show();
+ expect('inited').
+ then('show', function() { panel.destroy(); }).
+ then('hide', function() { test.done(); });
+ }
+});
+
+tests.testShowThenHideThenDestroy = makeEventOrderTest({
+ test: function(test, expect, panel) {
+ panel.show();
+ expect('show', function() { panel.hide(); }).
+ then('hide', function() { panel.destroy(); test.done(); });
+ }
+});
+
+tests.testContentURLOption = function(test) {
+ const URL_STRING = "about:buildconfig";
+ const HTML_CONTENT = "<html><title>Test</title><p>This is a test.</p></html>";
+
+ let (panel = Panel({ contentURL: URL_STRING })) {
+ test.pass("contentURL accepts a string URL.");
+ test.assertEqual(panel.contentURL, URL_STRING,
+ "contentURL is the string to which it was set.");
+ }
+
+ let dataURL = "data:text/html," + encodeURIComponent(HTML_CONTENT);
+ let (panel = Panel({ contentURL: dataURL })) {
+ test.pass("contentURL accepts a data: URL.");
+ }
+
+ let (panel = Panel({})) {
+ test.assert(panel.contentURL == null,
+ "contentURL is undefined.");
+ }
+
+ test.assertRaises(function () Panel({ contentURL: "foo" }),
+ "The `contentURL` option must be a valid URL.",
+ "Panel throws an exception if contentURL is not a URL.");
+};
+
+let panelSupported = true;
+
+try {
+ panels = require("panel");
+ Panel = panels.Panel;
+}
+catch(ex if ex.message == [
+ "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("")) {
+ panelSupported = false;
+}
+
+if (panelSupported) {
+ for (let test in tests)
+ exports[test] = tests[test];
+}
+else {
+ exports.testPanelNotSupported = function(test) {
+ test.pass("The panel module is not supported on this app.");
+ }
+}
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/test-passwords.js b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-passwords.js
new file mode 100644
index 0000000..e799b48
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-passwords.js
@@ -0,0 +1,277 @@
+"use strict";
+
+const { store, search, remove } = require("passwords");
+
+exports["test store requires `password` field"] = function(assert, done) {
+ store({
+ username: "foo",
+ realm: "bar",
+ onComplete: function onComplete() {
+ assert.fail("onComplete should not be called");
+ },
+ onError: function onError() {
+ assert.pass("'`password` is required");
+ done();
+ }
+ });
+};
+
+exports["test store requires `username` field"] = function(assert, done) {
+ store({
+ password: "foo",
+ realm: "bar",
+ onComplete: function onComplete() {
+ assert.fail("onComplete should not be called");
+ },
+ onError: function onError() {
+ assert.pass("'`username` is required");
+ done();
+ }
+ });
+};
+
+exports["test onComplete is optional"] = function(assert, done) {
+ store({
+ realm: "bla",
+ username: "bla",
+ password: "bla",
+ onError: function onError() {
+ assert.fail("onError was called");
+ }
+ });
+ assert.pass("exception is not thrown if `onComplete is missing")
+ done();
+};
+
+exports["test exceptions in onComplete are reported"] = function(assert, done) {
+ store({
+ realm: "throws",
+ username: "error",
+ password: "boom!",
+ onComplete: function onComplete(error) {
+ throw new Error("Boom!")
+ },
+ onError: function onError(error) {
+ assert.equal(error.message, "Boom!", "Error thrown is reported");
+ done();
+ }
+ });
+};
+
+exports["test store requires `realm` field"] = function(assert, done) {
+ store({
+ username: "foo",
+ password: "bar",
+ onComplete: function onComplete() {
+ assert.fail("onComplete should not be called");
+ },
+ onError: function onError() {
+ assert.pass("'`realm` is required");
+ done();
+ }
+ });
+};
+
+exports["test can't store same login twice"] = function(assert, done) {
+ store({
+ username: "user",
+ password: "pass",
+ realm: "realm",
+ onComplete: function onComplete() {
+ assert.pass("credential saved");
+
+ store({
+ username: "user",
+ password: "pass",
+ realm: "realm",
+ onComplete: function onComplete() {
+ assert.fail("onComplete should not be called");
+ },
+ onError: function onError() {
+ assert.pass("re-saving credential failed");
+
+ remove({
+ username: "user",
+ password: "pass",
+ realm: "realm",
+ onComplete: function onComplete() {
+ assert.pass("credential was removed");
+ done();
+ },
+ onError: function onError() {
+ assert.fail("remove should not fail");
+ }
+ });
+ }
+ });
+ },
+ onError: function onError() {
+ assert.fail("onError should not be called");
+ }
+ });
+};
+
+exports["test remove fails if no login found"] = function(assert, done) {
+ remove({
+ username: "foo",
+ password: "bar",
+ realm: "baz",
+ onComplete: function onComplete() {
+ assert.fail("should not be able to remove unstored credentials");
+ },
+ onError: function onError() {
+ assert.pass("can't remove unstored credentials");
+ done();
+ }
+ });
+};
+
+exports["test addon associated credentials"] = function(assert, done) {
+ store({
+ username: "foo",
+ password: "bar",
+ realm: "baz",
+ onComplete: function onComplete() {
+ search({
+ username: "foo",
+ password: "bar",
+ realm: "baz",
+ onComplete: function onComplete([credential]) {
+ assert.equal(credential.url.indexOf("addon:"), 0,
+ "`addon:` uri is used for add-on credentials");
+ assert.equal(credential.username, "foo",
+ "username matches");
+ assert.equal(credential.password, "bar",
+ "password matches");
+ assert.equal(credential.realm, "baz", "realm matches");
+ assert.equal(credential.formSubmitURL, null,
+ "`formSubmitURL` is `null` for add-on credentials");
+ assert.equal(credential.usernameField, "", "usernameField is empty");
+ assert.equal(credential.passwordField, "", "passwordField is empty");
+
+ remove({
+ username: credential.username,
+ password: credential.password,
+ realm: credential.realm,
+ onComplete: function onComplete() {
+ assert.pass("credential is removed");
+ done();
+ },
+ onError: function onError() {
+ assert.fail("onError should not be called");
+ }
+ });
+ },
+ onError: function onError() {
+ assert.fail("onError should not be called");
+ }
+ });
+ },
+ onError: function onError() {
+ assert.fail("onError should not be called");
+ }
+ });
+};
+
+exports["test web page associated credentials"] = function(assert, done) {
+ store({
+ url: "http://bar.foo.com/authentication/?login",
+ formSubmitURL: "http://login.foo.com/authenticate.cgi",
+ username: "user",
+ password: "pass",
+ usernameField: "user-f",
+ passwordField: "pass-f",
+ onComplete: function onComplete() {
+ search({
+ username: "user",
+ password: "pass",
+ url: "http://bar.foo.com",
+ formSubmitURL: "http://login.foo.com",
+ onComplete: function onComplete([credential]) {
+ assert.equal(credential.url, "http://bar.foo.com", "url matches");
+ assert.equal(credential.username, "user", "username matches");
+ assert.equal(credential.password, "pass", "password matches");
+ assert.equal(credential.realm, null, "realm is null");
+ assert.equal(credential.formSubmitURL, "http://login.foo.com",
+ "formSubmitURL matches");
+ assert.equal(credential.usernameField, "user-f",
+ "usernameField is matches");
+ assert.equal(credential.passwordField, "pass-f",
+ "passwordField matches");
+
+ remove({
+ url: credential.url,
+ formSubmitURL: credential.formSubmitURL,
+ username: credential.username,
+ password: credential.password,
+ usernameField: credential.usernameField,
+ passwordField: credential.passwordField,
+
+ onComplete: function onComplete() {
+ assert.pass("credential is removed");
+ done();
+ },
+ onError: function onError(e) {
+ assert.fail("onError should not be called");
+ }
+ });
+ },
+ onError: function onError() {
+ assert.fail("onError should not be called");
+ }
+ });
+ },
+ onError: function onError() {
+ assert.fail("onError should not be called");
+ }
+ });
+};
+
+exports["test site authentication credentials"] = function(assert, done) {
+ store({
+ url: "http://authentication.com",
+ username: "U",
+ password: "P",
+ realm: "R",
+ onComplete: function onComplete() {
+ search({
+ url: "http://authentication.com",
+ username: "U",
+ password: "P",
+ realm: "R",
+ onComplete: function onComplete([credential]) {
+ assert.equal(credential.url,"http://authentication.com",
+ "url matches");
+ assert.equal(credential.username, "U", "username matches");
+ assert.equal(credential.password, "P", "password matches");
+ assert.equal(credential.realm, "R", "realm matches");
+ assert.equal(credential.formSubmitURL, null, "formSubmitURL is null");
+ assert.equal(credential.usernameField, "", "usernameField is empty");
+ assert.equal(credential.passwordField, "", "passwordField is empty");
+
+ remove({
+ url: credential.url,
+ username: credential.username,
+ password: credential.password,
+ realm: credential.realm,
+ onComplete: function onComplete() {
+ assert.pass("credential is removed");
+ done();
+ },
+ onError: function onError() {
+ assert.fail("onError should not be called");
+ }
+ });
+ },
+ onError: function onError() {
+ assert.fail("onError should not be called");
+ }
+ });
+ },
+ onError: function onError() {
+ assert.fail("onError should not be called");
+ }
+ });
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/test-private-browsing.js b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-private-browsing.js
new file mode 100644
index 0000000..268e933
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-private-browsing.js
@@ -0,0 +1,237 @@
+/* ***** 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 ***** */
+
+let pb = require("private-browsing");
+let {Cc,Ci} = require("chrome");
+
+let pbService;
+// Currently, only Firefox implements the private browsing service.
+if (require("xul-app").is("Firefox")) {
+ pbService = Cc["@mozilla.org/privatebrowsing;1"].
+ getService(Ci.nsIPrivateBrowsingService);
+}
+
+if (pbService) {
+
+ // tests that isActive has the same value as the private browsing service
+ // expects
+ exports.testGetIsActive = function (test) {
+ test.assertEqual(pb.isActive, false,
+ "private-browsing.isActive is correct without modifying PB service");
+
+ pbService.privateBrowsingEnabled = true;
+ test.assert(pb.isActive,
+ "private-browsing.isActive is correct after modifying PB service");
+
+ // Switch back to normal mode.
+ pbService.privateBrowsingEnabled = false;
+ };
+
+ // tests that activating does put the browser into private browsing mode
+ exports.testActivateDeactivate = function (test) {
+ test.waitUntilDone();
+ pb.once("start", function onStart() {
+ test.assertEqual(pbService.privateBrowsingEnabled, true,
+ "private browsing mode was activated");
+ pb.deactivate();
+ });
+ pb.once("stop", function onStop() {
+ test.assertEqual(pbService.privateBrowsingEnabled, false,
+ "private browsing mode was deactivate");
+ test.done();
+ });
+ pb.activate();
+ };
+
+ exports.testStart = function(test) {
+ test.waitUntilDone();
+ pb.on("start", function onStart() {
+ test.assertEqual(this, pb, "`this` should be private-browsing module");
+ test.assert(pbService.privateBrowsingEnabled,
+ 'private mode is active when "start" event is emitted');
+ test.assert(pb.isActive,
+ '`isActive` is `true` when "start" event is emitted');
+ pb.removeListener("start", onStart);
+ test.done();
+ });
+ pb.activate();
+ };
+
+ exports.testStop = function(test) {
+ test.waitUntilDone();
+ pb.on("stop", function onStop() {
+ test.assertEqual(this, pb, "`this` should be private-browsing module");
+ test.assertEqual(pbService.privateBrowsingEnabled, false,
+ "private mode is disabled when stop event is emitted");
+ test.assertEqual(pb.isActive, false,
+ "`isActive` is `false` when stop event is emitted");
+ pb.removeListener("stop", onStop);
+ test.done();
+ });
+ pb.activate();
+ pb.deactivate();
+ };
+
+ exports.testAutomaticUnload = function(test) {
+ test.waitUntilDone();
+ // Create another private browsing instance and unload it
+ let loader = test.makeSandboxedLoader();
+ let pb2 = loader.require("private-browsing");
+ let called = false;
+ pb2.on("start", function onStart() {
+ called = true;
+ test.fail("should not be called:x");
+ });
+ loader.unload();
+
+ // Then switch to private mode in order to check that the previous instance
+ // is correctly destroyed
+ pb.activate();
+ pb.once("start", function onStart() {
+ require("timer").setTimeout(function () {
+ test.assert(!called,
+ "First private browsing instance is destroyed and inactive");
+
+ // Must reset to normal mode, so that next test starts with it.
+ pb.deactivate();
+ test.done();
+ }, 0);
+ });
+ };
+
+ exports.testBothListeners = function(test) {
+ test.waitUntilDone();
+ let stop = false;
+ let start = false;
+
+ function onStop() {
+ test.assertEqual(stop, false,
+ "stop callback must be called only once");
+ test.assertEqual(pbService.privateBrowsingEnabled, false,
+ "private mode is disabled when stop event is emitted");
+ test.assertEqual(pb.isActive, false,
+ "`isActive` is `false` when stop event is emitted");
+
+ pb.on("start", finish);
+ pb.removeListener("start", onStart);
+ pb.removeListener("start", onStart2);
+ pb.activate();
+ stop = true;
+ }
+
+ function onStart() {
+ test.assertEqual(false, start,
+ "stop callback must be called only once");
+ test.assert(pbService.privateBrowsingEnabled,
+ "private mode is active when start event is emitted");
+ test.assert(pb.isActive,
+ "`isActive` is `true` when start event is emitted");
+
+ pb.on("stop", onStop);
+ pb.deactivate();
+ start = true;
+ }
+
+ function onStart2() {
+ test.assert(start, "start listener must be called already");
+ test.assertEqual(false, stop, "stop callback must not be called yet");
+ }
+
+ function finish() {
+ test.assert(pbService.privateBrowsingEnabled, true,
+ "private mode is active when start event is emitted");
+ test.assert(pb.isActive,
+ "`isActive` is `true` when start event is emitted");
+
+ pb.removeListener("start", finish);
+ pb.removeListener("stop", onStop);
+
+ pb.deactivate();
+ pb.once("stop", function () {
+ test.assertEqual(pbService.privateBrowsingEnabled, false);
+ test.assertEqual(pb.isActive, false);
+
+ test.done();
+ });
+ }
+
+ pb.on("start", onStart);
+ pb.on("start", onStart2);
+ pbService.privateBrowsingEnabled = true;
+ };
+
+ exports["test activate private mode via handler"] = function(test) {
+ const tabs = require("tabs");
+
+ test.waitUntilDone();
+ function onReady(tab) {
+ if (tab.url == "about:robots")
+ tab.close(function() pb.activate());
+ }
+ function cleanup(tab) {
+ if (tab.url == "about:") {
+ tabs.removeListener("ready", cleanup);
+ tab.close(function onClose() {
+ test.done();
+ });
+ }
+ }
+
+ tabs.on("ready", onReady);
+ pb.once("start", function onStart() {
+ test.pass("private mode was activated");
+ pb.deactivate();
+ });
+ pb.once("stop", function onStop() {
+ test.pass("private mode was deactivated");
+ tabs.removeListener("ready", onReady);
+ tabs.on("ready", cleanup);
+ });
+ tabs.once("open", function onOpen() {
+ tabs.open("about:robots");
+ });
+ tabs.open("about:");
+ };
+}
+else {
+ // tests for the case where private browsing doesn't exist
+ exports.testNoImpl = function (test) {
+ test.assertEqual(pb.isActive, false,
+ "pb.isActive returns false when private browsing isn't supported");
+ };
+}
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/test-request.js b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-request.js
new file mode 100644
index 0000000..0bc7dbb
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-request.js
@@ -0,0 +1,394 @@
+/* ***** 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 ***** */
+
+const Request = require("request").Request;
+
+exports.testOptionsValidator = function(test) {
+ // First, a simple test to make sure we didn't break normal functionality.
+ test.assertRaises(function () {
+ Request({
+ url: null
+ });
+ }, 'The option "url" must be one of the following types: string');
+
+ // Next we'll have a Request that doesn't throw from c'tor, but from a setter.
+ let req = Request({
+ url: "http://playground.zpao.com/jetpack/request/text.php",
+ onComplete: function () {}
+ });
+ test.assertRaises(function () {
+ req.url = null;
+ }, 'The option "url" must be one of the following types: string');
+ // The url shouldn't have changed, so check that
+ test.assertEqual(req.url, "http://playground.zpao.com/jetpack/request/text.php");
+}
+
+exports.testContentValidator = function(test) {
+ test.waitUntilDone();
+ Request({
+ url: "data:text/html,response",
+ content: { 'key1' : null, 'key2' : 'some value' },
+ onComplete: function(response) {
+ test.assertEqual(response.text, "response?key1=null&key2=some+value");
+ test.done();
+ }
+ }).get();
+};
+
+// All tests below here require a network connection. They will be commented out
+// when checked in. If you'd like to run them, simply uncomment them.
+//
+// When we have the means, these tests will be converted so that they don't
+// require an external server nor a network connection.
+
+/*
+// This is request to a file that exists
+exports.testStatus_200 = function (test) {
+ test.waitUntilDone();
+ var req = Request({
+ url: "http://playground.zpao.com/jetpack/request/text.php",
+ onComplete: function (response) {
+ test.assertEqual(this, req, "`this` should be request");
+ test.assertEqual(response.status, 200);
+ test.assertEqual(response.statusText, "OK");
+ test.done();
+ }
+ }).get();
+}
+
+// This tries to get a file that doesn't exist
+exports.testStatus_404 = function (test) {
+ test.waitUntilDone();
+ Request({
+ // the following URL doesn't exist
+ url: "http://playground.zpao.com/jetpack/request/nonexistent.php",
+ onComplete: function (response) {
+ test.assertEqual(response.status, 404);
+ test.assertEqual(response.statusText, "Not Found");
+ test.done();
+ }
+ }).get();
+}
+
+exports.testSimpleXML = function (test) {
+ test.waitUntilDone();
+ Request({
+ // File originally available at http://www.w3schools.com/xml/note.xml
+ url: "http://playground.zpao.com/jetpack/request/note.xml",
+ onComplete: function (response) {
+ // response.xml should be a document, so lets use it
+ test.assertRaises(function() { response.xml },
+ "Sorry, the 'xml' property is no longer available. " +
+ "see bug 611042 for more information.");
+ test.done();
+ return;
+ let xml = response.xml;
+ let notes = xml.getElementsByTagName("note");
+ // Notes should have length of 1
+ test.assertEqual(notes.length, 1, "Should be 1 <note> in the XML");
+ let note = notes[0];
+
+ // Silly whitespace text nodes...
+ let text = note.childNodes[0];
+ test.assertEqual(note.childNodes[0].nodeName, "#text");
+
+ // Just test the next real node
+ let to = note.childNodes[1];
+ test.assertEqual(to.nodeName, "to");
+ test.assertEqual(to.textContent, "Tove");
+ test.assertEqual(to.childNodes[0].nodeValue, "Tove");
+ test.done();
+ }
+ }).get();
+}
+
+// a simple file with known contents
+exports.testSimpleText = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/text.php",
+ onComplete: function (response) {
+ test.assertEqual(response.text, "Look ma, no hands!\n");
+ test.done();
+ }
+ }).get();
+}
+
+// a simple file with a known header
+exports.testKnownHeader = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/headers.php",
+ onComplete: function (response) {
+ test.assertEqual(response.headers["x-zpao-header"], "Jamba Juice");
+ test.done();
+ }
+ }).get();
+}
+
+// complex headers
+exports.testKnownHeader = function (test) {
+ let headers = {
+ "x-zpao-header": "Jamba Juice is: delicious",
+ "x-zpao-header-2": "foo, bar",
+ "Set-Cookie": "foo=bar\nbaz=foo"
+ }
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/complex_headers.php",
+ onComplete: function (response) {
+ for (k in headers) {
+ test.assertEqual(response.headers[k], headers[k]);
+ }
+ test.done();
+ }
+ }).get();
+}
+
+exports.testContentTypeHeader = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/text.txt",
+ onComplete: function (response) {
+ test.assertEqual(response.headers["Content-Type"], "text/plain");
+ test.done();
+ }
+ }).get();
+}
+
+exports.testSimpleJSON = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/json.php",
+ onComplete: function (response) {
+ assertDeepEqual(test, response.json, { foo: "bar" });
+ test.done();
+ }
+ }).get();
+}
+
+exports.testInvalidJSON = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/invalid_json.php",
+ onComplete: function (response) {
+ test.assertEqual(response.json, null);
+ test.done();
+ }
+ }).get();
+}
+
+exports.testGetWithParamsNotContent = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/getpost.php?foo=bar",
+ onComplete: function (response) {
+ let expected = {
+ "POST": [],
+ "GET" : { foo: "bar" }
+ };
+ assertDeepEqual(test, response.json, expected);
+ test.done();
+ }
+ }).get();
+}
+
+exports.testGetWithContent = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/getpost.php",
+ content: { foo: "bar" },
+ onComplete: function (response) {
+ let expected = {
+ "POST": [],
+ "GET" : { foo: "bar" }
+ };
+ assertDeepEqual(test, response.json, expected);
+ test.done();
+ }
+ }).get();
+}
+
+exports.testGetWithParamsAndContent = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/getpost.php?foo=bar",
+ content: { baz: "foo" },
+ onComplete: function (response) {
+ let expected = {
+ "POST": [],
+ "GET" : { foo: "bar", baz: "foo" }
+ };
+ assertDeepEqual(test, response.json, expected);
+ test.done();
+ }
+ }).get();
+}
+
+exports.testSimplePost = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/getpost.php",
+ content: { foo: "bar" },
+ onComplete: function (response) {
+ let expected = {
+ "POST": { foo: "bar" },
+ "GET" : []
+ };
+ assertDeepEqual(test, response.json, expected);
+ test.done();
+ }
+ }).post();
+}
+
+exports.testEncodedContent = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/getpost.php",
+ content: "foo=bar&baz=foo",
+ onComplete: function (response) {
+ let expected = {
+ "POST": [],
+ "GET" : { foo: "bar", baz: "foo" }
+ };
+ assertDeepEqual(test, response.json, expected);
+ test.done();
+ }
+ }).get();
+}
+
+exports.testEncodedContentWithSpaces = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/getpost.php",
+ content: "foo=bar+hop!&baz=foo",
+ onComplete: function (response) {
+ let expected = {
+ "POST": [],
+ "GET" : { foo: "bar hop!", baz: "foo" }
+ };
+ assertDeepEqual(test, response.json, expected);
+ test.done();
+ }
+ }).get();
+}
+
+exports.testGetWithArray = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/getpost.php",
+ content: { foo: [1, 2], baz: "foo" },
+ onComplete: function (response) {
+ let expected = {
+ "POST": [],
+ "GET" : { foo: [1, 2], baz: "foo" }
+ };
+ assertDeepEqual(test, response.json, expected);
+ test.done();
+ }
+ }).get();
+}
+
+exports.testGetWithNestedArray = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/getpost.php",
+ content: { foo: [1, 2, [3, 4]], bar: "baz" },
+ onComplete: function (response) {
+ let expected = {
+ "POST": [],
+ "GET" : this.content
+ };
+ assertDeepEqual(test, response.json, expected);
+ test.done();
+ }
+ }).get();
+}
+
+exports.testGetWithNestedArray = function (test) {
+ test.waitUntilDone();
+ let request = Request({
+ url: "http://playground.zpao.com/jetpack/request/getpost.php",
+ content: {
+ foo: [1, 2, {
+ omg: "bbq",
+ "all your base!": "are belong to us"
+ }],
+ bar: "baz"
+ },
+ onComplete: function (response) {
+ let expected = {
+ "POST": [],
+ "GET" : request.content
+ };
+ assertDeepEqual(test, response.json, expected);
+ test.done();
+ }
+ }).get();
+}
+*/
+
+// This is not a proper testing for deep equal, but it's good enough for my uses
+// here. It will do type coercion to check equality, but that's good here. Data
+// coming from the server will be stringified and so "0" should be equal to 0.
+function assertDeepEqual(test, obj1, obj2, msg) {
+ function equal(o1, o2) {
+ // cover our non-object cases well enough
+ if (o1 == o2)
+ return true;
+ if (typeof(o1) != typeof(o2))
+ return false;
+ if (typeof(o1) != "object")
+ return o1 == o2;
+
+ let e = true;
+ for (let [key, val] in Iterator(o1)) {
+ e = e && key in o2 && equal(o2[key], val);
+ if (!e)
+ break;
+ }
+ for (let [key, val] in Iterator(o2)) {
+ e = e && key in o1 && equal(o1[key], val);
+ if (!e)
+ break;
+ }
+ return e;
+ }
+ msg = msg || "objects not equal - " + JSON.stringify(obj1) + " != " +
+ JSON.stringify(obj2);
+ test.assert(equal(obj1, obj2), msg);
+}
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/test-selection.js b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-selection.js
new file mode 100644
index 0000000..cd96072
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-selection.js
@@ -0,0 +1,490 @@
+/* ***** 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>
+ *
+ * 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 ***** */
+
+let timer = require("timer");
+let {Cc,Ci} = require("chrome");
+
+// Arbitrary delay needed to avoid weird behavior.
+// TODO: We need to find all uses of this and replace them
+// with more deterministic solutions.
+const ARB_DELAY = 100;
+
+// Select all divs elements in an HTML document
+function selectAllDivs(window) {
+ let divs = window.document.getElementsByTagName("div");
+ let s = window.getSelection();
+ if (s.rangeCount > 0)
+ s.removeAllRanges();
+ for (let i = 0; i < divs.length; i++) {
+ let range = window.document.createRange();
+ range.selectNode(divs[i]);
+ s.addRange(range);
+ }
+}
+
+function selectTextarea(window, from, to) {
+ let textarea = window.document.getElementsByTagName("textarea")[0];
+
+ from = from || 0;
+ to = to || textarea.value.length;
+
+ textarea.setSelectionRange(from, to);
+ textarea.focus();
+}
+
+function primeTestCase(html, test, callback) {
+ let tabBrowser = require("tab-browser");
+ let dataURL = "data:text/html," + encodeURI(html);
+ let tracker = tabBrowser.whenContentLoaded(
+ function(window) {
+ if (window.document.location.href != dataURL)
+ return;
+ callback(window, test);
+ timer.setTimeout(function() {
+ tracker.unload();
+ test.done();
+ window.close();
+ },
+ ARB_DELAY);
+ }
+ );
+ tabBrowser.addTab(dataURL);
+}
+
+const DIV1 = '<div id="foo">bar</div>';
+const DIV2 = '<div>noodles</div>';
+const HTML_MULTIPLE = '<html><body>' + DIV1 + DIV2 + '</body></html>';
+const HTML_SINGLE = '<html><body>' + DIV1 + '</body></html>';
+
+// Tests of contiguous
+
+exports.testContiguousMultiple = function testContiguousMultiple(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_MULTIPLE, test, function(window, test) {
+ selectAllDivs(window);
+ test.assertEqual(selection.isContiguous, false,
+ "selection.isContiguous multiple works.");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testContiguousSingle = function testContiguousSingle(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_SINGLE, test, function(window, test) {
+ selectAllDivs(window);
+ test.assertEqual(selection.isContiguous, true,
+ "selection.isContiguous single works.");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testContiguousWithoutSelection =
+ function testContiguousWithoutSelection(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_SINGLE, test, function(window, test) {
+ test.assertEqual(selection.isContiguous, false,
+ "selection.isContiguous without selection works.");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+/**
+ * Test that setting the contiguous property has no effect.
+ */
+/*exports.testSetContiguous = function testSetContiguous(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_MULTIPLE, test, function(window, test) {
+ selectAllDivs(window);
+ try {
+ selection.isContiguous = true;
+ test.assertEqual(selection.isContiguous, false,
+ "setting selection.isContiguous doesn't work (as expected).");
+ }
+ catch (e) {
+ test.pass("setting selection.isContiguous doesn't work (as expected).");
+ }
+ });
+
+ test.waitUntilDone(5000);
+};*/
+
+
+// HTML tests
+
+exports.testGetHTMLSingleSelection = function testGetHTMLSingleSelection(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_SINGLE, test, function(window, test) {
+ selectAllDivs(window);
+ test.assertEqual(selection.html, DIV1, "get html selection works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+/* Myk's comments: This is fine. However, it reminds me to figure out and
+ specify whether iteration is ordered. If so, we'll want to change this
+ test in the future to test that the discontiguous selections are returned in
+ the appropriate order. In the meantime, add a comment to that effect here */
+exports.testGetHTMLMultipleSelection =
+ function testGetHTMLMultipleSelection(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_MULTIPLE, test, function(window, test) {
+ selectAllDivs(window);
+ let assertions = false;
+ for each (let i in selection) {
+ test.assertEqual(true, [DIV1, DIV2].some(function(t) t == i.html),
+ "get multiple selection html works");
+ assertions = true;
+ }
+ // Ensure we ran at least one assertEqual()
+ test.assert(assertions, "No assertions were called");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testGetHTMLNull = function testGetHTMLNull(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_SINGLE, test, function(window, test) {
+ test.assertEqual(selection.html, null, "get html null works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testGetHTMLWeird = function testGetHTMLWeird(test) {
+ let selection = require("selection");
+ // If the getter is used when there are contiguous selections, the first
+ // selection should be returned
+ primeTestCase(HTML_MULTIPLE, test, function(window, test) {
+ selectAllDivs(window);
+ test.assertEqual(selection.html, DIV1, "get html weird works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testGetHTMLNullInTextareaSelection =
+ function testGetHTMLNullInTextareaSelection(test) {
+ let selection = require("selection");
+
+ primeTestCase(TEXT_FIELD, test, function(window, test) {
+ selectTextarea(window);
+
+ test.assertEqual(selection.html, null, "get html null in textarea works")
+ });
+
+ test.waitUntilDone(5000);
+};
+
+const REPLACEMENT_HTML = "<b>Lorem ipsum dolor sit amet</b>";
+
+exports.testSetHTMLSelection = function testSetHTMLSelection(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_SINGLE, test, function(window, test) {
+ selectAllDivs(window);
+ selection.html = REPLACEMENT_HTML;
+ test.assertEqual(selection.html, "<span>" + REPLACEMENT_HTML +
+ "</span>", "selection html works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testSetHTMLException = function testSetHTMLException(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_SINGLE, test, function(window, test) {
+ try {
+ selection.html = REPLACEMENT_HTML;
+ test.fail("set HTML throws when there's no selection.");
+ }
+ catch (e) {
+ test.pass("set HTML throws when there's no selection.");
+ }
+ });
+
+ test.waitUntilDone(5000);
+};
+
+const TEXT1 = "foo";
+const TEXT2 = "noodles";
+const TEXT_MULTIPLE = "<html><body><div>" + TEXT1 + "</div><div>" + TEXT2 +
+ "</div></body></html>";
+const TEXT_SINGLE = "<html><body><div>" + TEXT1 + "</div></body></html>";
+const TEXT_FIELD = "<html><body><textarea>" + TEXT1 + "</textarea></body></html>";
+
+// Text tests
+
+exports.testSetHTMLinTextareaSelection =
+ function testSetHTMLinTextareaSelection(test) {
+ let selection = require("selection");
+
+ primeTestCase(TEXT_FIELD, test, function(window, test) {
+ selectTextarea(window);
+
+ // HTML string is set as plain text in textareas, that's because
+ // `selection.html` and `selection.text` are basically aliases when a
+ // value is set. See bug 677269
+ selection.html = REPLACEMENT_HTML;
+
+ test.assertEqual(selection.text, REPLACEMENT_HTML,
+ "set selection html in textarea works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testGetTextSingleSelection =
+ function testGetTextSingleSelection(test) {
+ let selection = require("selection");
+ primeTestCase(TEXT_SINGLE, test, function(window, test) {
+ selectAllDivs(window);
+ test.assertEqual(selection.text, TEXT1, "get text selection works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testGetTextMultipleSelection =
+ function testGetTextMultipleSelection(test) {
+ let selection = require("selection");
+ primeTestCase(TEXT_MULTIPLE, test, function(window, test) {
+ selectAllDivs(window);
+ let assertions = false;
+ for each (let i in selection) {
+ test.assertEqual(true, [TEXT1, TEXT2].some(function(t) t == i.text),
+ "get multiple selection text works");
+ assertions = true;
+ }
+ // Ensure we ran at least one assertEqual()
+ test.assert(assertions, "No assertions were called");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testGetTextNull = function testGetTextNull(test) {
+ let selection = require("selection");
+ primeTestCase(TEXT_SINGLE, test, function(window, test) {
+ test.assertEqual(selection.text, null, "get text null works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testGetTextWeird = function testGetTextWeird(test) {
+ let selection = require("selection");
+ // If the getter is used when there are contiguous selections, the first
+ // selection should be returned
+ primeTestCase(TEXT_MULTIPLE, test, function(window, test) {
+ selectAllDivs(window);
+ test.assertEqual(selection.text, TEXT1, "get text weird works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testGetTextNullInTextareaSelection =
+ function testGetTextInTextareaSelection(test) {
+ let selection = require("selection");
+
+ primeTestCase(TEXT_FIELD, test, function(window, test) {
+ test.assertEqual(selection.text, null, "get text null in textarea works")
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testGetTextInTextareaSelection =
+ function testGetTextInTextareaSelection(test) {
+ let selection = require("selection");
+
+ primeTestCase(TEXT_FIELD, test, function(window, test) {
+ selectTextarea(window);
+
+ test.assertEqual(selection.text, TEXT1, "get text null in textarea works")
+ });
+
+ test.waitUntilDone(5000);
+};
+
+const REPLACEMENT_TEXT = "Lorem ipsum dolor sit amet";
+
+exports.testSetTextSelection = function testSetTextSelection(test) {
+ let selection = require("selection");
+ primeTestCase(TEXT_SINGLE, test, function(window, test) {
+ selectAllDivs(window);
+ selection.text = REPLACEMENT_TEXT;
+ test.assertEqual(selection.text, REPLACEMENT_TEXT, "selection text works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testSetHTMLException = function testSetHTMLException(test) {
+ let selection = require("selection");
+ primeTestCase(TEXT_SINGLE, test, function(window, test) {
+ try {
+ selection.text = REPLACEMENT_TEXT;
+ test.fail("set HTML throws when there's no selection.");
+ }
+ catch (e) {
+ test.pass("set HTML throws when there's no selection.");
+ }
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testSetTextInTextareaSelection =
+ function testSetTextInTextareaSelection(test) {
+ let selection = require("selection");
+
+ primeTestCase(TEXT_FIELD, test, function(window, test) {
+ selectTextarea(window);
+
+ selection.text = REPLACEMENT_TEXT;
+
+ test.assertEqual(selection.text, REPLACEMENT_TEXT,
+ "set selection text in textarea works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+// Iterator tests
+
+exports.testIterator = function testIterator(test) {
+ let selection = require("selection");
+ let selectionCount = 0;
+ primeTestCase(TEXT_MULTIPLE, test, function(window, test) {
+ selectAllDivs(window);
+ for each (let i in selection)
+ selectionCount++;
+ test.assertEqual(2, selectionCount, "iterator works.");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testIteratorWithTextareaSelection =
+ function testIteratorWithTextareaSelection(test) {
+ let selection = require("selection");
+ let selectionCount = 0;
+
+ primeTestCase(TEXT_FIELD, test, function(window, test) {
+ selectTextarea(window);
+
+ for each (let i in selection)
+ selectionCount++;
+
+ test.assertEqual(1, selectionCount, "iterator works in textarea.");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+/* onSelect tests */
+
+/*
+function sendSelectionSetEvent(window) {
+ const Ci = Components.interfaces;
+ let utils = window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils);
+ if (!utils.sendSelectionSetEvent(0, 1, false))
+ dump("**** sendSelectionSetEvent did not select anything\n");
+ else
+ dump("**** sendSelectionSetEvent succeeded\n");
+}
+
+// testOnSelect() requires nsIDOMWindowUtils, which is only available in
+// Firefox 3.7+.
+exports.testOnSelect = function testOnSelect(test) {
+ let selection = require("selection");
+ let callbackCount = 0;
+ primeTestCase(TEXT_SINGLE, test, function(window, test) {
+ selection.onSelect = function() {callbackCount++};
+ // Now simulate the user selecting stuff
+ sendSelectionSetEvent(window);
+ selection.text = REPLACEMENT_TEXT;
+ test.assertEqual(1, callbackCount, "onSelect text listener works.");
+ //test.pass();
+ //test.done();
+ });
+
+ test.waitUntilDone(5000);
+};
+
+// testOnSelectExceptionNoBubble() requires nsIDOMWindowUtils, which is only
+// available in Firefox 3.7+.
+exports.testOnSelectExceptionNoBubble =
+ function testOnSelectTextSelection(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_SINGLE, test, function(window, test) {
+ selection.onSelect = function() {
+ throw new Error("Exception thrown in testOnSelectExceptionNoBubble");
+ };
+ // Now simulate the user selecting stuff
+ sendSelectionSetEvent(window);
+ test.pass("onSelect catches exceptions.");
+ });
+
+ test.waitUntilDone(5000);
+};
+*/
+
+// If the module doesn't support the app we're being run in, require() will
+// throw. In that case, remove all tests above from exports, and add one dummy
+// test that passes.
+try {
+ require("selection");
+}
+catch (err) {
+ // This bug should be mentioned in the error message.
+ let bug = "https://bugzilla.mozilla.org/show_bug.cgi?id=560716";
+ if (err.message.indexOf(bug) < 0)
+ throw err;
+ for (let [prop, val] in Iterator(exports)) {
+ if (/^test/.test(prop) && typeof(val) === "function")
+ delete exports[prop];
+ }
+ exports.testAppNotSupported = function (test) {
+ test.pass("The selection module does not support this application.");
+ };
+}
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/test-simple-storage.js b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-simple-storage.js
new file mode 100644
index 0000000..6ec2507
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-simple-storage.js
@@ -0,0 +1,346 @@
+/* -*- 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 ***** */
+
+const file = require("file");
+const prefs = require("preferences-service");
+
+const QUOTA_PREF = "extensions.addon-sdk.simple-storage.quota";
+
+let {Cc,Ci} = require("chrome");
+let storeFile = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+storeFile.append("jetpack");
+storeFile.append(packaging.jetpackID);
+storeFile.append("simple-storage");
+storeFile.append("store.json");
+let storeFilename = storeFile.path;
+
+exports.testSetGet = function (test) {
+ test.waitUntilDone();
+
+ // Load the module once, set a value.
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+ manager(loader).jsonStore.onWrite = function (storage) {
+ test.assert(file.exists(storeFilename), "Store file should exist");
+
+ // Load the module again and make sure the value stuck.
+ loader = newLoader(test);
+ ss = loader.require("simple-storage");
+ manager(loader).jsonStore.onWrite = function (storage) {
+ file.remove(storeFilename);
+ test.done();
+ };
+ test.assertEqual(ss.storage.foo, val, "Value should persist");
+ loader.unload();
+ };
+ let val = "foo";
+ ss.storage.foo = val;
+ test.assertEqual(ss.storage.foo, val, "Value read should be value set");
+ loader.unload();
+};
+
+exports.testSetGetRootArray = function (test) {
+ setGetRoot(test, [1, 2, 3], function (arr1, arr2) {
+ if (arr1.length !== arr2.length)
+ return false;
+ for (let i = 0; i < arr1.length; i++) {
+ if (arr1[i] !== arr2[i])
+ return false;
+ }
+ return true;
+ });
+};
+
+exports.testSetGetRootBool = function (test) {
+ setGetRoot(test, true);
+};
+
+exports.testSetGetRootFunction = function (test) {
+ setGetRootError(test, function () {},
+ "Setting storage to a function should fail");
+};
+
+exports.testSetGetRootNull = function (test) {
+ setGetRoot(test, null);
+};
+
+exports.testSetGetRootNumber = function (test) {
+ setGetRoot(test, 3.14);
+};
+
+exports.testSetGetRootObject = function (test) {
+ setGetRoot(test, { foo: 1, bar: 2 }, function (obj1, obj2) {
+ for (let [prop, val] in Iterator(obj1)) {
+ if (!(prop in obj2) || obj2[prop] !== val)
+ return false;
+ }
+ for (let [prop, val] in Iterator(obj2)) {
+ if (!(prop in obj1) || obj1[prop] !== val)
+ return false;
+ }
+ return true;
+ });
+};
+
+exports.testSetGetRootString = function (test) {
+ setGetRoot(test, "sho' 'nuff");
+};
+
+exports.testSetGetRootUndefined = function (test) {
+ setGetRootError(test, undefined, "Setting storage to undefined should fail");
+};
+
+exports.testEmpty = function (test) {
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+ loader.unload();
+ test.assert(!file.exists(storeFilename), "Store file should not exist");
+};
+
+exports.testMalformed = function (test) {
+ let stream = file.open(storeFilename, "w");
+ stream.write("i'm not json");
+ stream.close();
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+ let empty = true;
+ for (let key in ss.storage) {
+ empty = false;
+ break;
+ }
+ test.assert(empty, "Malformed storage should cause root to be empty");
+ loader.unload();
+};
+
+// Go over quota and handle it by listener.
+exports.testQuotaExceededHandle = function (test) {
+ test.waitUntilDone();
+ prefs.set(QUOTA_PREF, 18);
+
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+ ss.on("OverQuota", function () {
+ test.pass("OverQuota was emitted as expected");
+ test.assertEqual(this, ss, "`this` should be simple storage");
+ ss.storage = { x: 4, y: 5 };
+
+ manager(loader).jsonStore.onWrite = function () {
+ loader = newLoader(test);
+ ss = loader.require("simple-storage");
+ let numProps = 0;
+ for (let prop in ss.storage)
+ numProps++;
+ test.assert(numProps, 2,
+ "Store should contain 2 values: " + ss.storage.toSource());
+ test.assertEqual(ss.storage.x, 4, "x value should be correct");
+ test.assertEqual(ss.storage.y, 5, "y value should be correct");
+ manager(loader).jsonStore.onWrite = function (storage) {
+ prefs.reset(QUOTA_PREF);
+ test.done();
+ };
+ loader.unload();
+ };
+ loader.unload();
+ });
+ // This will be JSON.stringify()ed to: {"a":1,"b":2,"c":3} (19 bytes)
+ ss.storage = { a: 1, b: 2, c: 3 };
+ manager(loader).jsonStore.write();
+};
+
+// Go over quota but don't handle it. The last good state should still persist.
+exports.testQuotaExceededNoHandle = function (test) {
+ test.waitUntilDone();
+ prefs.set(QUOTA_PREF, 5);
+
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+
+ manager(loader).jsonStore.onWrite = function (storage) {
+ loader = newLoader(test);
+ ss = loader.require("simple-storage");
+ test.assertEqual(ss.storage, val,
+ "Value should have persisted: " + ss.storage);
+ ss.storage = "some very long string that is very long";
+ ss.on("OverQuota", function () {
+ test.pass("OverQuota emitted as expected");
+ manager(loader).jsonStore.onWrite = function () {
+ test.fail("Over-quota value should not have been written");
+ };
+ loader.unload();
+
+ loader = newLoader(test);
+ ss = loader.require("simple-storage");
+ test.assertEqual(ss.storage, val,
+ "Over-quota value should not have been written, " +
+ "old value should have persisted: " + ss.storage);
+ loader.unload();
+ prefs.reset(QUOTA_PREF);
+ test.done();
+ });
+ manager(loader).jsonStore.write();
+ };
+
+ let val = "foo";
+ ss.storage = val;
+ loader.unload();
+};
+
+exports.testQuotaUsage = function (test) {
+ test.waitUntilDone();
+
+ let quota = 21;
+ prefs.set(QUOTA_PREF, quota);
+
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+
+ // {"a":1} (7 bytes)
+ ss.storage = { a: 1 };
+ test.assertEqual(ss.quotaUsage, 7 / quota, "quotaUsage should be correct");
+
+ // {"a":1,"bb":2} (14 bytes)
+ ss.storage = { a: 1, bb: 2 };
+ test.assertEqual(ss.quotaUsage, 14 / quota, "quotaUsage should be correct");
+
+ // {"a":1,"bb":2,"cc":3} (21 bytes)
+ ss.storage = { a: 1, bb: 2, cc: 3 };
+ test.assertEqual(ss.quotaUsage, 21 / quota, "quotaUsage should be correct");
+
+ manager(loader).jsonStore.onWrite = function () {
+ prefs.reset(QUOTA_PREF);
+ test.done();
+ };
+ loader.unload();
+};
+
+exports.testUninstall = function (test) {
+ test.waitUntilDone();
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+ manager(loader).jsonStore.onWrite = function () {
+ test.assert(file.exists(storeFilename), "Store file should exist");
+
+ loader = newLoader(test);
+ ss = loader.require("simple-storage");
+ loader.unload("uninstall");
+ test.assert(!file.exists(storeFilename), "Store file should be removed");
+ test.done();
+ };
+ ss.storage.foo = "foo";
+ loader.unload();
+};
+
+exports.testSetNoSetRead = function (test) {
+ test.waitUntilDone();
+
+ // Load the module, set a value.
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+ manager(loader).jsonStore.onWrite = function (storage) {
+ test.assert(file.exists(storeFilename), "Store file should exist");
+
+ // Load the module again but don't access ss.storage.
+ loader = newLoader(test);
+ ss = loader.require("simple-storage");
+ manager(loader).jsonStore.onWrite = function (storage) {
+ test.fail("Nothing should be written since `storage` was not accessed.");
+ };
+ loader.unload();
+
+ // Load the module a third time and make sure the value stuck.
+ loader = newLoader(test);
+ ss = loader.require("simple-storage");
+ manager(loader).jsonStore.onWrite = function (storage) {
+ file.remove(storeFilename);
+ test.done();
+ };
+ test.assertEqual(ss.storage.foo, val, "Value should persist");
+ loader.unload();
+ };
+ let val = "foo";
+ ss.storage.foo = val;
+ test.assertEqual(ss.storage.foo, val, "Value read should be value set");
+ loader.unload();
+};
+
+function manager(loader) {
+ return loader.findSandboxForModule("simple-storage").globalScope.manager;
+}
+
+function newLoader(test) {
+ return test.makeSandboxedLoader({ globals: { packaging: packaging } });
+}
+
+function setGetRoot(test, val, compare) {
+ test.waitUntilDone();
+
+ compare = compare || function (a, b) a === b;
+
+ // Load the module once, set a value.
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+ manager(loader).jsonStore.onWrite = function () {
+ test.assert(file.exists(storeFilename), "Store file should exist");
+
+ // Load the module again and make sure the value stuck.
+ loader = newLoader(test);
+ ss = loader.require("simple-storage");
+ manager(loader).jsonStore.onWrite = function () {
+ file.remove(storeFilename);
+ test.done();
+ };
+ test.assert(compare(ss.storage, val), "Value should persist");
+ loader.unload();
+ };
+ ss.storage = val;
+ test.assert(compare(ss.storage, val), "Value read should be value set");
+ loader.unload();
+}
+
+function setGetRootError(test, val, msg) {
+ let pred = "storage must be one of the following types: " +
+ "array, boolean, null, number, object, string";
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+ test.assertRaises(function () ss.storage = val, pred, msg);
+ loader.unload();
+}
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/test-tabs.js b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-tabs.js
new file mode 100644
index 0000000..cbd6808
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-tabs.js
@@ -0,0 +1,904 @@
+/* ***** 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)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+var {Cc,Ci} = require("chrome");
+
+// test tab.activeTab getter
+exports.testActiveTab_getter = function(test) {
+ test.waitUntilDone();
+
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+
+ let url = "data:text/html,<html><head><title>foo</title></head></html>";
+ require("tab-browser").addTab(
+ url,
+ {
+ onLoad: function(e) {
+ test.assert(tabs.activeTab);
+ test.assertEqual(tabs.activeTab.url, url);
+ test.assertEqual(tabs.activeTab.title, "foo");
+ closeBrowserWindow(window, function() test.done());
+ }
+ }
+ );
+ });
+};
+
+// test 'BrowserWindow' instance creation on tab 'activate' event
+// See bug 648244: there was a infinite loop.
+exports.testBrowserWindowCreationOnActivate = function(test) {
+ test.waitUntilDone();
+
+ let windows = require("windows").browserWindows;
+ let tabs = require("tabs");
+
+ let gotActivate = false;
+
+ tabs.once('activate', function onActivate(eventTab) {
+ test.assert(windows.activeWindow, "Is able to fetch activeWindow");
+ gotActivate = true;
+ });
+
+ openBrowserWindow(function(window, browser) {
+ test.assert(gotActivate, "Received activate event before openBrowserWindow's callback is called");
+ closeBrowserWindow(window, function () test.done());
+ });
+}
+
+// test tab.activeTab setter
+exports.testActiveTab_setter = function(test) {
+ test.waitUntilDone();
+
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let url = "data:text/html,<html><head><title>foo</title></head></html>";
+
+ tabs.on('ready', function onReady(tab) {
+ tabs.removeListener('ready', onReady);
+ test.assertEqual(tabs.activeTab.url, "about:blank", "activeTab url has not changed");
+ test.assertEqual(tab.url, url, "url of new background tab matches");
+ tabs.on('activate', function onActivate(eventTab) {
+ tabs.removeListener('activate', onActivate);
+ test.assertEqual(tabs.activeTab.url, url, "url after activeTab setter matches");
+ test.assertEqual(eventTab, tab, "event argument is the activated tab");
+ test.assertEqual(eventTab, tabs.activeTab, "the tab is the active one");
+ closeBrowserWindow(window, function() test.done());
+ });
+ tab.activate();
+ })
+
+ tabs.open({
+ url: url,
+ inBackground: true
+ });
+ });
+};
+
+exports.testAutomaticDestroy = function(test) {
+ test.waitUntilDone();
+
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+
+ // Create a second tab instance that we will destroy
+ let called = false;
+
+ let loader = test.makeSandboxedLoader();
+ let tabs2 = loader.require("tabs");
+ tabs2.on('open', function onOpen(tab) {
+ called = true;
+ });
+
+ loader.unload();
+
+ // Fire a tab event an ensure that this destroyed tab is inactive
+ tabs.once('open', function () {
+ require("timer").setTimeout(function () {
+ test.assert(!called, "Unloaded tab module is destroyed and inactive");
+ closeBrowserWindow(window, function() test.done());
+ }, 0);
+ });
+
+ tabs.open("data:text/html,foo");
+
+ });
+};
+
+// test tab properties
+exports.testTabProperties = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs= require("tabs");
+ let url = "data:text/html,<html><head><title>foo</title></head><body>foo</body></html>";
+ tabs.open({
+ url: url,
+ onReady: function(tab) {
+ test.assertEqual(tab.title, "foo", "title of the new tab matches");
+ test.assertEqual(tab.url, url, "URL of the new tab matches");
+ test.assert(tab.favicon, "favicon of the new tab is not empty");
+ test.assertEqual(tab.style, null, "style of the new tab matches");
+ test.assertEqual(tab.index, 1, "index of the new tab matches");
+ test.assertNotEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ });
+};
+
+// test tabs iterator and length property
+exports.testTabsIteratorAndLength = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let startCount = 0;
+ for each (let t in tabs) startCount++;
+ test.assertEqual(startCount, tabs.length, "length property is correct");
+ let url = "data:text/html,default";
+ tabs.open(url);
+ tabs.open(url);
+ tabs.open({
+ url: url,
+ onOpen: function(tab) {
+ let count = 0;
+ for each (let t in tabs) count++;
+ test.assertEqual(count, startCount + 3, "iterated tab count matches");
+ test.assertEqual(startCount + 3, tabs.length, "iterated tab count matches length property");
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ });
+};
+
+// test tab.url setter
+exports.testTabLocation = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let url1 = "data:text/html,foo";
+ let url2 = "data:text/html,bar";
+
+ tabs.on('ready', function onReady(tab) {
+ if (tab.url != url2)
+ return;
+ tabs.removeListener('ready', onReady);
+ test.pass("tab.load() loaded the correct url");
+ closeBrowserWindow(window, function() test.done());
+ });
+
+ tabs.open({
+ url: url1,
+ onOpen: function(tab) {
+ tab.url = url2
+ }
+ });
+ });
+};
+
+// test tab.close()
+exports.testTabClose = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let url = "data:text/html,foo";
+
+ test.assertNotEqual(tabs.activeTab.url, url, "tab is now the active tab");
+ tabs.on('ready', function onReady(tab) {
+ tabs.removeListener('ready', onReady);
+ test.assertEqual(tabs.activeTab.url, tab.url, "tab is now the active tab");
+ tab.close(function() {
+ closeBrowserWindow(window, function() test.done());
+ });
+ test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
+ });
+
+ tabs.open(url);
+ });
+};
+
+// test tab.reload()
+exports.testTabReload = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let url = "data:text/html,<!doctype%20html><title></title>";
+
+ tabs.open({ url: url, onReady: function onReady(tab) {
+ tab.removeListener("ready", onReady);
+
+ browser.addEventListener(
+ "load",
+ function onLoad() {
+ browser.removeEventListener("load", onLoad, true);
+
+ browser.addEventListener(
+ "load",
+ function onReload() {
+ browser.removeEventListener("load", onReload, true);
+ test.pass("the tab was loaded again");
+ test.assertEqual(tab.url, url, "the tab has the same URL");
+ closeBrowserWindow(window, function() test.done());
+ },
+ true
+ );
+ tab.reload();
+ },
+ true
+ );
+ }});
+ });
+};
+
+// test tab.move()
+exports.testTabMove = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let url = "data:text/html,foo";
+
+ tabs.open({
+ url: url,
+ onOpen: function(tab) {
+ test.assertEqual(tab.index, 1, "tab index before move matches");
+ tab.index = 0;
+ test.assertEqual(tab.index, 0, "tab index after move matches");
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ });
+};
+
+// open tab with default options
+exports.testOpen = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let url = "data:text/html,default";
+ tabs.open({
+ url: url,
+ onReady: function(tab) {
+ test.assertEqual(tab.url, url, "URL of the new tab matches");
+ test.assertEqual(window.content.location, url, "URL of active tab in the current window matches");
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ });
+};
+
+// open pinned tab
+exports.testOpenPinned = function(test) {
+ const xulApp = require("xul-app");
+ if (xulApp.versionInRange(xulApp.platformVersion, "2.0b2", "*")) {
+ // test tab pinning
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let url = "data:text/html,default";
+ tabs.open({
+ url: url,
+ isPinned: true,
+ onOpen: function(tab) {
+ test.assertEqual(tab.isPinned, true, "The new tab is pinned");
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ });
+ }
+ else {
+ test.pass("Pinned tabs are not supported in this application.");
+ }
+};
+
+// pin/unpin opened tab
+exports.testPinUnpin = function(test) {
+ const xulApp = require("xul-app");
+ if (xulApp.versionInRange(xulApp.platformVersion, "2.0b2", "*")) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let url = "data:text/html,default";
+ tabs.open({
+ url: url,
+ onOpen: function(tab) {
+ tab.pin();
+ test.assertEqual(tab.isPinned, true, "The tab was pinned correctly");
+ tab.unpin();
+ test.assertEqual(tab.isPinned, false, "The tab was unpinned correctly");
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ });
+ }
+ else {
+ test.pass("Pinned tabs are not supported in this application.");
+ }
+};
+
+// open tab in background
+exports.testInBackground = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let activeUrl = tabs.activeTab.url;
+ let url = "data:text/html,background";
+ test.assertEqual(activeWindow, window, "activeWindow matches this window");
+ tabs.on('ready', function onReady(tab) {
+ tabs.removeListener('ready', onReady);
+ test.assertEqual(tabs.activeTab.url, activeUrl, "URL of active tab has not changed");
+ test.assertEqual(tab.url, url, "URL of the new background tab matches");
+ test.assertEqual(activeWindow, window, "a new window was not opened");
+ test.assertNotEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL");
+ closeBrowserWindow(window, function() test.done());
+ });
+ tabs.open({
+ url: url,
+ inBackground: true
+ });
+ });
+};
+
+// open tab in new window
+exports.testOpenInNewWindow = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+
+ let cache = [];
+ let windowUtils = require("window-utils");
+ let wt = new windowUtils.WindowTracker({
+ onTrack: function(win) {
+ cache.push(win);
+ },
+ onUntrack: function(win) {
+ cache.splice(cache.indexOf(win), 1)
+ }
+ });
+ let startWindowCount = cache.length;
+
+ let url = "data:text/html,newwindow";
+ tabs.open({
+ url: url,
+ inNewWindow: true,
+ onReady: function(tab) {
+ let newWindow = cache[cache.length - 1];
+ test.assertEqual(cache.length, startWindowCount + 1, "a new window was opened");
+ test.assertEqual(activeWindow, newWindow, "new window is active");
+ test.assertEqual(tab.url, url, "URL of the new tab matches");
+ test.assertEqual(newWindow.content.location, url, "URL of new tab in new window matches");
+ test.assertEqual(tabs.activeTab.url, url, "URL of activeTab matches");
+ for (var i in cache) cache[i] = null;
+ wt.unload();
+ closeBrowserWindow(newWindow, function() {
+ closeBrowserWindow(window, function() test.done());
+ });
+ }
+ });
+ });
+};
+
+// onOpen event handler
+exports.testTabsEvent_onOpen = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("tabs");
+ let url = "data:text/html,1";
+ let eventCount = 0;
+
+ // add listener via property assignment
+ function listener1(tab) {
+ eventCount++;
+ };
+ tabs.on('open', listener1);
+
+ // add listener via collection add
+ tabs.on('open', function listener2(tab) {
+ test.assertEqual(++eventCount, 2, "both listeners notified");
+ tabs.removeListener('open', listener1);
+ tabs.removeListener('open', listener2);
+ closeBrowserWindow(window, function() test.done());
+ });
+
+ tabs.open(url);
+ });
+};
+
+// onClose event handler
+exports.testTabsEvent_onClose = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("tabs");
+ let url = "data:text/html,onclose";
+ let eventCount = 0;
+
+ // add listener via property assignment
+ function listener1(tab) {
+ eventCount++;
+ }
+ tabs.on('close', listener1);
+
+ // add listener via collection add
+ tabs.on('close', function listener2(tab) {
+ test.assertEqual(++eventCount, 2, "both listeners notified");
+ tabs.removeListener('close', listener1);
+ tabs.removeListener('close', listener2);
+ closeBrowserWindow(window, function() test.done());
+ });
+
+ tabs.on('ready', function onReady(tab) {
+ tabs.removeListener('ready', onReady);
+ tab.close();
+ });
+
+ tabs.open(url);
+ });
+};
+
+// onClose event handler when a window is closed
+exports.testTabsEvent_onCloseWindow = function(test) {
+ test.waitUntilDone();
+
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("tabs");
+
+ let closeCount = 0, individualCloseCount = 0;
+ function listener() {
+ closeCount++;
+ }
+ tabs.on('close', listener);
+
+ // One tab is already open with the window
+ let openTabs = 1;
+ function testCasePossiblyLoaded() {
+ if (++openTabs == 4) {
+ beginCloseWindow();
+ }
+ }
+
+ tabs.open({
+ url: "data:text/html,tab2",
+ onOpen: function() testCasePossiblyLoaded(),
+ onClose: function() individualCloseCount++
+ });
+
+ tabs.open({
+ url: "data:text/html,tab3",
+ onOpen: function() testCasePossiblyLoaded(),
+ onClose: function() individualCloseCount++
+ });
+
+ tabs.open({
+ url: "data:text/html,tab4",
+ onOpen: function() testCasePossiblyLoaded(),
+ onClose: function() individualCloseCount++
+ });
+
+ function beginCloseWindow() {
+ closeBrowserWindow(window, function testFinished() {
+ tabs.removeListener("close", listener);
+
+ test.assertEqual(closeCount, 4, "Correct number of close events received");
+ test.assertEqual(individualCloseCount, 3,
+ "Each tab with an attached onClose listener received a close " +
+ "event when the window was closed");
+
+ test.done();
+ });
+ }
+
+ });
+}
+
+// onReady event handler
+exports.testTabsEvent_onReady = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("tabs");
+ let url = "data:text/html,onready";
+ let eventCount = 0;
+
+ // add listener via property assignment
+ function listener1(tab) {
+ eventCount++;
+ };
+ tabs.on('ready', listener1);
+
+ // add listener via collection add
+ tabs.on('ready', function listener2(tab) {
+ test.assertEqual(++eventCount, 2, "both listeners notified");
+ tabs.removeListener('ready', listener1);
+ tabs.removeListener('ready', listener2);
+ closeBrowserWindow(window, function() test.done());
+ });
+
+ tabs.open(url);
+ });
+};
+
+// onActivate event handler
+exports.testTabsEvent_onActivate = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("tabs");
+ let url = "data:text/html,onactivate";
+ let eventCount = 0;
+
+ // add listener via property assignment
+ function listener1(tab) {
+ eventCount++;
+ };
+ tabs.on('activate', listener1);
+
+ // add listener via collection add
+ tabs.on('activate', function listener2(tab) {
+ test.assertEqual(++eventCount, 2, "both listeners notified");
+ tabs.removeListener('activate', listener1);
+ tabs.removeListener('activate', listener2);
+ closeBrowserWindow(window, function() test.done());
+ });
+
+ tabs.open(url);
+ });
+};
+
+// onDeactivate event handler
+exports.testTabsEvent_onDeactivate = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("tabs");
+ let url = "data:text/html,ondeactivate";
+ let eventCount = 0;
+
+ // add listener via property assignment
+ function listener1(tab) {
+ eventCount++;
+ };
+ tabs.on('deactivate', listener1);
+
+ // add listener via collection add
+ tabs.on('deactivate', function listener2(tab) {
+ test.assertEqual(++eventCount, 2, "both listeners notified");
+ tabs.removeListener('deactivate', listener1);
+ tabs.removeListener('deactivate', listener2);
+ closeBrowserWindow(window, function() test.done());
+ });
+
+ tabs.on('open', function onOpen(tab) {
+ tabs.removeListener('open', onOpen);
+ tabs.open("data:text/html,foo");
+ });
+
+ tabs.open(url);
+ });
+};
+
+// per-tab event handlers
+exports.testPerTabEvents = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("tabs");
+ let eventCount = 0;
+
+ tabs.open({
+ url: "data:text/html,foo",
+ onOpen: function(tab) {
+ // add listener via property assignment
+ function listener1() {
+ eventCount++;
+ };
+ tab.on('ready', listener1);
+
+ // add listener via collection add
+ tab.on('ready', function listener2() {
+ test.assertEqual(eventCount, 1, "both listeners notified");
+ tab.removeListener('ready', listener1);
+ tab.removeListener('ready', listener2);
+ closeBrowserWindow(window, function() test.done());
+ });
+ }
+ });
+ });
+};
+
+exports.testAttachOnOpen = function (test) {
+ // Take care that attach has to be called on tab ready and not on tab open.
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+
+ tabs.open({
+ url: "data:text/html,foobar",
+ onOpen: function (tab) {
+ let worker = tab.attach({
+ contentScript: 'self.postMessage(document.location.href); ',
+ onMessage: function (msg) {
+ test.assertEqual(msg, "about:blank",
+ "Worker document url is about:blank on open");
+ worker.destroy();
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ }
+ });
+
+ });
+}
+
+exports.testAttachOnMultipleDocuments = function (test) {
+ // Example of attach that process multiple tab documents
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let firstLocation = "data:text/html,foobar";
+ let secondLocation = "data:text/html,bar";
+ let thirdLocation = "data:text/html,fox";
+ let onReadyCount = 0;
+ let worker1 = null;
+ let worker2 = null;
+ let detachEventCount = 0;
+ tabs.open({
+ url: firstLocation,
+ onReady: function (tab) {
+ onReadyCount++;
+ if (onReadyCount == 1) {
+ worker1 = tab.attach({
+ contentScript: 'self.on("message", ' +
+ ' function () self.postMessage(document.location.href)' +
+ ');',
+ onMessage: function (msg) {
+ test.assertEqual(msg, firstLocation,
+ "Worker url is equal to the 1st document");
+ tab.url = secondLocation;
+ },
+ onDetach: function () {
+ detachEventCount++;
+ test.pass("Got worker1 detach event");
+ test.assertRaises(function () {
+ worker1.postMessage("ex-1");
+ },
+ /The page has been destroyed/,
+ "postMessage throw because worker1 is destroyed");
+ checkEnd();
+ }
+ });
+ worker1.postMessage("new-doc-1");
+ }
+ else if (onReadyCount == 2) {
+
+ worker2 = tab.attach({
+ contentScript: 'self.on("message", ' +
+ ' function () self.postMessage(document.location.href)' +
+ ');',
+ onMessage: function (msg) {
+ test.assertEqual(msg, secondLocation,
+ "Worker url is equal to the 2nd document");
+ tab.url = thirdLocation;
+ },
+ onDetach: function () {
+ detachEventCount++;
+ test.pass("Got worker2 detach event");
+ test.assertRaises(function () {
+ worker2.postMessage("ex-2");
+ },
+ /The page has been destroyed/,
+ "postMessage throw because worker2 is destroyed");
+ checkEnd();
+ }
+ });
+ worker2.postMessage("new-doc-2");
+ }
+ else if (onReadyCount == 3) {
+
+ tab.close();
+
+ }
+
+ }
+ });
+
+ function checkEnd() {
+ if (detachEventCount != 2)
+ return;
+
+ test.pass("Got all detach events");
+
+ closeBrowserWindow(window, function() test.done());
+ }
+
+ });
+}
+
+
+exports.testAttachWrappers = function (test) {
+ // Check that content script has access to wrapped values by default
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let document = "data:text/html,<script>var globalJSVar = true; " +
+ " document.getElementById = 3;</script>";
+ let count = 0;
+
+ tabs.open({
+ url: document,
+ onReady: function (tab) {
+ let worker = tab.attach({
+ contentScript: 'try {' +
+ ' self.postMessage(!("globalJSVar" in window));' +
+ ' self.postMessage(typeof window.globalJSVar == "undefined");' +
+ '} catch(e) {' +
+ ' self.postMessage(e.message);' +
+ '}',
+ onMessage: function (msg) {
+ test.assertEqual(msg, true, "Worker has wrapped objects ("+count+")");
+ if (count++ == 1)
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ }
+ });
+
+ });
+}
+
+/*
+// We do not offer unwrapped access to DOM since bug 601295 landed
+// See 660780 to track progress of unwrap feature
+exports.testAttachUnwrapped = function (test) {
+ // Check that content script has access to unwrapped values through unsafeWindow
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let document = "data:text/html,<script>var globalJSVar=true;</script>";
+ let count = 0;
+
+ tabs.open({
+ url: document,
+ onReady: function (tab) {
+ let worker = tab.attach({
+ contentScript: 'try {' +
+ ' self.postMessage(unsafeWindow.globalJSVar);' +
+ '} catch(e) {' +
+ ' self.postMessage(e.message);' +
+ '}',
+ onMessage: function (msg) {
+ test.assertEqual(msg, true, "Worker has access to javascript content globals ("+count+")");
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ }
+ });
+
+ });
+}
+*/
+
+exports['test window focus changes active tab'] = function(test) {
+ test.waitUntilDone();
+ let win1 = openBrowserWindow(function() {
+ let win2 = openBrowserWindow(function() {
+ let tabs = require("tabs");
+ tabs.on("activate", function onActivate() {
+ tabs.removeListener("activate", onActivate);
+ test.pass("activate was called on windows focus change.");
+ closeBrowserWindow(win1, function() {
+ closeBrowserWindow(win2, function() { test.done(); });
+ });
+ });
+ win1.focus();
+ }, "data:text/html,test window focus changes active tab</br><h1>Window #2");
+ }, "data:text/html,test window focus changes active tab</br><h1>Window #1");
+};
+
+exports['test ready event on new window tab'] = function(test) {
+ test.waitUntilDone();
+ let uri = encodeURI("data:text/html,Waiting for ready event!");
+
+ require("tabs").on("ready", function onReady(tab) {
+ if (tab.url === uri) {
+ require("tabs").removeListener("ready", onReady);
+ test.pass("ready event was emitted");
+ closeBrowserWindow(window, function() {
+ test.done();
+ });
+ }
+ });
+
+ let window = openBrowserWindow(function(){}, uri);
+};
+/******************* helpers *********************/
+
+// Helper for getting the active window
+this.__defineGetter__("activeWindow", function activeWindow() {
+ return Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator).
+ getMostRecentWindow("navigator:browser");
+});
+
+// Utility function to open a new browser window.
+function openBrowserWindow(callback, url) {
+ let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher);
+ let urlString = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ urlString.data = url;
+ let window = ww.openWindow(null, "chrome://browser/content/browser.xul",
+ "_blank", "chrome,all,dialog=no", urlString);
+
+ if (callback) {
+ window.addEventListener("load", function onLoad(event) {
+ if (event.target && event.target.defaultView == window) {
+ window.removeEventListener("load", onLoad, true);
+ let browsers = window.document.getElementsByTagName("tabbrowser");
+ try {
+ require("timer").setTimeout(function () {
+ callback(window, browsers[0]);
+ }, 10);
+ } catch (e) { console.exception(e); }
+ }
+ }, true);
+ }
+
+ return window;
+}
+
+// Helper for calling code at window close
+function closeBrowserWindow(window, callback) {
+ window.addEventListener("unload", function unload() {
+ window.removeEventListener("unload", unload, false);
+ callback();
+ }, false);
+ window.close();
+}
+
+// If the module doesn't support the app we're being run in, require() will
+// throw. In that case, remove all tests above from exports, and add one dummy
+// test that passes.
+try {
+ require("tabs");
+}
+catch (err) {
+ // This bug should be mentioned in the error message.
+ let bug = "https://bugzilla.mozilla.org/show_bug.cgi?id=560716";
+ if (err.message.indexOf(bug) < 0)
+ throw err;
+ for (let [prop, val] in Iterator(exports)) {
+ if (/^test/.test(prop) && typeof(val) === "function")
+ delete exports[prop];
+ }
+ exports.testAppNotSupported = function (test) {
+ test.pass("the tabs module does not support this application.");
+ };
+}
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/test-timers.js b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-timers.js
new file mode 100644
index 0000000..98ea613
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-timers.js
@@ -0,0 +1,44 @@
+/* ***** 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):
+ * Shane Tomlinson <stomlinson@mozilla.com> (Original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+const timers = require("timers");
+
+exports.testTimeout = function (test) {
+ test.waitUntilDone();
+ timers.setTimeout(function () {
+ test.pass("timers.setTimeout works");
+ test.done();
+ }, 0);
+}
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/test-widget.js b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-widget.js
new file mode 100644
index 0000000..0845517
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-widget.js
@@ -0,0 +1,909 @@
+const {Cc,Ci} = require("chrome");
+
+exports.testConstructor = function(test) {
+
+ const tabBrowser = require("tab-browser");
+
+ test.waitUntilDone(30000);
+
+ const widgets = require("widget");
+ const url = require("url");
+ const windowUtils = require("window-utils");
+
+ let browserWindow = windowUtils.activeBrowserWindow;
+ let doc = browserWindow.document;
+ let AddonsMgrListener = browserWindow.AddonsMgrListener;
+
+ function container() doc.getElementById("addon-bar");
+ function widgetCount() container() ? container().getElementsByTagName("toolbaritem").length : 0;
+ let widgetStartCount = widgetCount();
+ function widgetNode(index) container() ? container().getElementsByTagName("toolbaritem")[index] : null;
+
+ // Test basic construct/destroy
+ AddonsMgrListener.onInstalling();
+ let w = widgets.Widget({ id: "fooID", label: "foo", content: "bar" });
+ AddonsMgrListener.onInstalled();
+ test.assertEqual(widgetCount(), widgetStartCount + 1, "panel has correct number of child elements after widget construction");
+
+ // test widget height
+ test.assertEqual(widgetNode(0).firstChild.boxObject.height, 16, "widget has correct default height");
+
+ AddonsMgrListener.onUninstalling();
+ w.destroy();
+ AddonsMgrListener.onUninstalled();
+ w.destroy();
+ test.pass("Multiple destroys do not cause an error");
+ test.assertEqual(widgetCount(), widgetStartCount, "panel has correct number of child elements after destroy");
+
+ // Test automatic widget destroy on unload
+ let loader = test.makeSandboxedLoader();
+ let widgetsFromLoader = loader.require("widget");
+ let widgetStartCount = widgetCount();
+ let w = widgetsFromLoader.Widget({ id: "fooID", label: "foo", content: "bar" });
+ test.assertEqual(widgetCount(), widgetStartCount + 1, "widget has been correctly added");
+ loader.unload();
+ test.assertEqual(widgetCount(), widgetStartCount, "widget has been destroyed on module unload");
+
+ // Test nothing
+ test.assertRaises(
+ function() widgets.Widget({}),
+ "The widget must have a non-empty label property.",
+ "throws on no properties");
+
+ // Test no label
+ test.assertRaises(
+ function() widgets.Widget({content: "foo"}),
+ "The widget must have a non-empty label property.",
+ "throws on no label");
+
+ // Test empty label
+ test.assertRaises(
+ function() widgets.Widget({label: "", content: "foo"}),
+ "The widget must have a non-empty label property.",
+ "throws on empty label");
+
+ // Test no content or image
+ test.assertRaises(
+ function() widgets.Widget({id: "fooID", label: "foo"}),
+ "No content or contentURL property found. Widgets must have one or the other.",
+ "throws on no content");
+
+ // Test empty content, no image
+ test.assertRaises(
+ function() widgets.Widget({id:"fooID", label: "foo", content: ""}),
+ "No content or contentURL property found. Widgets must have one or the other.",
+ "throws on empty content");
+
+ // Test empty image, no content
+ test.assertRaises(
+ function() widgets.Widget({id:"fooID", label: "foo", image: ""}),
+ "No content or contentURL property found. Widgets must have one or the other.",
+ "throws on empty content");
+
+ // Test empty content, empty image
+ test.assertRaises(
+ function() widgets.Widget({id:"fooID", label: "foo", content: "", image: ""}),
+ "No content or contentURL property found. Widgets must have one or the other.",
+ "throws on empty content");
+
+ // Test duplicated ID
+ let duplicateID = widgets.Widget({id: "foo", label: "foo", content: "bar"});
+ test.assertRaises(
+ function() widgets.Widget({id: "foo", label: "bar", content: "bar"}),
+ /This widget ID is already used:/,
+ "throws on duplicated id");
+ duplicateID.destroy();
+
+ // Test duplicate label, different ID
+ let w1 = widgets.Widget({id: "id1", label: "foo", content: "bar"});
+ let w2 = widgets.Widget({id: "id2", label: "foo", content: "bar"});
+ w1.destroy();
+ w2.destroy();
+
+ // Test position restore on create/destroy/create
+ // Create 3 ordered widgets
+ let w1 = widgets.Widget({id: "first", label:"first", content: "bar"});
+ let w2 = widgets.Widget({id: "second", label:"second", content: "bar"});
+ let w3 = widgets.Widget({id: "third", label:"third", content: "bar"});
+ // Remove the middle widget
+ test.assertEqual(widgetNode(1).getAttribute("label"), "second", "second widget is the second widget inserted");
+ w2.destroy();
+ test.assertEqual(widgetNode(1).getAttribute("label"), "third", "second widget is removed, so second widget is now the third one");
+ w2 = widgets.Widget({id: "second", label:"second", content: "bar"});
+ test.assertEqual(widgetNode(1).getAttribute("label"), "second", "second widget is created again, at the same location");
+ // Cleanup this testcase
+ AddonsMgrListener.onUninstalling();
+ w1.destroy();
+ w2.destroy();
+ w3.destroy();
+ AddonsMgrListener.onUninstalled();
+
+ // Test concurrent widget module instances on addon-bar hiding
+ let loader = test.makeSandboxedLoader();
+ let anotherWidgetsInstance = loader.require("widget");
+ test.assert(container().collapsed, "UI is hidden when no widgets");
+ AddonsMgrListener.onInstalling();
+ let w1 = widgets.Widget({id: "foo", label: "foo", content: "bar"});
+ // Ideally we would let AddonsMgrListener display the addon bar
+ // But, for now, addon bar is immediatly displayed by sdk code
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=627484
+ test.assert(!container().collapsed, "UI is already visible when we just added the widget");
+ AddonsMgrListener.onInstalled();
+ test.assert(!container().collapsed, "UI become visible when we notify AddonsMgrListener about end of addon installation");
+ let w2 = anotherWidgetsInstance.Widget({id: "bar", label: "bar", content: "foo"});
+ test.assert(!container().collapsed, "UI still visible when we add a second widget");
+ AddonsMgrListener.onUninstalling();
+ w1.destroy();
+ AddonsMgrListener.onUninstalled();
+ test.assert(!container().collapsed, "UI still visible when we remove one of two widgets");
+ AddonsMgrListener.onUninstalling();
+ w2.destroy();
+ test.assert(!container().collapsed, "UI is still visible when we have removed all widget but still not called onUninstalled");
+ AddonsMgrListener.onUninstalled();
+ test.assert(container().collapsed, "UI is hidden when we have removed all widget and called onUninstalled");
+
+ // Helper for testing a single widget.
+ // Confirms proper addition and content setup.
+ function testSingleWidget(widgetOptions) {
+ // We have to display which test is being run, because here we do not
+ // use the regular test framework but rather a custom one that iterates
+ // the `tests` array.
+ console.info("executing: " + widgetOptions.id);
+
+ let startCount = widgetCount();
+ let widget = widgets.Widget(widgetOptions);
+ let node = widgetNode(startCount);
+ test.assert(node, "widget node at index");
+ test.assertEqual(node.tagName, "toolbaritem", "widget element is correct");
+ test.assertEqual(widget.width + "px", node.style.minWidth, "widget width is correct");
+ test.assertEqual(widgetCount(), startCount + 1, "container has correct number of child elements");
+ let content = node.firstElementChild;
+ test.assert(content, "found content");
+ test.assertMatches(content.tagName, /iframe|image/, "content is iframe or image");
+ return widget;
+ }
+
+ // Array of widgets to test
+ // and a function to test them.
+ let tests = [];
+ function nextTest() {
+ test.assertEqual(widgetCount(), 0, "widget in last test property cleaned itself up");
+ if (!tests.length)
+ test.done();
+ else
+ require("timer").setTimeout(tests.shift(), 0);
+ }
+ function doneTest() nextTest();
+
+ // text widget
+ tests.push(function testTextWidget() testSingleWidget({
+ id: "text",
+ label: "text widget",
+ content: "oh yeah",
+ contentScript: "self.postMessage(document.body.innerHTML);",
+ contentScriptWhen: "end",
+ onMessage: function (message) {
+ test.assertEqual(this.content, message, "content matches");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // html widget
+ tests.push(function testHTMLWidget() testSingleWidget({
+ id: "html",
+ label: "html widget",
+ content: "<div>oh yeah</div>",
+ contentScript: "self.postMessage(document.body.innerHTML);",
+ contentScriptWhen: "end",
+ onMessage: function (message) {
+ test.assertEqual(this.content, message, "content matches");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // image url widget
+ tests.push(function testImageURLWidget() testSingleWidget({
+ id: "image",
+ label: "image url widget",
+ contentURL: require("self").data.url("test.html"),
+ contentScript: "self.postMessage({title: document.title, " +
+ "tag: document.body.firstElementChild.tagName, " +
+ "content: document.body.firstElementChild.innerHTML});",
+ contentScriptWhen: "end",
+ onMessage: function (message) {
+ test.assertEqual(message.title, "foo", "title matches");
+ test.assertEqual(message.tag, "P", "element matches");
+ test.assertEqual(message.content, "bar", "element content matches");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // web uri widget
+ tests.push(function testWebURIWidget() testSingleWidget({
+ id: "web",
+ label: "web uri widget",
+ contentURL: require("self").data.url("test.html"),
+ contentScript: "self.postMessage({title: document.title, " +
+ "tag: document.body.firstElementChild.tagName, " +
+ "content: document.body.firstElementChild.innerHTML});",
+ contentScriptWhen: "end",
+ onMessage: function (message) {
+ test.assertEqual(message.title, "foo", "title matches");
+ test.assertEqual(message.tag, "P", "element matches");
+ test.assertEqual(message.content, "bar", "element content matches");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // event: onclick + content
+ tests.push(function testOnclickEventContent() testSingleWidget({
+ id: "click",
+ label: "click test widget - content",
+ content: "<div id='me'>foo</div>",
+ contentScript: "var evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('click', true, true ); " +
+ "document.getElementById('me').dispatchEvent(evt);",
+ contentScriptWhen: "end",
+ onClick: function() {
+ test.pass("onClick called");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // event: onmouseover + content
+ tests.push(function testOnmouseoverEventContent() testSingleWidget({
+ id: "mouseover",
+ label: "mouseover test widget - content",
+ content: "<div id='me'>foo</div>",
+ contentScript: "var evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('mouseover', true, true ); " +
+ "document.getElementById('me').dispatchEvent(evt);",
+ contentScriptWhen: "end",
+ onMouseover: function() {
+ test.pass("onMouseover called");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // event: onmouseout + content
+ tests.push(function testOnmouseoutEventContent() testSingleWidget({
+ id: "mouseout",
+ label: "mouseout test widget - content",
+ content: "<div id='me'>foo</div>",
+ contentScript: "var evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('mouseout', true, true ); " +
+ "document.getElementById('me').dispatchEvent(evt);",
+ contentScriptWhen: "end",
+ onMouseout: function() {
+ test.pass("onMouseout called");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // event: onclick + image
+ tests.push(function testOnclickEventImage() testSingleWidget({
+ id: "click",
+ label: "click test widget - image",
+ contentURL: require("self").data.url("moz_favicon.ico"),
+ contentScript: "var evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('click', true, true ); " +
+ "document.body.firstElementChild.dispatchEvent(evt);",
+ contentScriptWhen: "end",
+ onClick: function() {
+ test.pass("onClick called");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // event: onmouseover + image
+ tests.push(function testOnmouseoverEventImage() testSingleWidget({
+ id: "mouseover",
+ label: "mouseover test widget - image",
+ contentURL: require("self").data.url("moz_favicon.ico"),
+ contentScript: "var evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('mouseover', true, true ); " +
+ "document.body.firstElementChild.dispatchEvent(evt);",
+ contentScriptWhen: "end",
+ onMouseover: function() {
+ test.pass("onMouseover called");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // event: onmouseout + image
+ tests.push(function testOnmouseoutEventImage() testSingleWidget({
+ id: "mouseout",
+ label: "mouseout test widget - image",
+ contentURL: require("self").data.url("moz_favicon.ico"),
+ contentScript: "var evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('mouseout', true, true ); " +
+ "document.body.firstElementChild.dispatchEvent(evt);",
+ contentScriptWhen: "end",
+ onMouseout: function() {
+ test.pass("onMouseout called");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // test multiple widgets
+ tests.push(function testMultipleWidgets() {
+ let w1 = widgets.Widget({id: "first", label: "first widget", content: "first content"});
+ let w2 = widgets.Widget({id: "second", label: "second widget", content: "second content"});
+
+ w1.destroy();
+ w2.destroy();
+
+ doneTest();
+ });
+
+ // test updating widget content
+ let loads = 0;
+ tests.push(function testUpdatingWidgetContent() testSingleWidget({
+ id: "content",
+ label: "content update test widget",
+ content: "<div id='me'>foo</div>",
+ contentScript: "self.postMessage(1)",
+ contentScriptWhen: "ready",
+ onMessage: function(message) {
+ if (!this.flag) {
+ this.content = "<div id='me'>bar</div>";
+ this.flag = 1;
+ }
+ else {
+ test.assertEqual(this.content, "<div id='me'>bar</div>");
+ this.destroy();
+ doneTest();
+ }
+ }
+ }));
+
+ // test updating widget contentURL
+ let url1 = "data:text/html,<body>foodle</body>";
+ let url2 = "data:text/html,<body>nistel</body>";
+
+ tests.push(function testUpdatingContentURL() testSingleWidget({
+ id: "content",
+ label: "content update test widget",
+ contentURL: url1,
+ contentScript: "self.postMessage(document.location.href);",
+ contentScriptWhen: "end",
+ onMessage: function(message) {
+ if (!this.flag) {
+ test.assertEqual(this.contentURL.toString(), url1);
+ test.assertEqual(message, url1);
+ this.contentURL = url2;
+ this.flag = 1;
+ }
+ else {
+ test.assertEqual(this.contentURL.toString(), url2);
+ test.assertEqual(message, url2);
+ this.destroy();
+ doneTest();
+ }
+ }
+ }));
+
+ // test tooltip
+ tests.push(function testTooltip() testSingleWidget({
+ id: "text",
+ label: "text widget",
+ content: "oh yeah",
+ tooltip: "foo",
+ contentScript: "self.postMessage(1)",
+ contentScriptWhen: "ready",
+ onMessage: function(message) {
+ test.assertEqual(this.tooltip, "foo", "tooltip matches");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // test tooltip fallback to label
+ tests.push(function testTooltipFallback() testSingleWidget({
+ id: "fallback",
+ label: "fallback",
+ content: "oh yeah",
+ contentScript: "self.postMessage(1)",
+ contentScriptWhen: "ready",
+ onMessage: function(message) {
+ test.assertEqual(this.tooltip, this.label, "tooltip fallbacks to label");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // test updating widget tooltip
+ let updated = false;
+ tests.push(function testUpdatingTooltip() testSingleWidget({
+ id: "tooltip",
+ label: "tooltip update test widget",
+ tooltip: "foo",
+ content: "<div id='me'>foo</div>",
+ contentScript: "self.postMessage(1)",
+ contentScriptWhen: "ready",
+ onMessage: function(message) {
+ this.tooltip = "bar";
+ test.assertEqual(this.tooltip, "bar", "tooltip gets updated");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // test multiple windows
+ tests.push(function testMultipleWindows() {
+ tabBrowser.addTab("about:blank", { inNewWindow: true, onLoad: function(e) {
+ let browserWindow = e.target.defaultView;
+ let doc = browserWindow.document;
+ function container() doc.getElementById("addon-bar");
+ function widgetCount2() container() ? container().childNodes.length : 0;
+ let widgetStartCount2 = widgetCount2();
+
+ let w1Opts = {id:"first", label: "first widget", content: "first content"};
+ let w1 = testSingleWidget(w1Opts);
+ test.assertEqual(widgetCount2(), widgetStartCount2 + 1, "2nd window has correct number of child elements after first widget");
+
+ let w2Opts = {id:"second", label: "second widget", content: "second content"};
+ let w2 = testSingleWidget(w2Opts);
+ test.assertEqual(widgetCount2(), widgetStartCount2 + 2, "2nd window has correct number of child elements after second widget");
+
+ w1.destroy();
+ test.assertEqual(widgetCount2(), widgetStartCount2 + 1, "2nd window has correct number of child elements after first destroy");
+ w2.destroy();
+ test.assertEqual(widgetCount2(), widgetStartCount2, "2nd window has correct number of child elements after second destroy");
+
+ closeBrowserWindow(browserWindow, function() {
+ doneTest();
+ });
+ }});
+ });
+
+ // test window closing
+ tests.push(function testWindowClosing() {
+ // 1/ Create a new widget
+ let w1Opts = {
+ id:"first",
+ label: "first widget",
+ content: "first content",
+ contentScript: "self.port.on('event', function () self.port.emit('event'))"
+ };
+ let widget = testSingleWidget(w1Opts);
+ let windows = require("windows").browserWindows;
+
+ // 2/ Retrieve a WidgetView for the initial browser window
+ let acceptDetach = false;
+ let mainView = widget.getView(windows.activeWindow);
+ test.assert(mainView, "Got first widget view");
+ mainView.on("detach", function () {
+ // 8/ End of our test. Accept detach event only when it occurs after
+ // widget.destroy()
+ if (acceptDetach)
+ doneTest();
+ else
+ test.fail("View on initial window should not be destroyed");
+ });
+ mainView.port.on("event", function () {
+ // 7/ Receive event sent during 6/ and cleanup our test
+ acceptDetach = true;
+ widget.destroy();
+ });
+
+ // 3/ First: open a new browser window
+ windows.open({
+ url: "about:blank",
+ onOpen: function(window) {
+ // 4/ Retrieve a WidgetView for this new window
+ let view = widget.getView(window);
+ test.assert(view, "Got second widget view");
+ view.port.on("event", function () {
+ test.fail("We should not receive event on the detach view");
+ });
+ view.on("detach", function () {
+ // The related view is destroyed
+ // 6/ Send a custom event
+ test.assertRaises(function () {
+ view.port.emit("event");
+ },
+ /The widget has been destroyed and can no longer be used./,
+ "emit on a destroyed view should throw");
+ widget.port.emit("event");
+ });
+
+ // 5/ Destroy this window
+ window.close();
+ }
+ });
+ });
+
+ tests.push(function testAddonBarHide() {
+ // Hide the addon-bar
+ browserWindow.setToolbarVisibility(container(), false);
+
+ // Then open a browser window and verify that the addon-bar remains hidden
+ tabBrowser.addTab("about:blank", { inNewWindow: true, onLoad: function(e) {
+ let browserWindow = e.target.defaultView;
+ let doc = browserWindow.document;
+ function container2() doc.getElementById("addon-bar");
+ function widgetCount2() container2() ? container2().childNodes.length : 0;
+ let widgetStartCount2 = widgetCount2();
+
+ let w1Opts = {id:"first", label: "first widget", content: "first content"};
+ let w1 = testSingleWidget(w1Opts);
+ test.assertEqual(widgetCount2(), widgetStartCount2 + 1, "2nd window has correct number of child elements after widget creation");
+
+ w1.destroy();
+ test.assertEqual(widgetCount2(), widgetStartCount2, "2nd window has correct number of child elements after widget destroy");
+
+ test.assert(container().collapsed, "1st window has an hidden addon-bar");
+ test.assert(container2().collapsed, "2nd window has an hidden addon-bar");
+
+ browserWindow.setToolbarVisibility(container(), true);
+
+ closeBrowserWindow(browserWindow, function() {
+ doneTest();
+ });
+ }});
+ });
+
+ // test widget.width
+ tests.push(function testWidgetWidth() testSingleWidget({
+ id: "text",
+ label: "test widget.width",
+ content: "test width",
+ width: 200,
+ contentScript: "self.postMessage(1)",
+ contentScriptWhen: "ready",
+ onMessage: function(message) {
+ test.assertEqual(this.width, 200);
+
+ let node = widgetNode(0);
+ test.assertEqual(this.width, node.style.minWidth.replace("px", ""));
+ test.assertEqual(this.width, node.firstElementChild.style.width.replace("px", ""));
+ this.width = 300;
+ test.assertEqual(this.width, node.style.minWidth.replace("px", ""));
+ test.assertEqual(this.width, node.firstElementChild.style.width.replace("px", ""));
+
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // test click handler not respond to right-click
+ let clickCount = 0;
+ tests.push(function testNoRightClick() testSingleWidget({
+ id: "click-content",
+ label: "click test widget - content",
+ content: "<div id='me'>foo</div>",
+ contentScript: "var evt = document.createEvent('MouseEvents'); " +
+ "evt.initMouseEvent('click', true, true, window, " +
+ " 0, 0, 0, 0, 0, false, false, false, false, 2, null); " +
+ "document.getElementById('me').dispatchEvent(evt); " +
+ "evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('click', true, true ); " +
+ "document.getElementById('me').dispatchEvent(evt); " +
+ "evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('mouseover', true, true ); " +
+ "document.getElementById('me').dispatchEvent(evt);",
+ contentScriptWhen: "end",
+ onClick: function() clickCount++,
+ onMouseover: function() {
+ test.assertEqual(clickCount, 1, "right click wasn't sent to click handler");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // kick off test execution
+ doneTest();
+};
+
+exports.testPanelWidget1 = function testPanelWidget1(test) {
+ const widgets = require("widget");
+
+ let widget1 = widgets.Widget({
+ id: "panel1",
+ label: "panel widget 1",
+ content: "<div id='me'>foo</div>",
+ contentScript: "var evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('click', true, true ); " +
+ "document.body.dispatchEvent(evt);",
+ contentScriptWhen: "end",
+ panel: require("panel").Panel({
+ contentURL: "data:text/html,<body>Look ma, a panel!</body>",
+ onShow: function() {
+ widget1.destroy();
+ test.pass("panel displayed on click");
+ test.done();
+ }
+ })
+ });
+ test.waitUntilDone();
+};
+
+exports.testPanelWidget2 = function testPanelWidget2(test) {
+ const widgets = require("widget");
+ test.assertRaises(
+ function() {
+ widgets.Widget({
+ id: "panel2",
+ label: "panel widget 2",
+ panel: {}
+ });
+ },
+ "The option \"panel\" must be one of the following types: null, undefined, object",
+ "widget.panel must be a Panel object"
+ );
+};
+
+exports.testPanelWidget3 = function testPanelWidget3(test) {
+ const widgets = require("widget");
+ let onClickCalled = false;
+ let widget3 = widgets.Widget({
+ id: "panel3",
+ label: "panel widget 3",
+ content: "<div id='me'>foo</div>",
+ contentScript: "var evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('click', true, true ); " +
+ "document.body.firstElementChild.dispatchEvent(evt);",
+ contentScriptWhen: "end",
+ onClick: function() {
+ onClickCalled = true;
+ this.panel.show();
+ },
+ panel: require("panel").Panel({
+ contentURL: "data:text/html,<body>Look ma, a panel!</body>",
+ onShow: function() {
+ test.assert(
+ onClickCalled,
+ "onClick called on click for widget with both panel and onClick"
+ );
+ widget3.destroy();
+ test.done();
+ }
+ })
+ });
+ test.waitUntilDone();
+};
+
+exports.testWidgetMessaging = function testWidgetMessaging(test) {
+ test.waitUntilDone();
+ let origMessage = "foo";
+ const widgets = require("widget");
+ let widget = widgets.Widget({
+ id: "foo",
+ label: "foo",
+ content: "<bar>baz</bar>",
+ contentScriptWhen: "end",
+ contentScript: "self.on('message', function(data) { self.postMessage(data); }); self.postMessage('ready');",
+ onMessage: function(message) {
+ if (message == "ready")
+ widget.postMessage(origMessage);
+ else {
+ test.assertEqual(origMessage, message);
+ widget.destroy();
+ test.done();
+ }
+ }
+ });
+};
+
+exports.testWidgetViews = function testWidgetViews(test) {
+ test.waitUntilDone();
+ const widgets = require("widget");
+ let widget = widgets.Widget({
+ id: "foo",
+ label: "foo",
+ content: "<bar>baz</bar>",
+ contentScriptWhen: "ready",
+ contentScript: "self.on('message', function(data) self.postMessage(data)); self.postMessage('ready')",
+ onAttach: function(view) {
+ test.pass("WidgetView created");
+ view.on("message", function () {
+ test.pass("Got message in WidgetView");
+ widget.destroy();
+ });
+ view.on("detach", function () {
+ test.pass("WidgetView destroyed");
+ test.done();
+ });
+ }
+ });
+
+};
+
+exports.testWidgetViewsUIEvents = function testWidgetViewsUIEvents(test) {
+ test.waitUntilDone();
+ const widgets = require("widget");
+ let view = null;
+ let widget = widgets.Widget({
+ id: "foo",
+ label: "foo",
+ content: "<div id='me'>foo</div>",
+ contentScript: "var evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('click', true, true ); " +
+ "document.getElementById('me').dispatchEvent(evt);",
+ contentScriptWhen: "ready",
+ onAttach: function(attachView) {
+ view = attachView;
+ test.pass("Got attach event");
+ },
+ onClick: function (eventView) {
+ test.assertEqual(view, eventView,
+ "event first argument is equal to the WidgetView");
+ let view2 = widget.getView(require("windows").browserWindows.activeWindow);
+ test.assertEqual(view, view2,
+ "widget.getView return the same WidgetView");
+ widget.destroy();
+ test.done();
+ }
+ });
+};
+
+exports.testWidgetViewsCustomEvents = function testWidgetViewsCustomEvents(test) {
+ test.waitUntilDone();
+ const widgets = require("widget");
+ let widget = widgets.Widget({
+ id: "foo",
+ label: "foo",
+ content: "<div id='me'>foo</div>",
+ contentScript: "self.port.emit('event', 'ok');",
+ contentScriptWhen: "ready",
+ onAttach: function(view) {
+ view.port.on("event", function (data) {
+ test.assertEqual(data, "ok",
+ "event argument is valid on WidgetView");
+ });
+ },
+ });
+ widget.port.on("event", function (data) {
+ test.assertEqual(data, "ok",
+ "event argument is valid on Widget");
+ widget.destroy();
+ test.done();
+ });
+};
+
+exports.testWidgetViewsTooltip = function testWidgetViewsTooltip(test) {
+ test.waitUntilDone();
+ const widgets = require("widget");
+
+ let widget = new widgets.Widget({
+ id: "foo",
+ label: "foo",
+ content: "foo"
+ });
+ let view = widget.getView(require("windows").browserWindows.activeWindow);
+ widget.tooltip = null;
+ test.assertEqual(view.tooltip, "foo",
+ "view tooltip defaults to base widget label");
+ test.assertEqual(widget.tooltip, "foo",
+ "tooltip defaults to base widget label");
+ widget.destroy();
+ test.done();
+};
+
+exports.testWidgetMove = function testWidgetMove(test) {
+ test.waitUntilDone();
+
+ let windowUtils = require("window-utils");
+ let widgets = require("widget");
+
+ let browserWindow = windowUtils.activeBrowserWindow;
+ let doc = browserWindow.document;
+
+ let label = "unique-widget-label";
+ let origMessage = "message after node move";
+ let gotFirstReady = false;
+
+ let widget = widgets.Widget({
+ id: "foo",
+ label: label,
+ content: "<bar>baz</bar>",
+ contentScriptWhen: "ready",
+ contentScript: "self.on('message', function(data) { self.postMessage(data); }); self.postMessage('ready');",
+ onMessage: function(message) {
+ if (message == "ready") {
+ if (!gotFirstReady) {
+ test.pass("Got first ready event");
+ let widgetNode = doc.querySelector('toolbaritem[label="' + label + '"]');
+ let parent = widgetNode.parentNode;
+ parent.insertBefore(widgetNode, parent.firstChild);
+ gotFirstReady = true;
+ } else {
+ test.pass("Got second ready event");
+ widget.postMessage(origMessage);
+ }
+ }
+ else {
+ test.assertEqual(origMessage, message, "Got message after node move");
+ widget.destroy();
+ test.done();
+ }
+ }
+ });
+};
+
+/*
+The bug is exhibited when a widget with HTML content has it's content
+changed to new HTML content with a pound in it. Because the src of HTML
+content is converted to a data URI, the underlying iframe doesn't
+consider the content change a navigation change, so doesn't load
+the new content.
+*/
+exports.testWidgetWithPound = function testWidgetWithPound(test) {
+ test.waitUntilDone();
+
+ function getWidgetContent(widget) {
+ let windowUtils = require("window-utils");
+ let browserWindow = windowUtils.activeBrowserWindow;
+ let doc = browserWindow.document;
+ let widgetNode = doc.querySelector('toolbaritem[label="' + widget.label + '"]');
+ test.assert(widgetNode, 'found widget node in the front-end');
+ return widgetNode.firstChild.contentDocument.body.innerHTML;
+ }
+
+ let widgets = require("widget");
+ let count = 0;
+ let widget = widgets.Widget({
+ id: "1",
+ label: "foo",
+ content: "foo",
+ contentScript: "window.addEventListener('load', self.postMessage, false);",
+ onMessage: function() {
+ count++;
+ if (count == 1) {
+ widget.content = "foo#";
+ }
+ else {
+ test.assertEqual(getWidgetContent(widget), "foo#", "content updated to pound?");
+ widget.destroy();
+ test.done();
+ }
+ }
+ });
+};
+
+/******************* helpers *********************/
+
+// Helper for calling code at window close
+function closeBrowserWindow(window, callback) {
+ require("timer").setTimeout(function() {
+ window.addEventListener("unload", function onUnload() {
+ window.removeEventListener("unload", onUnload, false);
+ callback();
+ }, false);
+ window.close();
+ }, 0);
+}
+
+// ADD NO TESTS BELOW THIS LINE! ///////////////////////////////////////////////
+
+// If the module doesn't support the app we're being run in, require() will
+// throw. In that case, remove all tests above from exports, and add one dummy
+// test that passes.
+try {
+ require("widget");
+}
+catch (err) {
+ // This bug should be mentioned in the error message.
+ let bug = "https://bugzilla.mozilla.org/show_bug.cgi?id=560716";
+ if (err.message.indexOf(bug) < 0)
+ throw err;
+ for (let [prop, val] in Iterator(exports)) {
+ if (/^test/.test(prop) && typeof(val) === "function")
+ delete exports[prop];
+ }
+ exports.testAppNotSupported = function (test) {
+ test.pass("context-menu does not support this application.");
+ };
+}
+
diff --git a/tools/addon-sdk-1.3/packages/addon-kit/tests/test-windows.js b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-windows.js
new file mode 100644
index 0000000..befe353
--- /dev/null
+++ b/tools/addon-sdk-1.3/packages/addon-kit/tests/test-windows.js
@@ -0,0 +1,354 @@
+/* ***** 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 ***** */
+
+const {Cc, Ci} = require("chrome");
+
+exports.testOpenAndCloseWindow = function(test) {
+
+ test.waitUntilDone();
+ let windows = require("windows").browserWindows;
+
+ test.assertEqual(windows.length, 1, "Only one window open");
+
+ windows.open({
+ url: "data:text/html,<title>windows API test</title>",
+ onOpen: function(window) {
+ test.assertEqual(this, windows,
+ "The 'this' object is the windows object.");
+ test.assertEqual(window.tabs.length, 1, "Only one tab open");
+ test.assertEqual(windows.length, 2, "Two windows open");
+ window.tabs.activeTab.on('ready', function onReady(tab) {
+ tab.removeListener('ready', onReady);
+ test.assert(window.title.indexOf("windows API test") != -1,
+ "URL correctly loaded");
+ window.close();
+ });
+ },
+ onClose: function(window) {
+ test.assertEqual(window.tabs.length, 0, "Tabs were cleared");
+ test.assertEqual(windows.length, 1, "Only one window open");
+ test.done();
+ }
+ });
+};
+
+exports.testAutomaticDestroy = function(test) {
+
+ test.waitUntilDone();
+ let windows = require("windows").browserWindows;
+
+ // Create a second windows instance that we will unload
+ let called = false;
+ let loader = test.makeSandboxedLoader();
+ let windows2 = loader.require("windows").browserWindows;
+ windows2.on("open", function() {
+ called = true;
+ });
+
+ loader.unload();
+
+ // Fire a windows event and check that this unloaded instance is inactive
+ windows.open({
+ url: "data:text/html,foo",
+ onOpen: function(window) {
+ require("timer").setTimeout(function () {
+ test.assert(!called,
+ "Unloaded windows instance is destroyed and inactive");
+ window.close(function () {
+ test.done();
+ });
+ });
+ }
+ });
+
+};
+
+exports.testOnOpenOnCloseListeners = function(test) {
+ test.waitUntilDone();
+ let windows = require("windows").browserWindows;
+
+ test.assertEqual(windows.length, 1, "Only one window open");
+
+ let received = {
+ listener1: false,
+ listener2: false,
+ listener3: false,
+ listener4: false
+ }
+
+ function listener1() {
+ test.assertEqual(this, windows, "The 'this' object is the windows object.");
+ if (received.listener1)
+ test.fail("Event received twice");
+ received.listener1 = true;
+ }
+
+ function listener2() {
+ if (received.listener2)
+ test.fail("Event received twice");
+ received.listener2 = true;
+ }
+
+ function listener3() {
+ test.assertEqual(this, windows, "The 'this' object is the windows object.");
+ if (received.listener3)
+ test.fail("Event received twice");
+ received.listener3 = true;
+ }
+
+ function listener4() {
+ if (received.listener4)
+ test.fail("Event received twice");
+ received.listener4 = true;
+ }
+
+ windows.on('open', listener1);
+ windows.on('open', listener2);
+ windows.on('close', listener3);
+ windows.on('close', listener4);
+
+ function verify() {
+ test.assert(received.listener1, "onOpen handler called");
+ test.assert(received.listener2, "onOpen handler called");
+ test.assert(received.listener3, "onClose handler called");
+ test.assert(received.listener4, "onClose handler called");
+
+ windows.removeListener('open', listener1);
+ windows.removeListener('open', listener2);
+ windows.removeListener('close', listener3);
+ windows.removeListener('close', listener4);
+ test.done();
+ }
+
+
+ windows.open({
+ url: "data:text/html,foo",
+ onOpen: function(window) {
+ window.close(verify);
+ }
+ });
+};
+
+exports.testWindowTabsObject = function(test) {
+ test.waitUntilDone();
+ let windows = require("windows").browserWindows;
+
+ windows.open({
+ url: "data:text/html,<title>tab 1</title>",
+ onOpen: function onOpen(window) {
+ test.assertEqual(window.tabs.length, 1, "Only 1 tab open");
+
+ window.tabs.open({
+ url: "data:text/html,<title>tab 2</title>",
+ inBackground: true,
+ onReady: function onReady(newTab) {
+ test.assertEqual(window.tabs.length, 2, "New tab open");
+ test.assertEqual(newTab.title, "tab 2", "Correct new tab title");
+ test.assertEqual(window.tabs.activeTab.title, "tab 1", "Correct active tab");
+
+ let i = 1;
+ for each (let tab in window.tabs)
+ test.assertEqual(tab.title, "tab " + i++, "Correct title");
+
+ window.close();
+ }
+ });
+ },
+ onClose: function onClose(window) {
+ test.assertEqual(window.tabs.length, 0, "No more tabs on closed window");
+ test.done();
+ }
+ });
+};
+
+exports.testActiveWindow = function(test) {
+ const xulApp = require("xul-app");
+ if (xulApp.versionInRange(xulApp.platformVersion, "1.9.2", "1.9.2.*")) {
+ test.pass("This test is disabled on 3.6. For more information, see bug 598525");
+ return;
+ }
+
+ let windows = require("windows").browserWindows;
+
+ // API window objects
+ let window2, window3;
+
+ // Raw window objects
+ let nonBrowserWindow, rawWindow2, rawWindow3;
+
+ // Find the first non-browser window: probably the test runner window
+ let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator);
+ let winEnum = wm.getEnumerator("");
+ while (winEnum.hasMoreElements()) {
+ let win = winEnum.getNext();
+ if (win.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
+ nonBrowserWindow = win;
+ break;
+ }
+ }
+ if (!nonBrowserWindow) {
+ test.fail("This test can't proceed without a non-browser window");
+ return;
+ }
+
+ test.waitUntilDone();
+
+ let testSteps = [
+ function() {
+ test.assertEqual(windows.length, 3, "Correct number of browser windows");
+ let count = 0;
+ for (let window in windows)
+ count++;
+ test.assertEqual(count, 3, "Correct number of windows returned by iterator");
+
+ rawWindow2.focus();
+ continueAfterFocus(rawWindow2);
+ },
+ function() {
+ nonBrowserWindow.focus();
+ continueAfterFocus(nonBrowserWindow);
+ },
+ function() {
+ /**
+ * Bug 614079: This test fails intermittently on some specific linux
+ * environnements, without being able to reproduce it in same
+ * distribution with same window manager.
+ * Disable it until being able to reproduce it easily.
+
+ // On linux, focus is not consistent, so we can't be sure
+ // what window will be on top.
+ // Here when we focus "non-browser" window,
+ // Any Browser window may be selected as "active".
+ test.assert(windows.activeWindow == window2 || windows.activeWindow == window3,
+ "Non-browser windows aren't handled by this module");
+ */
+ window2.activate();
+ continueAfterFocus(rawWindow2);
+ },
+ function() {
+ test.assertEqual(windows.activeWindow.title, window2.title, "Correct active window - 2");
+ window3.activate();
+ continueAfterFocus(rawWindow3);
+ },
+ function() {
+ test.assertEqual(windows.activeWindow.title, window3.title, "Correct active window - 3");
+ nonBrowserWindow.focus();
+ finishTest();
+ }
+ ];
+
+ windows.open({
+ url: "data:text/html,<title>window 2</title>",
+ onOpen: function(window) {
+ window2 = window;
+ rawWindow2 = wm.getMostRecentWindow("navigator:browser");
+
+ windows.open({
+ url: "data:text/html,<title>window 3</title>",
+ onOpen: function(window) {
+ window.tabs.activeTab.on('ready', function onReady() {
+ window3 = window;
+ rawWindow3 = wm.getMostRecentWindow("navigator:browser");
+ nextStep()
+ });
+ }
+ });
+ }
+ });
+
+ function nextStep() {
+ if (testSteps.length > 0)
+ testSteps.shift()();
+ }
+
+ function continueAfterFocus(targetWindow) {
+
+ // Based on SimpleTest.waitForFocus
+ var fm = Cc["@mozilla.org/focus-manager;1"].
+ getService(Ci.nsIFocusManager);
+
+ var childTargetWindow = {};
+ fm.getFocusedElementForWindow(targetWindow, true, childTargetWindow);
+ childTargetWindow = childTargetWindow.value;
+
+ var focusedChildWindow = {};
+ if (fm.activeWindow) {
+ fm.getFocusedElementForWindow(fm.activeWindow, true, focusedChildWindow);
+ focusedChildWindow = focusedChildWindow.value;
+ }
+
+ var focused = (focusedChildWindow == childTargetWindow);
+ if (focused) {
+ nextStep();
+ } else {
+ childTargetWindow.addEventListener("focus", function focusListener() {
+ childTargetWindow.removeEventListener("focus", focusListener, true);
+ nextStep();
+ }, true);
+ }
+
+ }
+
+ function finishTest() {
+ window3.close(function() {
+ window2.close(function() {
+ test.done();
+ });
+ });
+ }
+};
+
+// If the module doesn't support the app we're being run in, require() will
+// throw. In that case, remove all tests above from exports, and add one dummy
+// test that passes.
+try {
+ require("windows");
+}
+catch (err) {
+ // This bug should be mentioned in the error message.
+ let bug = "https://bugzilla.mozilla.org/show_bug.cgi?id=571449";
+ if (err.message.indexOf(bug) < 0)
+ throw err;
+ for (let [prop, val] in Iterator(exports)) {
+ if (/^test/.test(prop) && typeof(val) === "function")
+ delete exports[prop];
+ }
+ exports.testAppNotSupported = function (test) {
+ test.pass("the windows module does not support this application.");
+ };
+}