aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.7/packages/addon-kit/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tools/addon-sdk-1.7/packages/addon-kit/tests')
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/helpers.js23
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/pagemod-test-helpers.js67
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-addon-page.js51
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-clipboard.js64
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-context-menu.html45
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-context-menu.js2067
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-hotkeys.js160
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-l10n.js97
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-module.js37
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-notifications.js46
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-page-mod.js526
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-page-worker.js366
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-panel.js466
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-passwords.js281
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-private-browsing.js204
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-request.js340
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-selection.js458
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-simple-prefs.js175
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-simple-storage.js311
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-tabs.js900
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-timers.js12
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-widget.js1010
-rw-r--r--tools/addon-sdk-1.7/packages/addon-kit/tests/test-windows.js309
23 files changed, 8015 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.7/packages/addon-kit/tests/helpers.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/helpers.js
new file mode 100644
index 0000000..399046f
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/helpers.js
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Loader } = require("@loader");
+
+exports.Loader = function(module, globals) {
+ var options = JSON.parse(JSON.stringify(require("@packaging")));
+ options.globals = globals;
+ let loader = Loader.new(options);
+ return Object.create(loader, {
+ require: { value: Loader.require.bind(loader, module.path) },
+ sandbox: { value: function sandbox(id) {
+ let path = options.manifest[module.path].requirements[id].path;
+ return loader.sandboxes[path].sandbox;
+ }},
+ unload: { value: function unload(reason, callback) {
+ loader.unload(reason, callback);
+ }}
+ })
+};
diff --git a/tools/addon-sdk-1.7/packages/addon-kit/tests/pagemod-test-helpers.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/pagemod-test-helpers.js
new file mode 100644
index 0000000..7c741eb
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/pagemod-test-helpers.js
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {Cc,Ci} = require("chrome");
+const timer = require("timer");
+const xulApp = require("xul-app");
+const { Loader } = require('./helpers');
+
+/**
+ * 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) {
+ 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 = Loader(module);
+ 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.7/packages/addon-kit/tests/test-addon-page.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-addon-page.js
new file mode 100644
index 0000000..c0ee35d
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-addon-page.js
@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+const { isTabOpen, activateTab, openTab, closeTab } = require('api-utils/tabs/utils');
+const windows = require('api-utils/window-utils');
+const { Loader } = require('./helpers');
+const { setTimeout } = require('api-utils/timer');
+
+let uri = require('self').data.url('index.html');
+
+function isChromeVisible(window)
+ window.document.documentElement.getAttribute('disablechrome') !== 'true'
+
+exports['test that add-on page has no chrome'] = function(assert, done) {
+ let loader = Loader(module);
+ loader.require('addon-kit/addon-page');
+
+ let window = windows.activeBrowserWindow;
+ let tab = openTab(window, uri);
+
+ assert.ok(isChromeVisible(window), 'chrome is visible for non addon page');
+
+ // need to do this in another turn to make sure event listener
+ // that sets property has time to do that.
+ setTimeout(function() {
+ activateTab(tab);
+
+ assert.ok(!isChromeVisible(window), 'chrome is not visible for addon page');
+
+ closeTab(tab);
+ assert.ok(isChromeVisible(window), 'chrome is visible again');
+ loader.unload();
+ done();
+ });
+};
+
+exports['test that add-on pages is closed on unload'] = function(assert) {
+ let loader = Loader(module);
+ loader.require('addon-kit/addon-page');
+
+ let tab = openTab(windows.activeBrowserWindow, uri);
+ loader.unload();
+
+ assert.ok(isTabOpen(tab), 'add-on page tabs are closed on unload');
+};
+
+
+require('test').run(exports);
diff --git a/tools/addon-sdk-1.7/packages/addon-kit/tests/test-clipboard.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-clipboard.js
new file mode 100644
index 0000000..1819d68
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-clipboard.js
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+// 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.7/packages/addon-kit/tests/test-context-menu.html b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-context-menu.html
new file mode 100644
index 0000000..4196d5f
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-context-menu.html
@@ -0,0 +1,45 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<html>
+ <head>
+ <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.7/packages/addon-kit/tests/test-context-menu.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-context-menu.js
new file mode 100644
index 0000000..c398933
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-context-menu.js
@@ -0,0 +1,2067 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let {Cc,Ci} = require("chrome");
+const { Loader } = require('./helpers');
+
+// 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 = module.uri.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();
+ });
+};
+
+
+// Ensure that contentScripFile is working correctly
+exports.testContentScriptFile = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ // Reject remote files
+ test.assertRaises(function() {
+ new loader.cm.Item({
+ label: "item",
+ contentScriptFile: "http://mozilla.com/context-menu.js"
+ });
+ },
+ "The 'contentScriptFile' option must be a local file URL " +
+ "or an array of local file URLs.",
+ "Item throws when contentScriptFile is a remote URL");
+
+ // But accept files from data folder
+ let item = new loader.cm.Item({
+ label: "item",
+ contentScriptFile: require("self").data.url("test-context-menu.js")
+ });
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([item], [], []);
+ test.done();
+ });
+};
+
+
+// The args passed to context listeners should be correct.
+exports.testContentContextArgs = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+ let callbacks = 0;
+
+ 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");
+ if (++callbacks == 2) test.done();
+ }
+ });
+
+ test.showMenu(null, function () {
+ if (++callbacks == 2) test.done();
+ });
+};
+
+// 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.
+ 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 = Loader(module);
+ let wrapper = {
+ loader: loader,
+ cm: loader.require("context-menu"),
+ globalScope: loader.sandbox("context-menu"),
+ 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.7/packages/addon-kit/tests/test-hotkeys.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-hotkeys.js
new file mode 100644
index 0000000..0e0ecd6
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-hotkeys.js
@@ -0,0 +1,160 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Hotkey } = require("hotkeys");
+const { keyDown } = require("dom/events/keys");
+const { Loader } = require('./helpers');
+
+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 = Loader(module);
+
+ 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.7/packages/addon-kit/tests/test-l10n.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-l10n.js
new file mode 100644
index 0000000..259b160
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-l10n.js
@@ -0,0 +1,97 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const prefs = require("preferences-service");
+const { Loader } = require('./helpers');
+
+const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
+const PREF_SELECTED_LOCALE = "general.useragent.locale";
+
+function setLocale(locale) {
+ prefs.set(PREF_MATCH_OS_LOCALE, false);
+ prefs.set(PREF_SELECTED_LOCALE, locale);
+}
+
+function resetLocale() {
+ prefs.reset(PREF_MATCH_OS_LOCALE);
+ prefs.reset(PREF_SELECTED_LOCALE);
+}
+
+exports.testExactMatching = function(test) {
+ let loader = Loader(module);
+ setLocale("fr-FR");
+
+ let _ = loader.require("l10n").get;
+ test.assertEqual(_("Not translated"), "Not translated",
+ "Key not translated");
+ test.assertEqual(_("Translated"), "Oui",
+ "Simple key translated");
+
+ // Placeholders
+ test.assertEqual(_("placeholderString", "works"), "Placeholder works",
+ "Value with placeholder");
+ test.assertEqual(_("Placeholder %s", "works"), "Placeholder works",
+ "Key without value but with placeholder");
+ test.assertEqual(_("Placeholders %2s %1s %s.", "working", "are", "correctly"),
+ "Placeholders are working correctly.",
+ "Multiple placeholders");
+
+ // Plurals
+ test.assertEqual(_("downloadsCount", 0),
+ "0 téléchargement",
+ "PluralForm form 'one' for 0 in french");
+ test.assertEqual(_("downloadsCount", 1),
+ "1 téléchargement",
+ "PluralForm form 'one' for 1 in french");
+ test.assertEqual(_("downloadsCount", 2),
+ "2 téléchargements",
+ "PluralForm form 'other' for n > 1 in french");
+
+ loader.unload();
+ resetLocale();
+}
+
+exports.testEnUsLocaleName = function(test) {
+ let loader = Loader(module);
+ setLocale("en-US");
+
+ let _ = loader.require("l10n").get;
+ test.assertEqual(_("Not translated"), "Not translated");
+ test.assertEqual(_("Translated"), "Yes");
+
+ // Check plural forms regular matching
+ test.assertEqual(_("downloadsCount", 0),
+ "0 downloads",
+ "PluralForm form 'other' for 0 in english");
+ test.assertEqual(_("downloadsCount", 1),
+ "one download",
+ "PluralForm form 'one' for 1 in english");
+ test.assertEqual(_("downloadsCount", 2),
+ "2 downloads",
+ "PluralForm form 'other' for n != 1 in english");
+
+ // Check optional plural forms
+ test.assertEqual(_("pluralTest", 0),
+ "optional zero form",
+ "PluralForm form 'zero' can be optionaly specified. (Isn't mandatory in english)");
+ test.assertEqual(_("pluralTest", 1),
+ "fallback to other",
+ "If the specific plural form is missing, we fallback to 'other'");
+
+ loader.unload();
+ resetLocale();
+}
+
+exports.testShortLocaleName = function(test) {
+ let loader = Loader(module);
+ setLocale("eo");
+
+ let _ = loader.require("l10n").get;
+ test.assertEqual(_("Not translated"), "Not translated");
+ test.assertEqual(_("Translated"), "jes");
+
+ loader.unload();
+ resetLocale();
+}
+
diff --git a/tools/addon-sdk-1.7/packages/addon-kit/tests/test-module.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-module.js
new file mode 100644
index 0000000..957d075
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-module.js
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/** 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.7/packages/addon-kit/tests/test-notifications.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-notifications.js
new file mode 100644
index 0000000..b0e1f37
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-notifications.js
@@ -0,0 +1,46 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { Loader } = require('./helpers');
+
+exports.testOnClick = function (test) {
+ let [loader, mockAlertServ] = makeLoader(module);
+ 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 = Loader(module);
+ 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);
+ }
+ };
+ loader.require("notifications");
+ let scope = loader.sandbox("notifications");
+ scope.notify = mockAlertServ.showAlertNotification.bind(mockAlertServ);
+ return [loader, mockAlertServ];
+};
diff --git a/tools/addon-sdk-1.7/packages/addon-kit/tests/test-page-mod.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-page-mod.js
new file mode 100644
index 0000000..14a3ef9
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-page-mod.js
@@ -0,0 +1,526 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var pageMod = require("page-mod");
+var testPageMod = require("pagemod-test-helpers").testPageMod;
+const { Loader } = require('./helpers');
+const tabs = require("tabs");
+
+/* 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:credits", [{
+ 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;
+ }
+ );
+};
+
+// Execute two concurrent page mods on same document to ensure that their
+// JS contexts are different
+exports.testMixedContext = function(test) {
+ let doneCallback = null;
+ let messages = 0;
+ let modObject = {
+ include: "data:text/html,",
+ contentScript: 'new ' + function WorkerScope() {
+ // Both scripts will execute this,
+ // context is shared if one script see the other one modification.
+ let isContextShared = "sharedAttribute" in document;
+ self.postMessage(isContextShared);
+ document.sharedAttribute = true;
+ },
+ onAttach: function(w) {
+ w.on("message", function (isContextShared) {
+ if (isContextShared) {
+ test.fail("Page mod contexts are mixed.");
+ doneCallback();
+ }
+ else if (++messages == 2) {
+ test.pass("Page mod contexts are different.");
+ doneCallback();
+ }
+ });
+ }
+ };
+ testPageMod(test, "data:text/html,", [modObject, modObject],
+ function(win, done) {
+ doneCallback = 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 tab;
+ let { PageMod } = require("page-mod");
+ let pageMod = new 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 = Loader(module);
+
+ 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();
+ }
+ });
+
+}
+
+exports.testPageModCss = function(test) {
+ let [pageMod] = testPageMod(test,
+ 'data:text/html,<div style="background: silver">css test</div>', [{
+ include: "data:*",
+ contentStyle: "div { height: 100px; }",
+ contentStyleFile:
+ require("self").data.url("pagemod-css-include-file.css")
+ }],
+ function(win, done) {
+ let div = win.document.querySelector("div");
+ test.assertEqual(
+ div.clientHeight,
+ 100,
+ "PageMod contentStyle worked"
+ );
+ test.assertEqual(
+ div.offsetHeight,
+ 120,
+ "PageMod contentStyleFile worked"
+ );
+ done();
+ }
+ );
+};
+
+exports.testPageModCssList = function(test) {
+ let [pageMod] = testPageMod(test,
+ 'data:text/html,<div style="width:320px; max-width: 480px!important">css test</div>', [{
+ include: "data:*",
+ contentStyleFile: [
+ // Highlight evaluation order in this list
+ "data:text/css,div { border: 1px solid black; }",
+ "data:text/css,div { border: 10px solid black; }",
+ // Highlight evaluation order between contentStylesheet & contentStylesheetFile
+ "data:text/css,div { height: 1000px; }",
+ // Highlight precedence between the author and user style sheet
+ "data:text/css,div { width: 200px; max-width: 640px!important}",
+ ],
+ contentStyle: [
+ "div { height: 10px; }",
+ "div { height: 100px; }"
+ ]
+ }],
+ function(win, done) {
+ let div = win.document.querySelector("div"),
+ style = win.getComputedStyle(div);
+
+ test.assertEqual(
+ div.clientHeight,
+ 100,
+ "PageMod contentStyle list works and is evaluated after contentStyleFile"
+ );
+
+ test.assertEqual(
+ div.offsetHeight,
+ 120,
+ "PageMod contentStyleFile list works"
+ );
+
+ test.assertEqual(
+ style.width,
+ "320px",
+ "PageMod author/user style sheet precedence works"
+ );
+
+ test.assertEqual(
+ style.maxWidth,
+ "640px",
+ "PageMod author/user style sheet precedence with !important works"
+ );
+
+ done();
+ }
+ );
+};
+
+exports.testPageModCssDestroy = function(test) {
+ let [pageMod] = testPageMod(test,
+ 'data:text/html,<div style="width:200px">css test</div>', [{
+ include: "data:*",
+ contentStyle: "div { width: 100px!important; }"
+ }],
+
+ function(win, done) {
+ let div = win.document.querySelector("div"),
+ style = win.getComputedStyle(div);
+
+ test.assertEqual(
+ style.width,
+ "100px",
+ "PageMod contentStyle worked"
+ );
+
+ pageMod.destroy();
+ test.assertEqual(
+ style.width,
+ "200px",
+ "PageMod contentStyle is removed after destroy"
+ );
+
+ done();
+
+ }
+ );
+};
diff --git a/tools/addon-sdk-1.7/packages/addon-kit/tests/test-page-worker.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-page-worker.js
new file mode 100644
index 0000000..5fc3bec
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-page-worker.js
@@ -0,0 +1,366 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let tests = {}, Pages, Page;
+const { Loader } = require('./helpers');
+
+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 = Loader(module);
+ let Pages = loader.require("page-worker");
+ let global = loader.sandbox("page-worker");
+
+ 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 = Loader(module);
+ 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) {
+ 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.7/packages/addon-kit/tests/test-panel.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-panel.js
new file mode 100644
index 0000000..e05400e
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-panel.js
@@ -0,0 +1,466 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let { Cc, Ci } = require("chrome");
+let panels = require('panel');
+let tests = {}, panels, Panel;
+const { Loader } = require('./helpers');
+
+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();
+ });
+};
+
+// Bug 696552: Ensure panel.contentURL modification support
+tests.testChangeContentURL = function(test) {
+ test.waitUntilDone();
+
+ let panel = Panel({
+ contentURL: "about:blank",
+ contentScript: "self.port.emit('ready', document.location.href);"
+ });
+ let count = 0;
+ panel.port.on("ready", function (location) {
+ count++;
+ if (count == 1) {
+ test.assertEqual(location, "about:blank");
+ test.assertEqual(panel.contentURL, "about:blank");
+ panel.contentURL = "about:buildconfig";
+ }
+ else {
+ test.assertEqual(location, "about:buildconfig");
+ test.assertEqual(panel.contentURL, "about:buildconfig");
+ 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 = Loader(module);
+ 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.7/packages/addon-kit/tests/test-passwords.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-passwords.js
new file mode 100644
index 0000000..bfb137a
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-passwords.js
@@ -0,0 +1,281 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { 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.7/packages/addon-kit/tests/test-private-browsing.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-private-browsing.js
new file mode 100644
index 0000000..1d60f6b
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-private-browsing.js
@@ -0,0 +1,204 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let pb = require("private-browsing");
+let {Cc,Ci} = require("chrome");
+const { Loader } = require('./helpers');
+
+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 = Loader(module);
+ 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.7/packages/addon-kit/tests/test-request.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-request.js
new file mode 100644
index 0000000..42425d7
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-request.js
@@ -0,0 +1,340 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { Request } = require("addon-kit/request");
+const { pathFor } = require("api-utils/system");
+const { startServerAsync } = require("api-utils/httpd");
+const file = require("api-utils/file");
+
+const basePath = pathFor("TmpD")
+const port = 8099;
+
+
+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 a request to a file that exists.
+exports.testStatus200 = function (test) {
+ let srv = startServerAsync(port, basePath);
+ let content = "Look ma, no hands!\n";
+ let basename = "test-request.txt"
+ prepareFile(basename, content);
+
+ test.waitUntilDone();
+ var req = Request({
+ url: "http://localhost:" + port + "/" + basename,
+ onComplete: function (response) {
+ test.assertEqual(this, req, "`this` should be request");
+ test.assertEqual(response.status, 200);
+ test.assertEqual(response.statusText, "OK");
+ test.assertEqual(response.headers["Content-Type"], "text/plain");
+ test.assertEqual(response.text, content);
+ srv.stop(function() test.done());
+ }
+ }).get();
+}
+
+// This tries to get a file that doesn't exist
+exports.testStatus404 = function (test) {
+ var srv = startServerAsync(port, basePath);
+
+ test.waitUntilDone();
+ Request({
+ // the following URL doesn't exist
+ url: "http://localhost:" + port + "/test-request-404.txt",
+ onComplete: function (response) {
+ test.assertEqual(response.status, 404);
+ test.assertEqual(response.statusText, "Not Found");
+ srv.stop(function() 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.testSimpleJSON = function (test) {
+ let srv = startServerAsync(port, basePath);
+ let json = { foo: "bar" };
+ let basename = "test-request.json";
+ prepareFile(basename, JSON.stringify(json));
+
+ test.waitUntilDone();
+ Request({
+ url: "http://localhost:" + port + "/" + basename,
+ onComplete: function (response) {
+ assertDeepEqual(test, response.json, json);
+ srv.stop(function() test.done());
+ }
+ }).get();
+}
+
+exports.testInvalidJSON = function (test) {
+ let srv = startServerAsync(port, basePath);
+ let basename = "test-request-invalid.json";
+ prepareFile(basename, '"this": "isn\'t JSON"');
+
+ test.waitUntilDone();
+ Request({
+ url: "http://localhost:" + port + "/" + basename,
+ onComplete: function (response) {
+ test.assertEqual(response.json, null);
+ srv.stop(function() 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);
+}
+
+function prepareFile(basename, content) {
+ let filePath = file.join(basePath, basename);
+ let fileStream = file.open(filePath, 'w');
+ fileStream.write(content);
+ fileStream.close();
+}
diff --git a/tools/addon-sdk-1.7/packages/addon-kit/tests/test-selection.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-selection.js
new file mode 100644
index 0000000..06feb7e
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-selection.js
@@ -0,0 +1,458 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+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.7/packages/addon-kit/tests/test-simple-prefs.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-simple-prefs.js
new file mode 100644
index 0000000..7452a8a
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-simple-prefs.js
@@ -0,0 +1,175 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+const { Loader } = require("./helpers");
+const { setTimeout } = require("timers");
+const { notify } = require("observer-service");
+const { jetpackID } = require("@packaging");
+
+exports.testSetGetBool = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let sp = loader.require("simple-prefs").prefs;
+
+ test.assertEqual(sp.test, undefined, "Value should not exist");
+ sp.test = true;
+ test.assert(sp.test, "Value read should be the value previously set");
+
+ loader.unload();
+ test.done();
+};
+
+exports.testSetGetInt = function(test) {
+ test.waitUntilDone();
+
+ // Load the module once, set a value.
+ let loader = Loader(module);
+ let sp = loader.require("simple-prefs").prefs;
+
+ test.assertEqual(sp["test-int"], undefined, "Value should not exist");
+ sp["test-int"] = 1;
+ test.assertEqual(sp["test-int"], 1, "Value read should be the value previously set");
+
+ loader.unload();
+ test.done();
+};
+
+exports.testSetComplex = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let sp = loader.require("simple-prefs").prefs;
+
+ try {
+ sp["test-complex"] = {test: true};
+ test.fail("Complex values are not allowed");
+ }
+ catch (e) {
+ test.pass("Complex values are not allowed");
+ }
+
+ loader.unload();
+ test.done();
+};
+
+exports.testSetGetString = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let sp = loader.require("simple-prefs").prefs;
+
+ test.assertEqual(sp["test-string"], undefined, "Value should not exist");
+ sp["test-string"] = "test";
+ test.assertEqual(sp["test-string"], "test", "Value read should be the value previously set");
+
+ loader.unload();
+ test.done();
+};
+
+exports.testHasAndRemove = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let sp = loader.require("simple-prefs").prefs;
+
+ sp.test = true;
+ test.assert(("test" in sp), "Value exists");
+ delete sp.test;
+ test.assertEqual(sp.test, undefined, "Value should be undefined");
+
+ loader.unload();
+ test.done();
+
+};
+
+exports.testPrefListener = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let sp = loader.require("simple-prefs");
+
+ let listener = function(prefName) {
+ test.assertEqual(prefName, "test-listen", "The prefs listener heard the right event");
+ test.done();
+ };
+
+ sp.on("test-listen", listener);
+
+ sp.prefs["test-listen"] = true;
+ loader.unload();
+};
+
+exports.testBtnListener = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let sp = loader.require("simple-prefs");
+
+ sp.on("test-btn-listen", function() {
+ test.pass("Button press event was heard");
+ test.done();
+ });
+ notify((jetpackID + "-cmdPressed"), "", "test-btn-listen");
+
+ loader.unload();
+};
+
+exports.testPrefRemoveListener = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let sp = loader.require("simple-prefs");
+ let counter = 0;
+
+ let listener = function() {
+ test.pass("The prefs listener was not removed yet");
+
+ if (++counter > 1)
+ test.fail("The prefs listener was not removed");
+
+ sp.removeListener("test-listen2", listener);
+
+ sp.prefs["test-listen2"] = false;
+
+ setTimeout(function() {
+ test.pass("The prefs listener was removed");
+ loader.unload();
+ test.done();
+ }, 250);
+ };
+
+ sp.on("test-listen2", listener);
+
+ // emit change
+ sp.prefs["test-listen2"] = true;
+};
+
+// Bug 710117: Test that simple-pref listeners are removed on unload
+exports.testPrefUnloadListener = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let sp = loader.require("simple-prefs");
+ let counter = 0;
+
+ let listener = function() {
+ test.assertEqual(++counter, 1, "This listener should only be called once");
+
+ loader.unload();
+
+ // this may not execute after unload, but definitely shouldn't fire listener
+ sp.prefs["test-listen3"] = false;
+ // this should execute, but also definitely shouldn't fire listener
+ require("simple-prefs").prefs["test-listen3"] = false; //
+
+ test.done();
+ };
+
+ sp.on("test-listen3", listener);
+
+ // emit change
+ sp.prefs["test-listen3"] = true;
+};
diff --git a/tools/addon-sdk-1.7/packages/addon-kit/tests/test-simple-storage.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-simple-storage.js
new file mode 100644
index 0000000..25d0770
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-simple-storage.js
@@ -0,0 +1,311 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const file = require("file");
+const prefs = require("preferences-service");
+
+const QUOTA_PREF = "extensions.addon-sdk.simple-storage.quota";
+
+let {Cc,Ci} = require("chrome");
+
+const { Loader } = require("./helpers");
+const options = require("@packaging");
+
+let storeFile = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+storeFile.append("jetpack");
+storeFile.append(options.jetpackID);
+storeFile.append("simple-storage");
+storeFile.append("store.json");
+let storeFilename = storeFile.path;
+
+function manager(loader) loader.sandbox("simple-storage").manager;
+
+exports.testSetGet = function (test) {
+ test.waitUntilDone();
+
+ // Load the module once, set a value.
+ let loader = Loader(module);
+ 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 = Loader(module);
+ 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 = Loader(module);
+ 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 = Loader(module);
+ 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 = Loader(module);
+ 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 = Loader(module);
+ 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 = Loader(module);
+ let ss = loader.require("simple-storage");
+
+ manager(loader).jsonStore.onWrite = function (storage) {
+ loader = Loader(module);
+ 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 = Loader(module);
+ 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 = Loader(module);
+ 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 = Loader(module);
+ let ss = loader.require("simple-storage");
+ manager(loader).jsonStore.onWrite = function () {
+ test.assert(file.exists(storeFilename), "Store file should exist");
+
+ loader = Loader(module);
+ 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 = Loader(module);
+ 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 = Loader(module);
+ 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 = Loader(module);
+ 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 setGetRoot(test, val, compare) {
+ test.waitUntilDone();
+
+ compare = compare || function (a, b) a === b;
+
+ // Load the module once, set a value.
+ let loader = Loader(module);
+ 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 = Loader(module);
+ 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 = Loader(module);
+ let ss = loader.require("simple-storage");
+ test.assertRaises(function () ss.storage = val, pred, msg);
+ loader.unload();
+}
diff --git a/tools/addon-sdk-1.7/packages/addon-kit/tests/test-tabs.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-tabs.js
new file mode 100644
index 0000000..5719ab4
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-tabs.js
@@ -0,0 +1,900 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+var {Cc,Ci} = require("chrome");
+const { Loader } = require("./helpers");
+
+// 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 = Loader(module);
+ 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);
+ });
+};
+
+exports.testTabsEvent_pinning = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("tabs");
+ let url = "data:text/html,1";
+
+ tabs.on('open', function onOpen(tab) {
+ tabs.removeListener('open', onOpen);
+ tab.pin();
+ });
+
+ tabs.on('pinned', function onPinned(tab) {
+ tabs.removeListener('pinned', onPinned);
+ test.assert(tab.isPinned, "notified tab is pinned");
+ tab.unpin();
+ });
+
+ tabs.on('unpinned', function onUnpinned(tab) {
+ tabs.removeListener('unpinned', onUnpinned);
+ test.assert(!tab.isPinned, "notified tab is not pinned");
+ closeBrowserWindow(window, function() test.done());
+ });
+
+ 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.7/packages/addon-kit/tests/test-timers.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-timers.js
new file mode 100644
index 0000000..90b26bf
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-timers.js
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+const 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.7/packages/addon-kit/tests/test-widget.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-widget.js
new file mode 100644
index 0000000..5d3475b
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-widget.js
@@ -0,0 +1,1010 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {Cc,Ci} = require("chrome");
+const { Loader } = require('./helpers');
+const widgets = require("widget");
+const url = require("url");
+const windowUtils = require("window-utils");
+const tabBrowser = require("tab-browser");
+
+exports.testConstructor = function(test) {
+ test.waitUntilDone(30000);
+
+ 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 = Loader(module);
+ 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 Bug 652527
+ test.assertRaises(
+ function() widgets.Widget({id: "", label: "bar", content: "bar"}),
+ /You have to specify a unique value for the id property of/,
+ "throws on falsey id");
+
+ // 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 = Loader(module);
+ 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 allow attribute
+ tests.push(function testDefaultAllow() testSingleWidget({
+ id: "allow",
+ label: "allow.script attribute",
+ content: "<script>document.title = 'ok';</script>",
+ contentScript: "self.postMessage(document.title)",
+ onMessage: function(message) {
+ test.assertEqual(message, "ok", "scripts are evaluated by default");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ tests.push(function testExplicitAllow() testSingleWidget({
+ id: "allow",
+ label: "allow.script attribute",
+ allow: {script: true},
+ content: "<script>document.title = 'ok';</script>",
+ contentScript: "self.postMessage(document.title)",
+ onMessage: function(message) {
+ test.assertEqual(message, "ok", "scripts are evaluated when we want to");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ tests.push(function testExplicitDisallow() testSingleWidget({
+ id: "allow",
+ label: "allow.script attribute",
+ content: "<script>document.title = 'ok';</script>",
+ allow: {script: false},
+ contentScript: "self.postMessage(document.title)",
+ onMessage: function(message) {
+ test.assertNotEqual(message, "ok", "scripts aren't evaluated when " +
+ "explicitly blocked it");
+ 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();
+ }
+ }
+ });
+};
+
+exports.testNavigationBarWidgets = function testNavigationBarWidgets(test) {
+ test.waitUntilDone();
+
+ let w1 = widgets.Widget({id: "1st", label: "1st widget", content: "1"});
+ let w2 = widgets.Widget({id: "2nd", label: "2nd widget", content: "2"});
+ let w3 = widgets.Widget({id: "3rd", label: "3rd widget", content: "3"});
+
+ // Hack to move 2nd and 3rd widgets manually to the navigation bar, in 5th
+ // position, i.e. after search box. 3rd widget will be in 5th and 2nd in 6th.
+ function getWidgetNode(toolbar, position) {
+ return toolbar.getElementsByTagName("toolbaritem")[position];
+ }
+ let browserWindow = windowUtils.activeBrowserWindow;
+ let doc = browserWindow.document;
+ let addonBar = doc.getElementById("addon-bar");
+ let w2Toolbaritem = getWidgetNode(addonBar, 1);
+ let w3ToolbarItem = getWidgetNode(addonBar, 2);
+ let navBar = doc.getElementById("nav-bar");
+ navBar.insertItem(w2Toolbaritem.id, navBar.childNodes[6], null, false);
+ navBar.insertItem(w3ToolbarItem.id, navBar.childNodes[6], null, false);
+ // Widget and Firefox codes rely on this `currentset` attribute,
+ // so ensure it is correctly saved
+ navBar.setAttribute("currentset", navBar.currentSet);
+ doc.persist(navBar.id, "currentset");
+ // Update addonbar too as we removed widget from there.
+ // Otherwise, widgets may still be added to this toolbar.
+ addonBar.setAttribute("currentset", addonBar.currentSet);
+ doc.persist(addonBar.id, "currentset");
+
+ tabBrowser.addTab("about:blank", { inNewWindow: true, onLoad: function(e) {
+ let browserWindow = e.target.defaultView;
+ let doc = browserWindow.document;
+ let navBar = doc.getElementById("nav-bar");
+ let addonBar = doc.getElementById("addon-bar");
+
+ // Ensure that 1st is in addon bar
+ test.assertEqual(getWidgetNode(addonBar, 0).getAttribute("label"), w1.label);
+ // And that 2nd and 3rd keep their original positions in navigation bar
+ test.assertEqual(navBar.childNodes[5].getAttribute("label"), w3.label);
+ test.assertEqual(navBar.childNodes[6].getAttribute("label"), w2.label);
+
+ w1.destroy();
+ w2.destroy();
+ w3.destroy();
+
+ closeBrowserWindow(browserWindow, function() {
+ 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("widget does not support this application.");
+ };
+}
+
diff --git a/tools/addon-sdk-1.7/packages/addon-kit/tests/test-windows.js b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-windows.js
new file mode 100644
index 0000000..b85fec7
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/addon-kit/tests/test-windows.js
@@ -0,0 +1,309 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const {Cc, Ci} = require("chrome");
+const { setTimeout } = require("timer");
+const { Loader } = require('./helpers');
+const wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator);
+let browserWindows;
+
+function getTestRunnerWindow() wm.getMostRecentWindow("test:runner")
+
+exports.testOpenAndCloseWindow = function(test) {
+ test.waitUntilDone();
+
+ test.assertEqual(browserWindows.length, 1, "Only one window open");
+
+ browserWindows.open({
+ url: "data:text/html,<title>windows API test</title>",
+ onOpen: function(window) {
+ test.assertEqual(this, browserWindows,
+ "The 'this' object is the windows object.");
+ test.assertEqual(window.tabs.length, 1, "Only one tab open");
+ test.assertEqual(browserWindows.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(browserWindows.length, 1, "Only one window open");
+ test.done();
+ }
+ });
+};
+
+exports.testAutomaticDestroy = function(test) {
+
+ test.waitUntilDone();
+ let windows = browserWindows;
+
+ // Create a second windows instance that we will unload
+ let called = false;
+ let loader = Loader(module);
+ 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) {
+ 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 = browserWindows;
+
+ test.assertEqual(browserWindows.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();
+
+ browserWindows.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 = browserWindows;
+
+ // API window objects
+ let window2, window3;
+
+ // Raw window objects
+ let nonBrowserWindow = getTestRunnerWindow(), rawWindow2, rawWindow3;
+
+ 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 {
+ browserWindows = require("windows").browserWindows;
+}
+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.");
+ };
+}