aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.12/test
diff options
context:
space:
mode:
Diffstat (limited to 'tools/addon-sdk-1.12/test')
-rw-r--r--tools/addon-sdk-1.12/test/addons/l10n/data/test-localization.html24
-rw-r--r--tools/addon-sdk-1.12/test/addons/l10n/locale/en-GB.properties16
-rw-r--r--tools/addon-sdk-1.12/test/addons/l10n/locale/eo.properties5
-rw-r--r--tools/addon-sdk-1.12/test/addons/l10n/locale/fr-FR.properties14
-rw-r--r--tools/addon-sdk-1.12/test/addons/l10n/main.js181
-rw-r--r--tools/addon-sdk-1.12/test/addons/l10n/package.json3
-rw-r--r--tools/addon-sdk-1.12/test/commonjs-test-adapter/asserts.js54
-rw-r--r--tools/addon-sdk-1.12/test/fixtures/addon-install-unit-test@mozilla.com.xpibin0 -> 1596 bytes
-rw-r--r--tools/addon-sdk-1.12/test/fixtures/chrome-worker/addEventListener.js8
-rw-r--r--tools/addon-sdk-1.12/test/fixtures/chrome-worker/jsctypes.js8
-rw-r--r--tools/addon-sdk-1.12/test/fixtures/chrome-worker/onerror.js8
-rw-r--r--tools/addon-sdk-1.12/test/fixtures/chrome-worker/onmessage.js10
-rw-r--r--tools/addon-sdk-1.12/test/fixtures/chrome-worker/setTimeout.js10
-rw-r--r--tools/addon-sdk-1.12/test/fixtures/chrome-worker/xhr.js13
-rw-r--r--tools/addon-sdk-1.12/test/fixtures/es5.js8
-rw-r--r--tools/addon-sdk-1.12/test/fixtures/loader/cycles/a.js7
-rw-r--r--tools/addon-sdk-1.12/test/fixtures/loader/cycles/b.js7
-rw-r--r--tools/addon-sdk-1.12/test/fixtures/loader/cycles/c.js7
-rw-r--r--tools/addon-sdk-1.12/test/fixtures/loader/cycles/main.js14
-rw-r--r--tools/addon-sdk-1.12/test/fixtures/sandbox-complex-character.js5
-rw-r--r--tools/addon-sdk-1.12/test/fixtures/sandbox-normal.js7
-rw-r--r--tools/addon-sdk-1.12/test/loader/fixture.js7
-rw-r--r--tools/addon-sdk-1.12/test/modules/add.js9
-rw-r--r--tools/addon-sdk-1.12/test/modules/async1.js14
-rw-r--r--tools/addon-sdk-1.12/test/modules/async2.js8
-rw-r--r--tools/addon-sdk-1.12/test/modules/badExportAndReturn.js10
-rw-r--r--tools/addon-sdk-1.12/test/modules/badFirst.js9
-rw-r--r--tools/addon-sdk-1.12/test/modules/badSecond.js8
-rw-r--r--tools/addon-sdk-1.12/test/modules/blue.js9
-rw-r--r--tools/addon-sdk-1.12/test/modules/castor.js10
-rw-r--r--tools/addon-sdk-1.12/test/modules/cheetah.js9
-rw-r--r--tools/addon-sdk-1.12/test/modules/color.js7
-rw-r--r--tools/addon-sdk-1.12/test/modules/dupe.js15
-rw-r--r--tools/addon-sdk-1.12/test/modules/dupeNested.js15
-rw-r--r--tools/addon-sdk-1.12/test/modules/dupeSetExports.js8
-rw-r--r--tools/addon-sdk-1.12/test/modules/exportsEquals.js5
-rw-r--r--tools/addon-sdk-1.12/test/modules/green.js10
-rw-r--r--tools/addon-sdk-1.12/test/modules/lion.js7
-rw-r--r--tools/addon-sdk-1.12/test/modules/orange.js10
-rw-r--r--tools/addon-sdk-1.12/test/modules/pollux.js10
-rw-r--r--tools/addon-sdk-1.12/test/modules/red.js16
-rw-r--r--tools/addon-sdk-1.12/test/modules/setExports.js5
-rw-r--r--tools/addon-sdk-1.12/test/modules/subtract.js9
-rw-r--r--tools/addon-sdk-1.12/test/modules/tiger.js8
-rw-r--r--tools/addon-sdk-1.12/test/modules/traditional1.js12
-rw-r--r--tools/addon-sdk-1.12/test/modules/traditional2.js6
-rw-r--r--tools/addon-sdk-1.12/test/modules/types/cat.js5
-rw-r--r--tools/addon-sdk-1.12/test/pagemod-test-helpers.js68
-rw-r--r--tools/addon-sdk-1.12/test/private-browsing-helper.js32
-rw-r--r--tools/addon-sdk-1.12/test/tabs/test-fennec-tabs.js621
-rw-r--r--tools/addon-sdk-1.12/test/tabs/test-firefox-tabs.js982
-rw-r--r--tools/addon-sdk-1.12/test/test-addon-installer.js131
-rw-r--r--tools/addon-sdk-1.12/test/test-addon-page.js65
-rw-r--r--tools/addon-sdk-1.12/test/test-api-utils.js224
-rw-r--r--tools/addon-sdk-1.12/test/test-app-strings.js62
-rw-r--r--tools/addon-sdk-1.12/test/test-array.js66
-rw-r--r--tools/addon-sdk-1.12/test/test-base64.js75
-rw-r--r--tools/addon-sdk-1.12/test/test-byte-streams.js169
-rw-r--r--tools/addon-sdk-1.12/test/test-chrome.js86
-rw-r--r--tools/addon-sdk-1.12/test/test-clipboard.js214
-rw-r--r--tools/addon-sdk-1.12/test/test-collection.js127
-rw-r--r--tools/addon-sdk-1.12/test/test-commonjs-test-adapter.js11
-rw-r--r--tools/addon-sdk-1.12/test/test-content-loader.js226
-rw-r--r--tools/addon-sdk-1.12/test/test-content-proxy.js915
-rw-r--r--tools/addon-sdk-1.12/test/test-content-symbiont.js184
-rw-r--r--tools/addon-sdk-1.12/test/test-content-worker.js657
-rw-r--r--tools/addon-sdk-1.12/test/test-context-menu.html46
-rw-r--r--tools/addon-sdk-1.12/test/test-context-menu.js2093
-rw-r--r--tools/addon-sdk-1.12/test/test-cortex.js122
-rw-r--r--tools/addon-sdk-1.12/test/test-cuddlefish.js49
-rw-r--r--tools/addon-sdk-1.12/test/test-deprecate.js78
-rw-r--r--tools/addon-sdk-1.12/test/test-deprecated-list.js196
-rw-r--r--tools/addon-sdk-1.12/test/test-dom.js88
-rw-r--r--tools/addon-sdk-1.12/test/test-environment.js50
-rw-r--r--tools/addon-sdk-1.12/test/test-errors.js70
-rw-r--r--tools/addon-sdk-1.12/test/test-event-core.js216
-rw-r--r--tools/addon-sdk-1.12/test/test-event-target.js167
-rw-r--r--tools/addon-sdk-1.12/test/test-events.js267
-rw-r--r--tools/addon-sdk-1.12/test/test-file.js273
-rw-r--r--tools/addon-sdk-1.12/test/test-frame-utils.js71
-rw-r--r--tools/addon-sdk-1.12/test/test-functional.js170
-rw-r--r--tools/addon-sdk-1.12/test/test-globals.js22
-rw-r--r--tools/addon-sdk-1.12/test/test-heritage.js375
-rw-r--r--tools/addon-sdk-1.12/test/test-hidden-frame.js51
-rw-r--r--tools/addon-sdk-1.12/test/test-hotkeys.js162
-rw-r--r--tools/addon-sdk-1.12/test/test-httpd.js72
-rw-r--r--tools/addon-sdk-1.12/test/test-keyboard-observer.js37
-rw-r--r--tools/addon-sdk-1.12/test/test-keyboard-utils.js62
-rw-r--r--tools/addon-sdk-1.12/test/test-l10n-locale.js141
-rw-r--r--tools/addon-sdk-1.12/test/test-l10n-plural-rules.js82
-rw-r--r--tools/addon-sdk-1.12/test/test-layout-change.js178
-rw-r--r--tools/addon-sdk-1.12/test/test-light-traits.js11
-rw-r--r--tools/addon-sdk-1.12/test/test-list.js43
-rw-r--r--tools/addon-sdk-1.12/test/test-loader.js27
-rw-r--r--tools/addon-sdk-1.12/test/test-match-pattern.js129
-rw-r--r--tools/addon-sdk-1.12/test/test-memory.js19
-rw-r--r--tools/addon-sdk-1.12/test/test-module.js37
-rw-r--r--tools/addon-sdk-1.12/test/test-modules.js148
-rw-r--r--tools/addon-sdk-1.12/test/test-namespace.js122
-rw-r--r--tools/addon-sdk-1.12/test/test-net-url.js212
-rw-r--r--tools/addon-sdk-1.12/test/test-notifications.js46
-rw-r--r--tools/addon-sdk-1.12/test/test-observer-service.js80
-rw-r--r--tools/addon-sdk-1.12/test/test-packaging.js15
-rw-r--r--tools/addon-sdk-1.12/test/test-page-mod.js918
-rw-r--r--tools/addon-sdk-1.12/test/test-page-worker.js384
-rw-r--r--tools/addon-sdk-1.12/test/test-panel.js486
-rw-r--r--tools/addon-sdk-1.12/test/test-passwords-utils.js142
-rw-r--r--tools/addon-sdk-1.12/test/test-passwords.js280
-rw-r--r--tools/addon-sdk-1.12/test/test-plain-text-console.js68
-rw-r--r--tools/addon-sdk-1.12/test/test-preferences-service.js143
-rw-r--r--tools/addon-sdk-1.12/test/test-preferences-target.js42
-rw-r--r--tools/addon-sdk-1.12/test/test-private-browsing.js204
-rw-r--r--tools/addon-sdk-1.12/test/test-promise.js327
-rw-r--r--tools/addon-sdk-1.12/test/test-querystring.js206
-rw-r--r--tools/addon-sdk-1.12/test/test-registry.js80
-rw-r--r--tools/addon-sdk-1.12/test/test-request.js342
-rw-r--r--tools/addon-sdk-1.12/test/test-require.js29
-rw-r--r--tools/addon-sdk-1.12/test/test-sandbox.js121
-rw-r--r--tools/addon-sdk-1.12/test/test-selection.js457
-rw-r--r--tools/addon-sdk-1.12/test/test-self.js57
-rw-r--r--tools/addon-sdk-1.12/test/test-set-exports.js35
-rw-r--r--tools/addon-sdk-1.12/test/test-simple-prefs.js231
-rw-r--r--tools/addon-sdk-1.12/test/test-simple-storage.js311
-rw-r--r--tools/addon-sdk-1.12/test/test-tab-browser.js513
-rw-r--r--tools/addon-sdk-1.12/test/test-tab-observer.js39
-rw-r--r--tools/addon-sdk-1.12/test/test-tab.js111
-rw-r--r--tools/addon-sdk-1.12/test/test-tabs-common.js296
-rw-r--r--tools/addon-sdk-1.12/test/test-tabs.js11
-rw-r--r--tools/addon-sdk-1.12/test/test-text-streams.js156
-rw-r--r--tools/addon-sdk-1.12/test/test-timer.js131
-rw-r--r--tools/addon-sdk-1.12/test/test-tmp-file.js24
-rw-r--r--tools/addon-sdk-1.12/test/test-tmp-file.txt1
-rw-r--r--tools/addon-sdk-1.12/test/test-traceback.js118
-rw-r--r--tools/addon-sdk-1.12/test/test-traits-core.js838
-rw-r--r--tools/addon-sdk-1.12/test/test-traits.js398
-rw-r--r--tools/addon-sdk-1.12/test/test-type.js92
-rw-r--r--tools/addon-sdk-1.12/test/test-unit-test.js254
-rw-r--r--tools/addon-sdk-1.12/test/test-unload.js167
-rw-r--r--tools/addon-sdk-1.12/test/test-url.js255
-rw-r--r--tools/addon-sdk-1.12/test/test-uuid.js27
-rw-r--r--tools/addon-sdk-1.12/test/test-widget.js1152
-rw-r--r--tools/addon-sdk-1.12/test/test-window-loader.js120
-rw-r--r--tools/addon-sdk-1.12/test/test-window-observer.js50
-rw-r--r--tools/addon-sdk-1.12/test/test-window-utils.js350
-rw-r--r--tools/addon-sdk-1.12/test/test-window-utils2.js60
-rw-r--r--tools/addon-sdk-1.12/test/test-windows-common.js59
-rw-r--r--tools/addon-sdk-1.12/test/test-windows-private-browsing.js35
-rw-r--r--tools/addon-sdk-1.12/test/test-windows.js11
-rw-r--r--tools/addon-sdk-1.12/test/test-xhr.js82
-rw-r--r--tools/addon-sdk-1.12/test/test-xpcom.js223
-rw-r--r--tools/addon-sdk-1.12/test/test-xul-app.js45
-rw-r--r--tools/addon-sdk-1.12/test/traits/assert.js98
-rw-r--r--tools/addon-sdk-1.12/test/traits/descriptor-tests.js335
-rw-r--r--tools/addon-sdk-1.12/test/traits/inheritance-tests.js104
-rw-r--r--tools/addon-sdk-1.12/test/traits/object-tests.js321
-rw-r--r--tools/addon-sdk-1.12/test/traits/utils.js56
-rw-r--r--tools/addon-sdk-1.12/test/windows/test-fennec-windows.js46
-rw-r--r--tools/addon-sdk-1.12/test/windows/test-firefox-windows.js369
158 files changed, 23402 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.12/test/addons/l10n/data/test-localization.html b/tools/addon-sdk-1.12/test/addons/l10n/data/test-localization.html
new file mode 100644
index 0000000..5428863
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/addons/l10n/data/test-localization.html
@@ -0,0 +1,24 @@
+<!-- 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>
+ <meta charset="UTF-8">
+ <title>HTML Localization</title>
+ </head>
+ <body>
+ <div data-l10n-id="Not translated">Kept as-is</div>
+ <ul data-l10n-id="Translated">
+ <li>Inner html content is replaced,</li>
+ <li data-l10n-id="text-content">
+ Elements with data-l10n-id attribute whose parent element is translated
+ will be replaced by the content of the translation.
+ </li>
+ </ul>
+ <div data-l10n-id="text-content">No</div>
+ <div data-l10n-id="Translated">
+ A data-l10n-id value can be used in multiple elements
+ </div>
+ </body>
+</html
diff --git a/tools/addon-sdk-1.12/test/addons/l10n/locale/en-GB.properties b/tools/addon-sdk-1.12/test/addons/l10n/locale/en-GB.properties
new file mode 100644
index 0000000..e625690
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/addons/l10n/locale/en-GB.properties
@@ -0,0 +1,16 @@
+# 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/.
+
+Translated= Yes
+
+text-content=no <b>HTML</b> injection
+
+downloadsCount=%d downloads
+downloadsCount[one]=one download
+
+pluralTest=fallback to other
+pluralTest[zero]=optional zero form
+
+explicitPlural[one]=one
+explicitPlural[other]=other
diff --git a/tools/addon-sdk-1.12/test/addons/l10n/locale/eo.properties b/tools/addon-sdk-1.12/test/addons/l10n/locale/eo.properties
new file mode 100644
index 0000000..a979fca
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/addons/l10n/locale/eo.properties
@@ -0,0 +1,5 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Translated= jes
diff --git a/tools/addon-sdk-1.12/test/addons/l10n/locale/fr-FR.properties b/tools/addon-sdk-1.12/test/addons/l10n/locale/fr-FR.properties
new file mode 100644
index 0000000..2c5ffbb
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/addons/l10n/locale/fr-FR.properties
@@ -0,0 +1,14 @@
+# 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/.
+
+Translated= Oui
+
+placeholderString= Placeholder %s
+
+# Plural forms
+%d downloads=%d téléchargements
+%d downloads[one]=%d téléchargement
+
+downloadsCount=%d téléchargements
+downloadsCount[one]=%d téléchargement
diff --git a/tools/addon-sdk-1.12/test/addons/l10n/main.js b/tools/addon-sdk-1.12/test/addons/l10n/main.js
new file mode 100644
index 0000000..b4e980f
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/addons/l10n/main.js
@@ -0,0 +1,181 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const prefs = require("sdk/preferences/service");
+const { Loader } = require('sdk/test/loader');
+const { resolveURI } = require('toolkit/loader');
+const { rootURI } = require("@loader/options");
+
+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);
+}
+
+function definePseudo(loader, id, exports) {
+ let uri = resolveURI(id, loader.mapping);
+ loader.modules[uri] = { exports: exports };
+}
+
+function createTest(locale, testFunction) {
+ return function (assert, done) {
+ let loader = Loader(module);
+ // Change the locale before loading new l10n modules in order to load
+ // the right .json file
+ setLocale(locale);
+ // Initialize main l10n module in order to load new locale files
+ loader.require("sdk/l10n/loader").
+ load(rootURI).
+ then(function success(data) {
+ definePseudo(loader, '@l10n/data', data);
+ // Execute the given test function
+ try {
+ testFunction(assert, loader, function onDone() {
+ loader.unload();
+ resetLocale();
+ done();
+ });
+ }
+ catch(e) {
+ console.exception(e);
+ }
+ },
+ function failure(error) {
+ assert.fail("Unable to load locales: " + error);
+ });
+ };
+}
+
+exports.testExactMatching = createTest("fr-FR", function(assert, loader, done) {
+ let _ = loader.require("sdk/l10n").get;
+ assert.equal(_("Not translated"), "Not translated",
+ "Key not translated");
+ assert.equal(_("Translated"), "Oui",
+ "Simple key translated");
+
+ // Placeholders
+ assert.equal(_("placeholderString", "works"), "Placeholder works",
+ "Value with placeholder");
+ assert.equal(_("Placeholder %s", "works"), "Placeholder works",
+ "Key without value but with placeholder");
+ assert.equal(_("Placeholders %2s %1s %s.", "working", "are", "correctly"),
+ "Placeholders are working correctly.",
+ "Multiple placeholders");
+
+ // Plurals
+ assert.equal(_("downloadsCount", 0),
+ "0 téléchargement",
+ "PluralForm form 'one' for 0 in french");
+ assert.equal(_("downloadsCount", 1),
+ "1 téléchargement",
+ "PluralForm form 'one' for 1 in french");
+ assert.equal(_("downloadsCount", 2),
+ "2 téléchargements",
+ "PluralForm form 'other' for n > 1 in french");
+
+ done();
+});
+
+exports.testHtmlLocalization = createTest("en-GB", function(assert, loader, done) {
+
+ // Ensure initing html component that watch document creations
+ // Note that this module is automatically initialized in
+ // cuddlefish.js:Loader.main in regular addons. But it isn't for unit tests.
+ let loaderHtmlL10n = loader.require("sdk/l10n/html");
+ loaderHtmlL10n.enable();
+
+ let uri = require("sdk/self").data.url("test-localization.html");
+ let worker = loader.require("sdk/page-worker").Page({
+ contentURL: uri,
+ contentScript: "new " + function ContentScriptScope() {
+ let nodes = document.body.querySelectorAll("*[data-l10n-id]");
+ self.postMessage([nodes[0].innerHTML,
+ nodes[1].innerHTML,
+ nodes[2].innerHTML,
+ nodes[3].innerHTML]);
+ },
+ onMessage: function (data) {
+ assert.equal(
+ data[0],
+ "Kept as-is",
+ "Nodes with unknown id in .properties are kept 'as-is'"
+ );
+ assert.equal(data[1], "Yes", "HTML is translated");
+ assert.equal(
+ data[2],
+ "no &lt;b&gt;HTML&lt;/b&gt; injection",
+ "Content from .properties is text content; HTML can't be injected."
+ );
+ assert.equal(data[3], "Yes", "Multiple elements with same data-l10n-id are accepted.");
+
+ done();
+ }
+ });
+
+});
+
+exports.testEnUsLocaleName = createTest("en-US", function(assert, loader, done) {
+ let _ = loader.require("sdk/l10n").get;
+
+ assert.equal(_("Not translated"), "Not translated",
+ "String w/o translation is kept as-is");
+ assert.equal(_("Translated"), "Yes",
+ "String with translation is correctly translated");
+
+ // Check plural forms regular matching
+ assert.equal(_("downloadsCount", 0),
+ "0 downloads",
+ "PluralForm form 'other' for 0 in english");
+ assert.equal(_("downloadsCount", 1),
+ "one download",
+ "PluralForm form 'one' for 1 in english");
+ assert.equal(_("downloadsCount", 2),
+ "2 downloads",
+ "PluralForm form 'other' for n != 1 in english");
+
+ // Check optional plural forms
+ assert.equal(_("pluralTest", 0),
+ "optional zero form",
+ "PluralForm form 'zero' can be optionaly specified. (Isn't mandatory in english)");
+ assert.equal(_("pluralTest", 1),
+ "fallback to other",
+ "If the specific plural form is missing, we fallback to 'other'");
+
+ // Ensure that we can omit specifying the generic key without [other]
+ // key[one] = ...
+ // key[other] = ... # Instead of `key = ...`
+ assert.equal(_("explicitPlural", 1),
+ "one",
+ "PluralForm form can be omitting generic key [i.e. without ...[other] at end of key)");
+ assert.equal(_("explicitPlural", 10),
+ "other",
+ "PluralForm form can be omitting generic key [i.e. without ...[other] at end of key)");
+
+ done();
+});
+
+exports.testShortLocaleName = createTest("eo", function(assert, loader, done) {
+ let _ = loader.require("sdk/l10n").get;
+ assert.equal(_("Not translated"), "Not translated",
+ "String w/o translation is kept as-is");
+ assert.equal(_("Translated"), "jes",
+ "String with translation is correctly translated");
+
+ done();
+});
+
+
+// Before running tests, disable HTML service which is automatially enabled
+// in api-utils/addon/runner.js
+require('sdk/l10n/html').disable();
+
+require("sdk/test/runner").runTestsFromModule(module);
diff --git a/tools/addon-sdk-1.12/test/addons/l10n/package.json b/tools/addon-sdk-1.12/test/addons/l10n/package.json
new file mode 100644
index 0000000..a13c952
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/addons/l10n/package.json
@@ -0,0 +1,3 @@
+{
+ "id": "test-l10n"
+} \ No newline at end of file
diff --git a/tools/addon-sdk-1.12/test/commonjs-test-adapter/asserts.js b/tools/addon-sdk-1.12/test/commonjs-test-adapter/asserts.js
new file mode 100644
index 0000000..8618ace
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/commonjs-test-adapter/asserts.js
@@ -0,0 +1,54 @@
+/* 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 AssertBase = require("test/assert").Assert;
+
+/**
+ * Generates custom assertion constructors that may be bundled with a test
+ * suite.
+ * @params {String}
+ * names of assertion function to be added to the generated Assert.
+ */
+function Assert() {
+ let assertDescriptor = {};
+ Array.forEach(arguments, function(name) {
+ assertDescriptor[name] = { value: function(message) {
+ this.pass(message);
+ }}
+ });
+
+ return function Assert() {
+ return Object.create(AssertBase.apply(null, arguments), assertDescriptor);
+ };
+}
+
+exports["test suite"] = {
+ Assert: Assert("foo"),
+ "test that custom assertor is passed to test function": function(assert) {
+ assert.ok("foo" in assert, "custom assertion function `foo` is defined");
+ assert.foo("custom assertion function `foo` is called");
+ },
+ "test sub suite": {
+ "test that `Assert` is inherited by sub suits": function(assert) {
+ assert.ok("foo" in assert, "assertion function `foo` is not defined");
+ },
+ "test sub sub suite": {
+ Assert: Assert("bar"),
+ "test that custom assertor is passed to test function": function(assert) {
+ assert.ok("bar" in assert,
+ "custom assertion function `bar` is defined");
+ assert.bar("custom assertion function `bar` is called");
+ },
+ "test that `Assert` is not inherited by sub sub suits": function(assert) {
+ assert.ok(!("foo" in assert),
+ "assertion function `foo` is not defined");
+ }
+ }
+ }
+};
+
+if (module == require.main)
+ require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/fixtures/addon-install-unit-test@mozilla.com.xpi b/tools/addon-sdk-1.12/test/fixtures/addon-install-unit-test@mozilla.com.xpi
new file mode 100644
index 0000000..c7cfccd
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/fixtures/addon-install-unit-test@mozilla.com.xpi
Binary files differ
diff --git a/tools/addon-sdk-1.12/test/fixtures/chrome-worker/addEventListener.js b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/addEventListener.js
new file mode 100644
index 0000000..a251701
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/addEventListener.js
@@ -0,0 +1,8 @@
+/* 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';
+
+postMessage('Hello');
diff --git a/tools/addon-sdk-1.12/test/fixtures/chrome-worker/jsctypes.js b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/jsctypes.js
new file mode 100644
index 0000000..8d5ea9a
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/jsctypes.js
@@ -0,0 +1,8 @@
+/* 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';
+
+postMessage(typeof ctypes.open);
diff --git a/tools/addon-sdk-1.12/test/fixtures/chrome-worker/onerror.js b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/onerror.js
new file mode 100644
index 0000000..7310a70
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/onerror.js
@@ -0,0 +1,8 @@
+/* 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';
+
+throw new Error('ok');
diff --git a/tools/addon-sdk-1.12/test/fixtures/chrome-worker/onmessage.js b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/onmessage.js
new file mode 100644
index 0000000..42962f9
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/onmessage.js
@@ -0,0 +1,10 @@
+/* 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';
+
+onmessage = function (event) {
+ postMessage(event.data);
+};
diff --git a/tools/addon-sdk-1.12/test/fixtures/chrome-worker/setTimeout.js b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/setTimeout.js
new file mode 100644
index 0000000..1a137f1
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/setTimeout.js
@@ -0,0 +1,10 @@
+/* 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';
+
+setTimeout(function () {
+ postMessage('ok');
+}, 0);
diff --git a/tools/addon-sdk-1.12/test/fixtures/chrome-worker/xhr.js b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/xhr.js
new file mode 100644
index 0000000..7f76289
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/xhr.js
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+'use strict';
+
+let xhr = XMLHttpRequest();
+xhr.open("GET", "data:text/plain,ok", true);
+xhr.onload = function () {
+ postMessage(xhr.responseText);
+};
+xhr.send(null);
diff --git a/tools/addon-sdk-1.12/test/fixtures/es5.js b/tools/addon-sdk-1.12/test/fixtures/es5.js
new file mode 100644
index 0000000..746cae3
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/fixtures/es5.js
@@ -0,0 +1,8 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+exports.frozen = Object.freeze({});
+exports.sealed = Object.seal({});
+exports.inextensible = Object.preventExtensions({});
diff --git a/tools/addon-sdk-1.12/test/fixtures/loader/cycles/a.js b/tools/addon-sdk-1.12/test/fixtures/loader/cycles/a.js
new file mode 100644
index 0000000..f6e13cc
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/fixtures/loader/cycles/a.js
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+exports.b = require('b');
diff --git a/tools/addon-sdk-1.12/test/fixtures/loader/cycles/b.js b/tools/addon-sdk-1.12/test/fixtures/loader/cycles/b.js
new file mode 100644
index 0000000..69e23f1
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/fixtures/loader/cycles/b.js
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+exports.a = require('a');
diff --git a/tools/addon-sdk-1.12/test/fixtures/loader/cycles/c.js b/tools/addon-sdk-1.12/test/fixtures/loader/cycles/c.js
new file mode 100644
index 0000000..ce34b3c
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/fixtures/loader/cycles/c.js
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+exports.main = require('main');
diff --git a/tools/addon-sdk-1.12/test/fixtures/loader/cycles/main.js b/tools/addon-sdk-1.12/test/fixtures/loader/cycles/main.js
new file mode 100644
index 0000000..6d13200
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/fixtures/loader/cycles/main.js
@@ -0,0 +1,14 @@
+/* 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 a = require('a');
+var b = require('b');
+var c = require('c');
+
+exports.a = a;
+exports.b = b;
+exports.c = c;
+exports.main = exports;
diff --git a/tools/addon-sdk-1.12/test/fixtures/sandbox-complex-character.js b/tools/addon-sdk-1.12/test/fixtures/sandbox-complex-character.js
new file mode 100644
index 0000000..6ae6769
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/fixtures/sandbox-complex-character.js
@@ -0,0 +1,5 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var chars = 'გამარჯობა';
diff --git a/tools/addon-sdk-1.12/test/fixtures/sandbox-normal.js b/tools/addon-sdk-1.12/test/fixtures/sandbox-normal.js
new file mode 100644
index 0000000..5425298
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/fixtures/sandbox-normal.js
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var a = 1;
+this.b = 2;
+function f() { return 4; }
diff --git a/tools/addon-sdk-1.12/test/loader/fixture.js b/tools/addon-sdk-1.12/test/loader/fixture.js
new file mode 100644
index 0000000..ebf91ab
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/loader/fixture.js
@@ -0,0 +1,7 @@
+/* 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/. */
+
+exports.foo = foo;
+exports.bar = 2;
+print('testing');
diff --git a/tools/addon-sdk-1.12/test/modules/add.js b/tools/addon-sdk-1.12/test/modules/add.js
new file mode 100644
index 0000000..5472934
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/add.js
@@ -0,0 +1,9 @@
+/* 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/. */
+
+define('modules/add', function () {
+ return function (a, b) {
+ return a + b;
+ };
+});
diff --git a/tools/addon-sdk-1.12/test/modules/async1.js b/tools/addon-sdk-1.12/test/modules/async1.js
new file mode 100644
index 0000000..b7b60fd
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/async1.js
@@ -0,0 +1,14 @@
+/* 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/. */
+
+define(['./traditional2', './async2'], function () {
+ var traditional2 = require('./traditional2');
+ return {
+ name: 'async1',
+ traditional1Name: traditional2.traditional1Name,
+ traditional2Name: traditional2.name,
+ async2Name: require('./async2').name,
+ async2Traditional2Name: require('./async2').traditional2Name
+ };
+});
diff --git a/tools/addon-sdk-1.12/test/modules/async2.js b/tools/addon-sdk-1.12/test/modules/async2.js
new file mode 100644
index 0000000..802fb25
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/async2.js
@@ -0,0 +1,8 @@
+/* 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/. */
+
+define(['./traditional2', 'exports'], function (traditional2, exports) {
+ exports.name = 'async2';
+ exports.traditional2Name = traditional2.name;
+});
diff --git a/tools/addon-sdk-1.12/test/modules/badExportAndReturn.js b/tools/addon-sdk-1.12/test/modules/badExportAndReturn.js
new file mode 100644
index 0000000..35a5359
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/badExportAndReturn.js
@@ -0,0 +1,10 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This is a bad module, it asks for exports but also returns a value from
+// the define defintion function.
+define(['exports'], function (exports) {
+ return 'badExportAndReturn';
+});
+
diff --git a/tools/addon-sdk-1.12/test/modules/badFirst.js b/tools/addon-sdk-1.12/test/modules/badFirst.js
new file mode 100644
index 0000000..fdb03bd
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/badFirst.js
@@ -0,0 +1,9 @@
+/* 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/. */
+
+define(['./badSecond'], function (badSecond) {
+ return {
+ name: 'badFirst'
+ };
+});
diff --git a/tools/addon-sdk-1.12/test/modules/badSecond.js b/tools/addon-sdk-1.12/test/modules/badSecond.js
new file mode 100644
index 0000000..8321a85
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/badSecond.js
@@ -0,0 +1,8 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var first = require('./badFirst');
+
+exports.name = 'badSecond';
+exports.badFirstName = first.name;
diff --git a/tools/addon-sdk-1.12/test/modules/blue.js b/tools/addon-sdk-1.12/test/modules/blue.js
new file mode 100644
index 0000000..14ab149
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/blue.js
@@ -0,0 +1,9 @@
+/* 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/. */
+
+define(function () {
+ return {
+ name: 'blue'
+ };
+});
diff --git a/tools/addon-sdk-1.12/test/modules/castor.js b/tools/addon-sdk-1.12/test/modules/castor.js
new file mode 100644
index 0000000..9209623
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/castor.js
@@ -0,0 +1,10 @@
+/* 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/. */
+
+define(['exports', './pollux'], function(exports, pollux) {
+ exports.name = 'castor';
+ exports.getPolluxName = function () {
+ return pollux.name;
+ };
+});
diff --git a/tools/addon-sdk-1.12/test/modules/cheetah.js b/tools/addon-sdk-1.12/test/modules/cheetah.js
new file mode 100644
index 0000000..37d12bf
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/cheetah.js
@@ -0,0 +1,9 @@
+/* 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/. */
+
+define(function () {
+ return function () {
+ return 'cheetah';
+ };
+});
diff --git a/tools/addon-sdk-1.12/test/modules/color.js b/tools/addon-sdk-1.12/test/modules/color.js
new file mode 100644
index 0000000..0d946bd
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/color.js
@@ -0,0 +1,7 @@
+/* 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/. */
+
+define({
+ type: 'color'
+});
diff --git a/tools/addon-sdk-1.12/test/modules/dupe.js b/tools/addon-sdk-1.12/test/modules/dupe.js
new file mode 100644
index 0000000..f87fa55
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/dupe.js
@@ -0,0 +1,15 @@
+/* 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/. */
+
+define({
+ name: 'dupe'
+});
+
+// This is wrong and should not be allowed. Only one call to
+// define per file.
+define([], function () {
+ return {
+ name: 'dupe2'
+ };
+});
diff --git a/tools/addon-sdk-1.12/test/modules/dupeNested.js b/tools/addon-sdk-1.12/test/modules/dupeNested.js
new file mode 100644
index 0000000..703948c
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/dupeNested.js
@@ -0,0 +1,15 @@
+/* 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/. */
+
+
+define(function () {
+ // This is wrong and should not be allowed.
+ define('dupeNested2', {
+ name: 'dupeNested2'
+ });
+
+ return {
+ name: 'dupeNested'
+ };
+});
diff --git a/tools/addon-sdk-1.12/test/modules/dupeSetExports.js b/tools/addon-sdk-1.12/test/modules/dupeSetExports.js
new file mode 100644
index 0000000..12a1be2
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/dupeSetExports.js
@@ -0,0 +1,8 @@
+/* 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/. */
+
+define({name: "dupeSetExports"});
+
+// so this should cause a failure
+module.setExports("no no no");
diff --git a/tools/addon-sdk-1.12/test/modules/exportsEquals.js b/tools/addon-sdk-1.12/test/modules/exportsEquals.js
new file mode 100644
index 0000000..e176316
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/exportsEquals.js
@@ -0,0 +1,5 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+module.exports = 4;
diff --git a/tools/addon-sdk-1.12/test/modules/green.js b/tools/addon-sdk-1.12/test/modules/green.js
new file mode 100644
index 0000000..ce2d134
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/green.js
@@ -0,0 +1,10 @@
+/* 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/. */
+
+define('modules/green', ['./color'], function (color) {
+ return {
+ name: 'green',
+ parentType: color.type
+ };
+});
diff --git a/tools/addon-sdk-1.12/test/modules/lion.js b/tools/addon-sdk-1.12/test/modules/lion.js
new file mode 100644
index 0000000..9c3ac3b
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/lion.js
@@ -0,0 +1,7 @@
+/* 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/. */
+
+define(function(require) {
+ return 'lion';
+});
diff --git a/tools/addon-sdk-1.12/test/modules/orange.js b/tools/addon-sdk-1.12/test/modules/orange.js
new file mode 100644
index 0000000..900a32b
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/orange.js
@@ -0,0 +1,10 @@
+/* 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/. */
+
+define(['./color'], function (color) {
+ return {
+ name: 'orange',
+ parentType: color.type
+ };
+});
diff --git a/tools/addon-sdk-1.12/test/modules/pollux.js b/tools/addon-sdk-1.12/test/modules/pollux.js
new file mode 100644
index 0000000..61cf66f
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/pollux.js
@@ -0,0 +1,10 @@
+/* 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/. */
+
+define(['exports', './castor'], function(exports, castor) {
+ exports.name = 'pollux';
+ exports.getCastorName = function () {
+ return castor.name;
+ };
+});
diff --git a/tools/addon-sdk-1.12/test/modules/red.js b/tools/addon-sdk-1.12/test/modules/red.js
new file mode 100644
index 0000000..c47b8e9
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/red.js
@@ -0,0 +1,16 @@
+/* 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/. */
+
+define(function (require) {
+ // comment fake-outs for require finding.
+ // require('bad1');
+ return {
+ name: 'red',
+ parentType: require('./color').type
+ };
+
+ /*
+ require('bad2');
+ */
+});
diff --git a/tools/addon-sdk-1.12/test/modules/setExports.js b/tools/addon-sdk-1.12/test/modules/setExports.js
new file mode 100644
index 0000000..495c301
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/setExports.js
@@ -0,0 +1,5 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+module.setExports(5);
diff --git a/tools/addon-sdk-1.12/test/modules/subtract.js b/tools/addon-sdk-1.12/test/modules/subtract.js
new file mode 100644
index 0000000..06e1053
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/subtract.js
@@ -0,0 +1,9 @@
+/* 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/. */
+
+define(function () {
+ return function (a, b) {
+ return a - b;
+ }
+});
diff --git a/tools/addon-sdk-1.12/test/modules/tiger.js b/tools/addon-sdk-1.12/test/modules/tiger.js
new file mode 100644
index 0000000..e332deb
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/tiger.js
@@ -0,0 +1,8 @@
+/* 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/. */
+
+define(function (require, exports) {
+ exports.name = 'tiger';
+ exports.type = require('modules/types/cat').type;
+});
diff --git a/tools/addon-sdk-1.12/test/modules/traditional1.js b/tools/addon-sdk-1.12/test/modules/traditional1.js
new file mode 100644
index 0000000..28af8ef
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/traditional1.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/. */
+
+exports.name = 'traditional1'
+
+var async1 = require('./async1');
+
+exports.traditional2Name = async1.traditional2Name;
+exports.traditional1Name = async1.traditional1Name;
+exports.async2Name = async1.async2Name;
+exports.async2Traditional2Name = async1.async2Traditional2Name;
diff --git a/tools/addon-sdk-1.12/test/modules/traditional2.js b/tools/addon-sdk-1.12/test/modules/traditional2.js
new file mode 100644
index 0000000..67b64ee
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/traditional2.js
@@ -0,0 +1,6 @@
+/* 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/. */
+
+exports.name = 'traditional2';
+exports.traditional1Name = require('./traditional1').name;
diff --git a/tools/addon-sdk-1.12/test/modules/types/cat.js b/tools/addon-sdk-1.12/test/modules/types/cat.js
new file mode 100644
index 0000000..41513d6
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/modules/types/cat.js
@@ -0,0 +1,5 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+exports.type = 'cat';
diff --git a/tools/addon-sdk-1.12/test/pagemod-test-helpers.js b/tools/addon-sdk-1.12/test/pagemod-test-helpers.js
new file mode 100644
index 0000000..c76bcb9
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/pagemod-test-helpers.js
@@ -0,0 +1,68 @@
+/* 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("sdk/timers");
+const xulApp = require("sdk/system/xul-app");
+const { Loader } = require("sdk/test/loader");
+
+/**
+ * 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("sdk/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.12/test/private-browsing-helper.js b/tools/addon-sdk-1.12/test/private-browsing-helper.js
new file mode 100644
index 0000000..01b85e3
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/private-browsing-helper.js
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+let { Cc,Ci } = require('chrome');
+const { Loader } = require('sdk/test/loader');
+let { loader } = LoaderWithHookedConsole();
+const pb = loader.require('sdk/private-browsing');
+const pbUtils = loader.require('sdk/private-browsing/utils');
+
+function LoaderWithHookedConsole() {
+ let errors = [];
+ let loader = Loader(module, {
+ console: Object.create(console, {
+ error: { value: function(e) {
+ if (!/DEPRECATED:/.test(e)) {
+ console.error(e);
+ }
+ }}
+ })
+ });
+
+ return {
+ loader: loader,
+ errors: errors
+ }
+}
+
+exports.pb = pb;
+exports.pbUtils = pbUtils;
+exports.LoaderWithHookedConsole = LoaderWithHookedConsole;
diff --git a/tools/addon-sdk-1.12/test/tabs/test-fennec-tabs.js b/tools/addon-sdk-1.12/test/tabs/test-fennec-tabs.js
new file mode 100644
index 0000000..f3cd660
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/tabs/test-fennec-tabs.js
@@ -0,0 +1,621 @@
+/* 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('sdk/test/loader');
+const timer = require('sdk/timers');
+const tabs = require('sdk/tabs');
+const windows = require('sdk/windows');
+
+const tabsLen = tabs.length;
+const URL = 'data:text/html;charset=utf-8,<html><head><title>#title#</title></head></html>';
+const ERR_MSG = 'Error: This method is not yet supported by Fennec';
+
+function LoaderWithHookedConsole() {
+ let errors = [];
+ let loader = Loader(module, {
+ console: Object.create(console, {
+ error: { value: function(error) {
+ errors.push(error);
+ }}
+ })
+ });
+
+ return {
+ loader: loader,
+ errors: errors
+ }
+}
+
+// TEST: tab unloader
+exports.testAutomaticDestroy = function(test) {
+ test.waitUntilDone();
+
+ let called = false;
+
+ let loader2 = Loader(module);
+ let loader3 = Loader(module);
+ let tabs2 = loader2.require('sdk/tabs');
+ let tabs3 = loader3.require('sdk/tabs');
+ let tabs2Len = tabs2.length;
+
+ tabs2.on('open', function onOpen(tab) {
+ test.fail("an onOpen listener was called that should not have been");
+ called = true;
+ });
+ tabs2.on('ready', function onReady(tab) {
+ test.fail("an onReady listener was called that should not have been");
+ called = true;
+ });
+ tabs2.on('select', function onSelect(tab) {
+ test.fail("an onSelect listener was called that should not have been");
+ called = true;
+ });
+ tabs2.on('close', function onClose(tab) {
+ test.fail("an onClose listener was called that should not have been");
+ called = true;
+ });
+ loader2.unload();
+
+ tabs3.on('open', function onOpen(tab) {
+ test.pass("an onOpen listener was called for tabs3");
+
+ tab.on('ready', function onReady(tab) {
+ test.fail("an onReady listener was called that should not have been");
+ called = true;
+ });
+ tab.on('select', function onSelect(tab) {
+ test.fail("an onSelect listener was called that should not have been");
+ called = true;
+ });
+ tab.on('close', function onClose(tab) {
+ test.fail("an onClose listener was called that should not have been");
+ called = true;
+ });
+ });
+ tabs3.open(URL.replace(/#title#/, 'tabs3'));
+ loader3.unload();
+
+ // Fire a tab event and ensure that the destroyed tab is inactive
+ tabs.once('open', function(tab) {
+ test.pass('tabs.once("open") works!');
+
+ test.assertEqual(tabs2Len, tabs2.length, "tabs2 length was not changed");
+ test.assertEqual(tabs.length, (tabs2.length+2), "tabs.length > tabs2.length");
+
+ tab.once('ready', function() {
+ test.pass('tab.once("ready") works!');
+
+ tab.once('close', function() {
+ test.pass('tab.once("close") works!');
+
+ timer.setTimeout(function () {
+ test.assert(!called, "Unloaded tab module is destroyed and inactive");
+
+ // end test
+ test.done();
+ });
+ });
+
+ tab.close();
+ });
+ });
+
+ tabs.open('data:text/html;charset=utf-8,foo');
+};
+
+// TEST: tab properties
+exports.testTabProperties = function(test) {
+ test.waitUntilDone();
+ let { loader, errors } = LoaderWithHookedConsole();
+ let tabs = loader.require('sdk/tabs');
+
+ let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>";
+ let tabsLen = tabs.length;
+ 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");
+ // TODO: remove need for this test by implementing the favicon feature
+ test.assertEqual(errors.length, 1, "favicon logs an error for now");
+ test.assertEqual(tab.style, null, "style of the new tab matches");
+ test.assertEqual(tab.index, tabsLen, "index of the new tab matches");
+ test.assertNotEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
+
+ tab.close(function() {
+ loader.unload();
+
+ // end test
+ test.done();
+ });
+ }
+ });
+};
+
+// TEST: tabs iterator and length property
+exports.testTabsIteratorAndLength = function(test) {
+ test.waitUntilDone();
+
+ let newTabs = [];
+ let startCount = 0;
+ for each (let t in tabs) startCount++;
+
+ test.assertEqual(startCount, tabs.length, "length property is correct");
+
+ let url = "data:text/html;charset=utf-8,testTabsIteratorAndLength";
+ tabs.open({url: url, onOpen: function(tab) newTabs.push(tab)});
+ tabs.open({url: url, onOpen: function(tab) newTabs.push(tab)});
+ 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");
+
+ let newTabsLength = newTabs.length;
+ newTabs.forEach(function(t) t.close(function() {
+ if (--newTabsLength > 0) return;
+
+ tab.close(function() {
+ // end test
+ test.done();
+ });
+ }));
+ }
+ });
+};
+
+// TEST: tab.url setter
+exports.testTabLocation = function(test) {
+ test.waitUntilDone();
+
+ let url1 = "data:text/html;charset=utf-8,foo";
+ let url2 = "data:text/html;charset=utf-8,bar";
+
+ tabs.on('ready', function onReady(tab) {
+ if (tab.url != url2)
+ return;
+
+ tabs.removeListener('ready', onReady);
+ test.pass("tab loaded the correct url");
+
+ tab.close(function() {
+ // end test
+ test.done();
+ });
+ });
+
+ tabs.open({
+ url: url1,
+ onOpen: function(tab) {
+ tab.url = url2;
+ }
+ });
+};
+
+// TEST: tab.reload()
+exports.testTabReload = function(test) {
+ test.waitUntilDone();
+
+ let url = "data:text/html;charset=utf-8,<!doctype%20html><title></title>";
+
+ tabs.open({
+ url: url,
+ onReady: function onReady(tab) {
+ tab.removeListener('ready', onReady);
+
+ tab.once(
+ 'ready',
+ function onReload() {
+ test.pass("the tab was loaded again");
+ test.assertEqual(tab.url, url, "the tab has the same URL");
+
+ // end test
+ tab.close(function() test.done());
+ }
+ );
+
+ tab.reload();
+ }
+ });
+};
+
+// TEST: tab.move()
+exports.testTabMove = function(test) {
+ test.waitUntilDone();
+
+ let { loader, errors } = LoaderWithHookedConsole();
+ let tabs = loader.require('sdk/tabs');
+
+ let url = "data:text/html;charset=utf-8,testTabMove";
+
+ tabs.open({
+ url: url,
+ onOpen: function(tab1) {
+ test.assert(tab1.index >= 0, "opening a tab returns a tab w/ valid index");
+
+ tabs.open({
+ url: url,
+ onOpen: function(tab) {
+ let i = tab.index;
+ test.assert(tab.index > tab1.index, "2nd tab has valid index");
+ tab.index = 0;
+ test.assertEqual(tab.index, i, "tab index after move matches");
+ test.assertEqual(errors.length, 1, "setting tab.index logs error");
+
+ // end test
+ tab1.close(function() tab.close(function() {
+ loader.unload();
+ test.done();
+ }));
+ }
+ });
+ }
+ });
+};
+
+// TEST: open tab with default options
+exports.testTabsOpen_alt = function(test) {
+ test.waitUntilDone();
+
+ let { loader, errors } = LoaderWithHookedConsole();
+ let tabs = loader.require('sdk/tabs');
+ let url = "data:text/html;charset=utf-8,default";
+
+ tabs.open({
+ url: url,
+ onReady: function(tab) {
+ test.assertEqual(tab.url, url, "URL of the new tab matches");
+ test.assertEqual(tabs.activeTab, tab, "URL of active tab in the current window matches");
+ test.assertEqual(tab.isPinned, false, "The new tab is not pinned");
+ test.assertEqual(errors.length, 1, "isPinned logs error");
+
+ // end test
+ tab.close(function() {
+ loader.unload();
+ test.done();
+ });
+ }
+ });
+};
+
+// TEST: open pinned tab
+exports.testOpenPinned_alt = function(test) {
+ test.waitUntilDone();
+
+ let { loader, errors } = LoaderWithHookedConsole();
+ let tabs = loader.require('sdk/tabs');
+ let url = "about:blank";
+
+ tabs.open({
+ url: url,
+ isPinned: true,
+ onOpen: function(tab) {
+ test.assertEqual(tab.isPinned, false, "The new tab is pinned");
+ test.assertEqual(errors.length, 2, "isPinned logs error");
+
+ // end test
+ tab.close(function() {
+ loader.unload();
+ test.done();
+ });
+ }
+ });
+};
+
+// TEST: pin/unpin opened tab
+exports.testPinUnpin_alt = function(test) {
+ test.waitUntilDone();
+
+ let { loader, errors } = LoaderWithHookedConsole();
+ let tabs = loader.require('sdk/tabs');
+ let url = "data:text/html;charset=utf-8,default";
+
+ tabs.open({
+ url: url,
+ onOpen: function(tab) {
+ tab.pin();
+ test.assertEqual(tab.isPinned, false, "The tab was pinned correctly");
+ test.assertEqual(errors.length, 2, "tab.pin() logs error");
+
+ tab.unpin();
+ test.assertEqual(tab.isPinned, false, "The tab was unpinned correctly");
+ test.assertEqual(errors.length, 4, "tab.unpin() logs error");
+
+ // end test
+ tab.close(function() {
+ loader.unload();
+ test.done();
+ });
+ }
+ });
+};
+
+// TEST: open tab in background
+exports.testInBackground = function(test) {
+ test.waitUntilDone();
+
+ let activeUrl = tabs.activeTab.url;
+ let url = "data:text/html;charset=utf-8,background";
+ let window = windows.browserWindows.activeWindow;
+ tabs.once('ready', function onReady(tab) {
+ 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(windows.browserWindows.activeWindow, window, "a new window was not opened");
+ test.assertNotEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL");
+
+ // end test
+ tab.close(function() test.done());
+ });
+
+ tabs.open({
+ url: url,
+ inBackground: true
+ });
+};
+
+// TEST: open tab in new window
+exports.testOpenInNewWindow = function(test) {
+ test.waitUntilDone();
+
+ let url = "data:text/html;charset=utf-8,newwindow";
+ let window = windows.browserWindows.activeWindow;
+
+ tabs.open({
+ url: url,
+ inNewWindow: true,
+ onReady: function(tab) {
+ test.assertEqual(windows.browserWindows.length, 1, "a new window was not opened");
+ test.assertEqual(windows.browserWindows.activeWindow, window, "old window is active");
+ test.assertEqual(tab.url, url, "URL of the new tab matches");
+ test.assertEqual(tabs.activeTab, tab, "tab is the activeTab");
+
+ tab.close(function() test.done());
+ }
+ });
+};
+
+// TEST: onOpen event handler
+exports.testTabsEvent_onOpen = function(test) {
+ test.waitUntilDone();
+
+ let url = URL.replace('#title#', 'testTabsEvent_onOpen');
+ 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);
+
+ // ends test
+ tab.close(function() test.done());
+ });
+
+ tabs.open(url);
+};
+
+// TEST: onClose event handler
+exports.testTabsEvent_onClose = function(test) {
+ test.waitUntilDone();
+
+ let url = "data:text/html;charset=utf-8,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);
+
+ // end test
+ test.done();
+ });
+
+ tabs.on('ready', function onReady(tab) {
+ tabs.removeListener('ready', onReady);
+ tab.close();
+ });
+
+ tabs.open(url);
+};
+
+// TEST: onClose event handler when a window is closed
+exports.testTabsEvent_onCloseWindow = function(test) {
+ test.waitUntilDone();
+
+ let closeCount = 0, individualCloseCount = 0;
+ function listener() {
+ closeCount++;
+ }
+ tabs.on('close', listener);
+
+ // One tab is already open with the window
+ let openTabs = 0;
+ function testCasePossiblyLoaded(tab) {
+ tab.close(function() {
+ if (++openTabs == 3) {
+ tabs.removeListener("close", listener);
+
+ test.assertEqual(closeCount, 3, "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();
+ }
+ });
+ }
+
+ tabs.open({
+ url: "data:text/html;charset=utf-8,tab2",
+ onOpen: testCasePossiblyLoaded,
+ onClose: function() individualCloseCount++
+ });
+
+ tabs.open({
+ url: "data:text/html;charset=utf-8,tab3",
+ onOpen: testCasePossiblyLoaded,
+ onClose: function() individualCloseCount++
+ });
+
+ tabs.open({
+ url: "data:text/html;charset=utf-8,tab4",
+ onOpen: testCasePossiblyLoaded,
+ onClose: function() individualCloseCount++
+ });
+};
+
+// TEST: onReady event handler
+exports.testTabsEvent_onReady = function(test) {
+ test.waitUntilDone();
+
+ let url = "data:text/html;charset=utf-8,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);
+
+ // end test
+ tab.close(function() test.done());
+ });
+
+ tabs.open(url);
+};
+
+// TEST: onActivate event handler
+exports.testTabsEvent_onActivate = function(test) {
+ test.waitUntilDone();
+
+ let url = "data:text/html;charset=utf-8,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");
+ test.assertEqual(tab, tabs.activeTab, 'the active tab is correct');
+ tabs.removeListener('activate', listener1);
+ tabs.removeListener('activate', listener2);
+
+ // end test
+ tab.close(function() test.done());
+ });
+
+ tabs.open(url);
+};
+
+// TEST: onDeactivate event handler
+exports.testTabsEvent_onDeactivate = function(test) {
+ test.waitUntilDone();
+
+ let url = "data:text/html;charset=utf-8,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');
+ test.assertNotEqual(tab, tabs.activeTab, 'the active tab is not the deactivated tab');
+ tabs.removeListener('deactivate', listener1);
+ tabs.removeListener('deactivate', listener2);
+
+ // end test
+ tab.close(function() test.done());
+ });
+
+ tabs.on('activate', function onActivate(tab) {
+ tabs.removeListener('activate', onActivate);
+ tabs.open("data:text/html;charset=utf-8,foo");
+ tab.close();
+ });
+
+ tabs.open(url);
+};
+
+// TEST: per-tab event handlers
+exports.testPerTabEvents = function(test) {
+ test.waitUntilDone();
+
+ let eventCount = 0;
+
+ tabs.open({
+ url: "data:text/html;charset=utf-8,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);
+
+ // end test
+ tab.close(function() test.done());
+ });
+ }
+ });
+};
+
+// TEST: tabs.activeTab getter
+exports.testActiveTab_getter_alt = function(test) {
+ test.waitUntilDone();
+
+ let url = URL.replace("#title#", "foo");
+ tabs.open({
+ url: url,
+ onActivate: function(tab) {
+ test.assertEqual(tabs.activeTab.url, tab.url, 'the active tab is correct');
+
+ tab.once('ready', function() {
+ test.assertEqual(tab.url, url);
+ test.assertEqual(tab.title, "foo");
+
+ tab.close(function() {
+ // end test
+ test.done();
+ });
+ });
+ }
+ });
+};
diff --git a/tools/addon-sdk-1.12/test/tabs/test-firefox-tabs.js b/tools/addon-sdk-1.12/test/tabs/test-firefox-tabs.js
new file mode 100644
index 0000000..d7c822b
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/tabs/test-firefox-tabs.js
@@ -0,0 +1,982 @@
+/* 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('sdk/test/loader');
+const timer = require('sdk/timers');
+const { StringBundle } = require('sdk/deprecated/app-strings');
+
+// TEST: tabs.activeTab getter
+exports.testActiveTab_getter = function(test) {
+ test.waitUntilDone();
+
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("sdk/tabs");
+
+ let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head></html>";
+ require("sdk/deprecated/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());
+ }
+ }
+ );
+ });
+};
+
+// Bug 682681 - tab.title should never be empty
+exports.testBug682681_aboutURI = function(test) {
+ test.waitUntilDone();
+
+ let tabStrings = StringBundle('chrome://browser/locale/tabbrowser.properties');
+
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("sdk/tabs");
+
+ tabs.on('ready', function onReady(tab) {
+ tabs.removeListener('ready', onReady);
+
+ test.assertEqual(tab.title,
+ tabStrings.get('tabs.emptyTabTitle'),
+ "title of about: tab is not blank");
+
+ // end of test
+ closeBrowserWindow(window, function() test.done());
+ });
+
+ // open a about: url
+ tabs.open({
+ url: "about:blank",
+ inBackground: true
+ });
+ });
+};
+
+// related to Bug 682681
+exports.testTitleForDataURI = function(test) {
+ test.waitUntilDone();
+
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("sdk/tabs");
+
+ tabs.on('ready', function onReady(tab) {
+ tabs.removeListener('ready', onReady);
+
+ test.assertEqual(tab.title, "tab", "data: title is not Connecting...");
+
+ // end of test
+ closeBrowserWindow(window, function() test.done());
+ });
+
+ // open a about: url
+ tabs.open({
+ url: "data:text/html;charset=utf-8,<title>tab</title>",
+ inBackground: true
+ });
+ });
+};
+
+// 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("sdk/windows").browserWindows;
+ let tabs = require("sdk/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.activate()
+exports.testActiveTab_setter = function(test) {
+ test.waitUntilDone();
+
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("sdk/tabs");
+ let url = "data:text/html;charset=utf-8,<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
+ });
+ });
+};
+
+// TEST: tab unloader
+exports.testAutomaticDestroy = function(test) {
+ test.waitUntilDone();
+
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("sdk/tabs");
+
+ // Create a second tab instance that we will destroy
+ let called = false;
+
+ let loader = Loader(module);
+ let tabs2 = loader.require("sdk/tabs");
+ tabs2.on('open', function onOpen(tab) {
+ called = true;
+ });
+
+ loader.unload();
+
+ // Fire a tab event and ensure that the destroyed tab is inactive
+ tabs.once('open', function () {
+ timer.setTimeout(function () {
+ test.assert(!called, "Unloaded tab module is destroyed and inactive");
+ closeBrowserWindow(window, function() test.done());
+ }, 0);
+ });
+
+ tabs.open("data:text/html;charset=utf-8,foo");
+ });
+};
+
+// test tab properties
+exports.testTabProperties = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs= require("sdk/tabs");
+ let url = "data:text/html;charset=utf-8,<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: tab properties
+exports.testTabContentTypeAndReload = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("sdk/tabs");
+ let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>";
+ let urlXML = "data:text/xml;charset=utf-8,<foo>bar</foo>";
+ tabs.open({
+ url: url,
+ onReady: function(tab) {
+ if (tab.url === url) {
+ test.assertEqual(tab.contentType, "text/html");
+ tab.url = urlXML;
+ } else {
+ test.assertEqual(tab.contentType, "text/xml");
+ closeBrowserWindow(window, function() test.done());
+ }
+ }
+ });
+ });
+};
+
+// TEST: tabs iterator and length property
+exports.testTabsIteratorAndLength = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("sdk/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;charset=utf-8,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("sdk/tabs");
+ let url1 = "data:text/html;charset=utf-8,foo";
+ let url2 = "data:text/html;charset=utf-8,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("sdk/tabs");
+ let url = "data:text/html;charset=utf-8,foo";
+
+ test.assertNotEqual(tabs.activeTab.url, url, "tab is not 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.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
+ 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("sdk/tabs");
+ let url = "data:text/html;charset=utf-8,<!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("sdk/tabs");
+ let url = "data:text/html;charset=utf-8,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());
+ }
+ });
+ });
+};
+
+// TEST: open tab with default options
+exports.testOpen = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("sdk/tabs");
+ let url = "data:text/html;charset=utf-8,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");
+ test.assertEqual(tab.isPinned, false, "The new tab is not pinned");
+
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ });
+};
+
+// TEST: open pinned tab
+exports.testOpenPinned = function(test) {
+ const xulApp = require("sdk/system/xul-app");
+ if (xulApp.versionInRange(xulApp.platformVersion, "2.0b2", "*")) {
+ // test tab pinning
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("sdk/tabs");
+ let url = "data:text/html;charset=utf-8,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.");
+ }
+};
+
+// TEST: pin/unpin opened tab
+exports.testPinUnpin = function(test) {
+ const xulApp = require("sdk/system/xul-app");
+ if (xulApp.versionInRange(xulApp.platformVersion, "2.0b2", "*")) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("sdk/tabs");
+ let url = "data:text/html;charset=utf-8,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.");
+ }
+};
+
+// TEST: open tab in background
+exports.testInBackground = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("sdk/tabs");
+ let activeUrl = tabs.activeTab.url;
+ let url = "data:text/html;charset=utf-8,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
+ });
+ });
+};
+
+// TEST: open tab in new window
+exports.testOpenInNewWindow = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("sdk/tabs");
+
+ let cache = [];
+ let windowUtils = require("sdk/deprecated/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;charset=utf-8,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());
+ });
+ }
+ });
+ });
+};
+
+// TEST: onOpen event handler
+exports.testTabsEvent_onOpen = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("sdk/tabs");
+ let url = "data:text/html;charset=utf-8,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);
+ });
+};
+
+// TEST: onClose event handler
+exports.testTabsEvent_onClose = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("sdk/tabs");
+ let url = "data:text/html;charset=utf-8,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);
+ });
+};
+
+// TEST: onClose event handler when a window is closed
+exports.testTabsEvent_onCloseWindow = function(test) {
+ test.waitUntilDone();
+
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("sdk/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;charset=utf-8,tab2",
+ onOpen: function() testCasePossiblyLoaded(),
+ onClose: function() individualCloseCount++
+ });
+
+ tabs.open({
+ url: "data:text/html;charset=utf-8,tab3",
+ onOpen: function() testCasePossiblyLoaded(),
+ onClose: function() individualCloseCount++
+ });
+
+ tabs.open({
+ url: "data:text/html;charset=utf-8,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();
+ });
+ }
+
+ });
+}
+
+// TEST: onReady event handler
+exports.testTabsEvent_onReady = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("sdk/tabs");
+ let url = "data:text/html;charset=utf-8,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);
+ });
+};
+
+// TEST: onActivate event handler
+exports.testTabsEvent_onActivate = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("sdk/tabs");
+ let url = "data:text/html;charset=utf-8,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("sdk/tabs");
+ let url = "data:text/html;charset=utf-8,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;charset=utf-8,foo");
+ });
+
+ tabs.open(url);
+ });
+};
+
+// pinning
+exports.testTabsEvent_pinning = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("sdk/tabs");
+ let url = "data:text/html;charset=utf-8,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);
+ });
+};
+
+// TEST: per-tab event handlers
+exports.testPerTabEvents = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("sdk/tabs");
+ let eventCount = 0;
+
+ tabs.open({
+ url: "data:text/html;charset=utf-8,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("sdk/tabs");
+
+ tabs.open({
+ url: "data:text/html;charset=utf-8,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("sdk/tabs");
+ let firstLocation = "data:text/html;charset=utf-8,foobar";
+ let secondLocation = "data:text/html;charset=utf-8,bar";
+ let thirdLocation = "data:text/html;charset=utf-8,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");
+ },
+ /Couldn't find the worker/,
+ "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");
+ },
+ /Couldn't find the worker/,
+ "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("sdk/tabs");
+ let document = "data:text/html;charset=utf-8,<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("sdk/tabs");
+ let document = "data:text/html;charset=utf-8,<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("sdk/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;charset=utf-8,test window focus changes active tab</br><h1>Window #2");
+ }, "data:text/html;charset=utf-8,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;charset=utf-8,Waiting for ready event!");
+
+ require("sdk/tabs").on("ready", function onReady(tab) {
+ if (tab.url === uri) {
+ require("sdk/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 {
+ 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("sdk/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;
+
+ module.exports = {
+ testAppNotSupported: function (test) {
+ test.pass("the tabs module does not support this application.");
+ }
+ }
+}
diff --git a/tools/addon-sdk-1.12/test/test-addon-installer.js b/tools/addon-sdk-1.12/test/test-addon-installer.js
new file mode 100644
index 0000000..e2d3b10
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-addon-installer.js
@@ -0,0 +1,131 @@
+/* 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, Cu } = require("chrome");
+const AddonInstaller = require("sdk/addon/installer");
+const observers = require("sdk/deprecated/observer-service");
+const { setTimeout } = require("sdk/timers");
+const tmp = require("sdk/test/tmp-file");
+const system = require("sdk/system");
+
+const testFolderURL = module.uri.split('test-addon-installer.js')[0];
+const ADDON_URL = testFolderURL + "fixtures/addon-install-unit-test@mozilla.com.xpi";
+const ADDON_PATH = tmp.createFromURL(ADDON_URL);
+
+exports.testInstall = function (test) {
+ test.waitUntilDone();
+
+ // Save all events distpatched by bootstrap.js of the installed addon
+ let events = [];
+ function eventsObserver(subject, data) {
+ events.push(data);
+ }
+ observers.add("addon-install-unit-test", eventsObserver, false);
+
+ // Install the test addon
+ AddonInstaller.install(ADDON_PATH).then(
+ function onInstalled(id) {
+ test.assertEqual(id, "addon-install-unit-test@mozilla.com", "`id` is valid");
+
+ // Now uninstall it
+ AddonInstaller.uninstall(id).then(function () {
+ // Ensure that bootstrap.js methods of the addon have been called
+ // successfully and in the right order
+ let expectedEvents = ["install", "startup", "shutdown", "uninstall"];
+ test.assertEqual(JSON.stringify(events),
+ JSON.stringify(expectedEvents),
+ "addon's bootstrap.js functions have been called");
+
+ observers.remove("addon-install-unit-test", eventsObserver);
+ test.done();
+ });
+ },
+ function onFailure(code) {
+ test.fail("Install failed: "+code);
+ observers.remove("addon-install-unit-test", eventsObserver);
+ test.done();
+ }
+ );
+}
+
+exports.testFailingInstallWithInvalidPath = function (test) {
+ test.waitUntilDone();
+
+ AddonInstaller.install("invalid-path").then(
+ function onInstalled(id) {
+ test.fail("Unexpected success");
+ test.done();
+ },
+ function onFailure(code) {
+ test.assertEqual(code, AddonInstaller.ERROR_FILE_ACCESS,
+ "Got expected error code");
+ test.done();
+ }
+ );
+}
+
+exports.testFailingInstallWithInvalidFile = function (test) {
+ test.waitUntilDone();
+
+ let directory = system.pathFor("ProfD");
+ AddonInstaller.install(directory).then(
+ function onInstalled(id) {
+ test.fail("Unexpected success");
+ test.done();
+ },
+ function onFailure(code) {
+ test.assertEqual(code, AddonInstaller.ERROR_CORRUPT_FILE,
+ "Got expected error code");
+ test.done();
+ }
+ );
+}
+
+exports.testUpdate = function (test) {
+ test.waitUntilDone();
+
+ // Save all events distpatched by bootstrap.js of the installed addon
+ let events = [];
+ let iteration = 1;
+ function eventsObserver(subject, data) {
+ events.push(data);
+ }
+ observers.add("addon-install-unit-test", eventsObserver);
+
+ function onInstalled(id) {
+ let prefix = "[" + iteration + "] ";
+ test.assertEqual(id, "addon-install-unit-test@mozilla.com",
+ prefix + "`id` is valid");
+
+ // On 2nd and 3rd iteration, we receive uninstall events from the last
+ // previously installed addon
+ let expectedEvents =
+ iteration == 1
+ ? ["install", "startup"]
+ : ["shutdown", "uninstall", "install", "startup"];
+ test.assertEqual(JSON.stringify(events),
+ JSON.stringify(expectedEvents),
+ prefix + "addon's bootstrap.js functions have been called");
+
+ if (iteration++ < 3) {
+ next();
+ }
+ else {
+ observers.remove("addon-install-unit-test", eventsObserver);
+ test.done();
+ }
+ }
+ function onFailure(code) {
+ test.fail("Install failed: "+code);
+ observers.remove("addon-install-unit-test", eventsObserver);
+ test.done();
+ }
+
+ function next() {
+ events = [];
+ AddonInstaller.install(ADDON_PATH).then(onInstalled, onFailure);
+ }
+
+ next();
+}
diff --git a/tools/addon-sdk-1.12/test/test-addon-page.js b/tools/addon-sdk-1.12/test/test-addon-page.js
new file mode 100644
index 0000000..cff3273
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-addon-page.js
@@ -0,0 +1,65 @@
+/* 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, getURI } = require('sdk/tabs/utils');
+const windows = require('sdk/deprecated/window-utils');
+const { Loader } = require('sdk/test/loader');
+const { setTimeout } = require('sdk/timers');
+const { is } = require('sdk/system/xul-app');
+const tabs = require('sdk/tabs');
+
+let uri = require('sdk/self').data.url('index.html');
+
+function isChromeVisible(window) {
+ let x = window.document.documentElement.getAttribute('disablechrome')
+ return x !== '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.equal(isChromeVisible(window), is('Fennec'), '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 are closed on unload'] = function(assert, done) {
+ let loader = Loader(module);
+ loader.require('sdk/addon-page');
+
+ // Wait for addon page document to be loaded
+ tabs.once("ready", function listener(tab) {
+ // Ignore loading of about:blank document
+ if (tab.url != uri)
+ return;
+
+ loader.unload();
+ assert.ok(!isTabOpen(tab), 'add-on page tabs are closed on unload');
+
+ done();
+ }, false);
+
+ tabs.open(uri);
+};
+
+
+require('sdk/test').run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-api-utils.js b/tools/addon-sdk-1.12/test/test-api-utils.js
new file mode 100644
index 0000000..5ba031a
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-api-utils.js
@@ -0,0 +1,224 @@
+/* -*- 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/. */
+
+const apiUtils = require("sdk/deprecated/api-utils");
+
+exports.testPublicConstructor = function (test) {
+ function PrivateCtor() {}
+ PrivateCtor.prototype = {};
+
+ let PublicCtor = apiUtils.publicConstructor(PrivateCtor);
+ test.assert(
+ PrivateCtor.prototype.isPrototypeOf(PublicCtor.prototype),
+ "PrivateCtor.prototype should be prototype of PublicCtor.prototype"
+ );
+
+ function testObj(useNew) {
+ let obj = useNew ? new PublicCtor() : PublicCtor();
+ test.assert(obj instanceof PublicCtor,
+ "Object should be instance of PublicCtor");
+ test.assert(obj instanceof PrivateCtor,
+ "Object should be instance of PrivateCtor");
+ test.assert(PublicCtor.prototype.isPrototypeOf(obj),
+ "PublicCtor's prototype should be prototype of object");
+ test.assertEqual(obj.constructor, PublicCtor,
+ "Object constructor should be PublicCtor");
+ }
+ testObj(true);
+ testObj(false);
+};
+
+exports.testValidateOptionsEmpty = function (test) {
+ let val = apiUtils.validateOptions(null, {});
+ assertObjsEqual(test, val, {});
+
+ val = apiUtils.validateOptions(null, { foo: {} });
+ assertObjsEqual(test, val, {});
+
+ val = apiUtils.validateOptions({}, {});
+ assertObjsEqual(test, val, {});
+
+ val = apiUtils.validateOptions({}, { foo: {} });
+ assertObjsEqual(test, val, {});
+};
+
+exports.testValidateOptionsNonempty = function (test) {
+ let val = apiUtils.validateOptions({ foo: 123 }, {});
+ assertObjsEqual(test, val, {});
+
+ val = apiUtils.validateOptions({ foo: 123, bar: 456 },
+ { foo: {}, bar: {}, baz: {} });
+ assertObjsEqual(test, val, { foo: 123, bar: 456 });
+};
+
+exports.testValidateOptionsMap = function (test) {
+ let val = apiUtils.validateOptions({ foo: 3, bar: 2 }, {
+ foo: { map: function (v) v * v },
+ bar: { map: function (v) undefined }
+ });
+ assertObjsEqual(test, val, { foo: 9, bar: undefined });
+};
+
+exports.testValidateOptionsMapException = function (test) {
+ let val = apiUtils.validateOptions({ foo: 3 }, {
+ foo: { map: function () { throw new Error(); }}
+ });
+ assertObjsEqual(test, val, { foo: 3 });
+};
+
+exports.testValidateOptionsOk = function (test) {
+ let val = apiUtils.validateOptions({ foo: 3, bar: 2, baz: 1 }, {
+ foo: { ok: function (v) v },
+ bar: { ok: function (v) v }
+ });
+ assertObjsEqual(test, val, { foo: 3, bar: 2 });
+
+ test.assertRaises(
+ function () apiUtils.validateOptions({ foo: 2, bar: 2 }, {
+ bar: { ok: function (v) v > 2 }
+ }),
+ 'The option "bar" is invalid.',
+ "ok should raise exception on invalid option"
+ );
+
+ test.assertRaises(
+ function () apiUtils.validateOptions(null, { foo: { ok: function (v) v }}),
+ 'The option "foo" is invalid.',
+ "ok should raise exception on invalid option"
+ );
+};
+
+exports.testValidateOptionsIs = function (test) {
+ let opts = {
+ array: [],
+ boolean: true,
+ func: function () {},
+ nul: null,
+ number: 1337,
+ object: {},
+ string: "foo",
+ undef1: undefined
+ };
+ let requirements = {
+ array: { is: ["array"] },
+ boolean: { is: ["boolean"] },
+ func: { is: ["function"] },
+ nul: { is: ["null"] },
+ number: { is: ["number"] },
+ object: { is: ["object"] },
+ string: { is: ["string"] },
+ undef1: { is: ["undefined"] },
+ undef2: { is: ["undefined"] }
+ };
+ let val = apiUtils.validateOptions(opts, requirements);
+ assertObjsEqual(test, val, opts);
+
+ test.assertRaises(
+ function () apiUtils.validateOptions(null, {
+ foo: { is: ["object", "number"] }
+ }),
+ 'The option "foo" must be one of the following types: object, number',
+ "Invalid type should raise exception"
+ );
+};
+
+exports.testValidateOptionsMapIsOk = function (test) {
+ let [map, is, ok] = [false, false, false];
+ let val = apiUtils.validateOptions({ foo: 1337 }, {
+ foo: {
+ map: function (v) v.toString(),
+ is: ["string"],
+ ok: function (v) v.length > 0
+ }
+ });
+ assertObjsEqual(test, val, { foo: "1337" });
+
+ let requirements = {
+ foo: {
+ is: ["object"],
+ ok: function () test.fail("is should have caused us to throw by now")
+ }
+ };
+ test.assertRaises(
+ function () apiUtils.validateOptions(null, requirements),
+ 'The option "foo" must be one of the following types: object',
+ "is should be used before ok is called"
+ );
+};
+
+exports.testValidateOptionsErrorMsg = function (test) {
+ test.assertRaises(
+ function () apiUtils.validateOptions(null, {
+ foo: { ok: function (v) v, msg: "foo!" }
+ }),
+ "foo!",
+ "ok should raise exception with customized message"
+ );
+};
+
+exports.testValidateMapWithMissingKey = function (test) {
+ let val = apiUtils.validateOptions({ }, {
+ foo: {
+ map: function (v) v || "bar"
+ }
+ });
+ assertObjsEqual(test, val, { foo: "bar" });
+
+ val = apiUtils.validateOptions({ }, {
+ foo: {
+ map: function (v) { throw "bar" }
+ }
+ });
+ assertObjsEqual(test, val, { });
+};
+
+exports.testAddIterator = function testAddIterator(test) {
+ let obj = {};
+ let keys = ["foo", "bar", "baz"];
+ let vals = [1, 2, 3];
+ let keysVals = [["foo", 1], ["bar", 2], ["baz", 3]];
+ apiUtils.addIterator(
+ obj,
+ function keysValsGen() {
+ for each (let keyVal in keysVals)
+ yield keyVal;
+ }
+ );
+
+ let keysItr = [];
+ for (let key in obj)
+ keysItr.push(key);
+ test.assertEqual(keysItr.length, keys.length,
+ "the keys iterator returns the correct number of items");
+ for (let i = 0; i < keys.length; i++)
+ test.assertEqual(keysItr[i], keys[i], "the key is correct");
+
+ let valsItr = [];
+ for each (let val in obj)
+ valsItr.push(val);
+ test.assertEqual(valsItr.length, vals.length,
+ "the vals iterator returns the correct number of items");
+ for (let i = 0; i < vals.length; i++)
+ test.assertEqual(valsItr[i], vals[i], "the val is correct");
+
+};
+
+function assertObjsEqual(test, obj1, obj2) {
+ var items = 0;
+ for (let key in obj1) {
+ items++;
+ test.assert(key in obj2, "obj1 key should be present in obj2");
+ test.assertEqual(obj2[key], obj1[key], "obj1 value should match obj2 value");
+ }
+ for (let key in obj2) {
+ items++;
+ test.assert(key in obj1, "obj2 key should be present in obj1");
+ test.assertEqual(obj1[key], obj2[key], "obj2 value should match obj1 value");
+ }
+ if (!items)
+ test.assertEqual(JSON.stringify(obj1), JSON.stringify(obj2),
+ "obj1 should have same JSON representation as obj2");
+}
diff --git a/tools/addon-sdk-1.12/test/test-app-strings.js b/tools/addon-sdk-1.12/test/test-app-strings.js
new file mode 100644
index 0000000..faa52fc
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-app-strings.js
@@ -0,0 +1,62 @@
+/* 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");
+
+let StringBundle = require("sdk/deprecated/app-strings").StringBundle;
+exports.testStringBundle = function(test) {
+ let url = "chrome://global/locale/security/caps.properties";
+
+ let strings = StringBundle(url);
+
+ test.assertEqual(strings.url, url,
+ "'url' property contains correct URL of string bundle");
+
+ let appLocale = Cc["@mozilla.org/intl/nslocaleservice;1"].
+ getService(Ci.nsILocaleService).
+ getApplicationLocale();
+
+ let stringBundle = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(url, appLocale);
+
+ let (name = "Yes") {
+ test.assertEqual(strings.get(name), stringBundle.GetStringFromName(name),
+ "getting a string returns the string");
+ }
+
+ let (name = "ExtensionCapability", args = ["foo"]) {
+ test.assertEqual(strings.get(name, args),
+ stringBundle.formatStringFromName(name, args, args.length),
+ "getting a formatted string returns the formatted string");
+ }
+
+ test.assertRaises(function () strings.get("nonexistentString"),
+ "String 'nonexistentString' could not be retrieved from " +
+ "the bundle due to an unknown error (it doesn't exist?).",
+ "retrieving a nonexistent string throws an exception");
+
+ let a = [], b = [];
+ let enumerator = stringBundle.getSimpleEnumeration();
+ while (enumerator.hasMoreElements()) {
+ let elem = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
+ a.push([elem.key, elem.value]);
+ }
+ for (let key in strings)
+ b.push([ key, strings.get(key) ]);
+
+ // Sort the arrays, because we don't assume enumeration has a set order.
+ // Sort compares [key, val] as string "key,val", which sorts the way we want
+ // it to, so there is no need to provide a custom compare function.
+ a.sort();
+ b.sort();
+
+ test.assertEqual(a.length, b.length,
+ "the iterator returns the correct number of items");
+ for (let i = 0; i < a.length; i++) {
+ test.assertEqual(a[i][0], b[i][0], "the iterated string's name is correct");
+ test.assertEqual(a[i][1], b[i][1],
+ "the iterated string's value is correct");
+ }
+};
diff --git a/tools/addon-sdk-1.12/test/test-array.js b/tools/addon-sdk-1.12/test/test-array.js
new file mode 100644
index 0000000..d68891b
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-array.js
@@ -0,0 +1,66 @@
+/* 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 array = require('sdk/util/array');
+
+exports.testHas = function(test) {
+ var testAry = [1, 2, 3];
+ test.assertEqual(array.has([1, 2, 3], 1), true);
+ test.assertEqual(testAry.length, 3);
+ test.assertEqual(testAry[0], 1);
+ test.assertEqual(testAry[1], 2);
+ test.assertEqual(testAry[2], 3);
+ test.assertEqual(array.has(testAry, 2), true);
+ test.assertEqual(array.has(testAry, 3), true);
+ test.assertEqual(array.has(testAry, 4), false);
+ test.assertEqual(array.has(testAry, '1'), false);
+};
+exports.testHasAny = function(test) {
+ var testAry = [1, 2, 3];
+ test.assertEqual(array.hasAny([1, 2, 3], [1]), true);
+ test.assertEqual(array.hasAny([1, 2, 3], [1, 5]), true);
+ test.assertEqual(array.hasAny([1, 2, 3], [5, 1]), true);
+ test.assertEqual(array.hasAny([1, 2, 3], [5, 2]), true);
+ test.assertEqual(array.hasAny([1, 2, 3], [5, 3]), true);
+ test.assertEqual(array.hasAny([1, 2, 3], [5, 4]), false);
+ test.assertEqual(testAry.length, 3);
+ test.assertEqual(testAry[0], 1);
+ test.assertEqual(testAry[1], 2);
+ test.assertEqual(testAry[2], 3);
+ test.assertEqual(array.hasAny(testAry, [2]), true);
+ test.assertEqual(array.hasAny(testAry, [3]), true);
+ test.assertEqual(array.hasAny(testAry, [4]), false);
+ test.assertEqual(array.hasAny(testAry), false);
+ test.assertEqual(array.hasAny(testAry, '1'), false);
+};
+
+exports.testAdd = function(test) {
+ var testAry = [1];
+ test.assertEqual(array.add(testAry, 1), false);
+ test.assertEqual(testAry.length, 1);
+ test.assertEqual(testAry[0], 1);
+ test.assertEqual(array.add(testAry, 2), true);
+ test.assertEqual(testAry.length, 2);
+ test.assertEqual(testAry[0], 1);
+ test.assertEqual(testAry[1], 2);
+};
+
+exports.testRemove = function(test) {
+ var testAry = [1, 2];
+ test.assertEqual(array.remove(testAry, 3), false);
+ test.assertEqual(testAry.length, 2);
+ test.assertEqual(testAry[0], 1);
+ test.assertEqual(testAry[1], 2);
+ test.assertEqual(array.remove(testAry, 2), true);
+ test.assertEqual(testAry.length, 1);
+ test.assertEqual(testAry[0], 1);
+};
+
+exports.testFlatten = function(test) {
+ test.assertEqual(array.flatten([1, 2, 3]).length, 3);
+ test.assertEqual(array.flatten([1, [2, 3]]).length, 3);
+ test.assertEqual(array.flatten([1, [2, [3]]]).length, 3);
+ test.assertEqual(array.flatten([[1], [[2, [3]]]]).length, 3);
+};
diff --git a/tools/addon-sdk-1.12/test/test-base64.js b/tools/addon-sdk-1.12/test/test-base64.js
new file mode 100644
index 0000000..6ca7500
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-base64.js
@@ -0,0 +1,75 @@
+/* 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 base64 = require("sdk/base64");
+
+const text = "Awesome!";
+const b64text = "QXdlc29tZSE=";
+
+const utf8text = "✓ à la mode";
+const b64utf8text = "4pyTIMOgIGxhIG1vZGU=";
+
+exports["test base64.encode"] = function (assert) {
+ assert.equal(base64.encode(text), b64text, "encode correctly")
+}
+
+exports["test base64.decode"] = function (assert) {
+ assert.equal(base64.decode(b64text), text, "decode correctly")
+}
+
+exports["test base64.encode Unicode"] = function (assert) {
+
+ assert.equal(base64.encode(utf8text, "utf-8"), b64utf8text,
+ "encode correctly Unicode strings.")
+}
+
+exports["test base64.decode Unicode"] = function (assert) {
+
+ assert.equal(base64.decode(b64utf8text, "utf-8"), utf8text,
+ "decode correctly Unicode strings.")
+}
+
+exports["test base64.encode with wrong charset"] = function (assert) {
+
+ assert.throws(function() {
+ base64.encode(utf8text, "utf-16");
+ }, "The charset argument can be only 'utf-8'");
+
+ assert.throws(function() {
+ base64.encode(utf8text, "");
+ }, "The charset argument can be only 'utf-8'");
+
+ assert.throws(function() {
+ base64.encode(utf8text, 8);
+ }, "The charset argument can be only 'utf-8'");
+
+}
+
+exports["test base64.decode with wrong charset"] = function (assert) {
+
+ assert.throws(function() {
+ base64.decode(utf8text, "utf-16");
+ }, "The charset argument can be only 'utf-8'");
+
+ assert.throws(function() {
+ base64.decode(utf8text, "");
+ }, "The charset argument can be only 'utf-8'");
+
+ assert.throws(function() {
+ base64.decode(utf8text, 8);
+ }, "The charset argument can be only 'utf-8'");
+
+}
+
+exports["test encode/decode Unicode without utf-8 as charset"] = function (assert) {
+
+ assert.notEqual(base64.decode(base64.encode(utf8text)), utf8text,
+ "Unicode strings needs 'utf-8' charset"
+ );
+
+}
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-byte-streams.js b/tools/addon-sdk-1.12/test/test-byte-streams.js
new file mode 100644
index 0000000..18a105a
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-byte-streams.js
@@ -0,0 +1,169 @@
+/* -*- 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 byteStreams = require("sdk/io/byte-streams");
+const file = require("sdk/io/file");
+const { pathFor } = require("sdk/system");
+const { Loader } = require("sdk/test/loader");
+
+const STREAM_CLOSED_ERROR = "The stream is closed and cannot be used.";
+
+// This should match the constant of the same name in byte-streams.js.
+const BUFFER_BYTE_LEN = 0x8000;
+
+exports.testWriteRead = function (test) {
+ let fname = dataFileFilename();
+
+ // Write a small string less than the stream's buffer size...
+ let str = "exports.testWriteRead data!";
+ let stream = open(test, fname, true);
+ test.assert(!stream.closed, "stream.closed after open should be false");
+ stream.write(str);
+ stream.close();
+ test.assert(stream.closed, "Stream should be closed after stream.close");
+ test.assertRaises(function () stream.write("This shouldn't be written!"),
+ STREAM_CLOSED_ERROR,
+ "stream.write after close should raise error");
+
+ // ... and read it.
+ stream = open(test, fname);
+ test.assertEqual(stream.read(), str,
+ "stream.read should return string written");
+ test.assertEqual(stream.read(), "",
+ "stream.read at EOS should return empty string");
+ stream.close();
+ test.assert(stream.closed, "Stream should be closed after stream.close");
+ test.assertRaises(function () stream.read(),
+ STREAM_CLOSED_ERROR,
+ "stream.read after close should raise error");
+
+ file.remove(fname);
+};
+
+// Write a big string many times the size of the stream's buffer and read it.
+exports.testWriteReadBig = function (test) {
+ let str = "";
+ let bufLen = BUFFER_BYTE_LEN;
+ let fileSize = bufLen * 10;
+ for (let i = 0; i < fileSize; i++)
+ str += i % 10;
+ let fname = dataFileFilename();
+ let stream = open(test, fname, true);
+ stream.write(str);
+ stream.close();
+ stream = open(test, fname);
+ test.assertEqual(stream.read(), str,
+ "stream.read should return string written");
+ stream.close();
+ file.remove(fname);
+};
+
+// The same, but write and read in chunks.
+exports.testWriteReadChunks = function (test) {
+ let str = "";
+ let bufLen = BUFFER_BYTE_LEN;
+ let fileSize = bufLen * 10;
+ for (let i = 0; i < fileSize; i++)
+ str += i % 10;
+ let fname = dataFileFilename();
+ let stream = open(test, fname, true);
+ let i = 0;
+ while (i < str.length) {
+ // Use a chunk length that spans buffers.
+ let chunk = str.substr(i, bufLen + 1);
+ stream.write(chunk);
+ i += bufLen + 1;
+ }
+ stream.close();
+ stream = open(test, fname);
+ let readStr = "";
+ bufLen = BUFFER_BYTE_LEN;
+ let readLen = bufLen + 1;
+ do {
+ var frag = stream.read(readLen);
+ readStr += frag;
+ } while (frag);
+ stream.close();
+ test.assertEqual(readStr, str,
+ "stream.write and read in chunks should work as expected");
+ file.remove(fname);
+};
+
+exports.testReadLengths = function (test) {
+ let fname = dataFileFilename();
+ let str = "exports.testReadLengths data!";
+ let stream = open(test, fname, true);
+ stream.write(str);
+ stream.close();
+
+ stream = open(test, fname);
+ test.assertEqual(stream.read(str.length * 1000), str,
+ "stream.read with big byte length should return string " +
+ "written");
+ stream.close();
+
+ stream = open(test, fname);
+ test.assertEqual(stream.read(0), "",
+ "string.read with zero byte length should return empty " +
+ "string");
+ stream.close();
+
+ stream = open(test, fname);
+ test.assertEqual(stream.read(-1), "",
+ "string.read with negative byte length should return " +
+ "empty string");
+ stream.close();
+
+ file.remove(fname);
+};
+
+exports.testTruncate = function (test) {
+ let fname = dataFileFilename();
+ let str = "exports.testReadLengths data!";
+ let stream = open(test, fname, true);
+ stream.write(str);
+ stream.close();
+
+ stream = open(test, fname);
+ test.assertEqual(stream.read(), str,
+ "stream.read should return string written");
+ stream.close();
+
+ stream = open(test, fname, true);
+ stream.close();
+
+ stream = open(test, fname);
+ test.assertEqual(stream.read(), "",
+ "stream.read after truncate should be empty");
+ stream.close();
+
+ file.remove(fname);
+};
+
+exports.testUnload = function (test) {
+ let loader = Loader(module);
+ let file = loader.require("sdk/io/file");
+
+ let filename = dataFileFilename("temp-b");
+ let stream = file.open(filename, "wb");
+
+ loader.unload();
+ test.assert(stream.closed, "Stream should be closed after module unload");
+};
+
+// Returns the name of a file that should be used to test writing and reading.
+function dataFileFilename() {
+ return file.join(pathFor("ProfD"), "test-byte-streams-data");
+}
+
+// Opens and returns the given file and ensures it's of the correct class.
+function open(test, filename, forWriting) {
+ let stream = file.open(filename, forWriting ? "wb" : "b");
+ let klass = forWriting ? "ByteWriter" : "ByteReader";
+ test.assert(stream instanceof byteStreams[klass],
+ "Opened stream should be a " + klass);
+ return stream;
+}
diff --git a/tools/addon-sdk-1.12/test/test-chrome.js b/tools/addon-sdk-1.12/test/test-chrome.js
new file mode 100644
index 0000000..697d392
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-chrome.js
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+'use strict';
+
+let chrome = require('chrome');
+
+const FIXTURES_URL = module.uri.substr(0, module.uri.lastIndexOf('/') + 1) +
+ 'fixtures/chrome-worker/'
+
+exports['test addEventListener'] = function(assert, done) {
+ let uri = FIXTURES_URL + 'addEventListener.js';
+
+ let worker = new chrome.ChromeWorker(uri);
+ worker.addEventListener('message', function(event) {
+ assert.equal(event.data, 'Hello', 'message received');
+ worker.terminate();
+ done();
+ });
+};
+
+exports['test onmessage'] = function(assert, done) {
+ let uri = FIXTURES_URL + 'onmessage.js';
+
+ let worker = new chrome.ChromeWorker(uri);
+ worker.onmessage = function(event) {
+ assert.equal(event.data, 'ok', 'message received');
+ worker.terminate();
+ done();
+ };
+ worker.postMessage('ok');
+};
+
+exports['test setTimeout'] = function(assert, done) {
+ let uri = FIXTURES_URL + 'setTimeout.js';
+
+ let worker = new chrome.ChromeWorker(uri);
+ worker.onmessage = function(event) {
+ assert.equal(event.data, 'ok', 'setTimeout fired');
+ worker.terminate();
+ done();
+ };
+};
+
+exports['test jsctypes'] = function(assert, done) {
+ let uri = FIXTURES_URL + 'jsctypes.js';
+
+ let worker = new chrome.ChromeWorker(uri);
+ worker.onmessage = function(event) {
+ assert.equal(event.data, 'function', 'ctypes.open is a function');
+ worker.terminate();
+ done();
+ };
+};
+
+exports['test XMLHttpRequest'] = function(assert, done) {
+ let uri = FIXTURES_URL + 'xhr.js';
+
+ let worker = new chrome.ChromeWorker(uri);
+ worker.onmessage = function(event) {
+ assert.equal(event.data, 'ok', 'XMLHttpRequest works');
+ worker.terminate();
+ done();
+ };
+};
+
+exports['test onerror'] = function(assert, done) {
+ let uri = FIXTURES_URL + 'onerror.js';
+
+ let worker = new chrome.ChromeWorker(uri);
+ worker.onerror = function(event) {
+ assert.equal(event.filename, uri, 'event reports the correct uri');
+ assert.equal(event.lineno, 8, 'event reports the correct line number');
+ assert.equal(event.target, worker, 'event reports the correct worker');
+ assert.ok(event.message.match(/ok/),
+ 'event contains the exception message');
+ // Call preventDefault in order to avoid being displayed in JS console.
+ event.preventDefault();
+ worker.terminate();
+ done();
+ };
+};
+
+require('test').run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-clipboard.js b/tools/addon-sdk-1.12/test/test-clipboard.js
new file mode 100644
index 0000000..4749747
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-clipboard.js
@@ -0,0 +1,214 @@
+/* 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 imageTools = Cc["@mozilla.org/image/tools;1"].
+ getService(Ci.imgITools);
+
+const io = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+const base64png = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYA" +
+ "AABzenr0AAAASUlEQVRYhe3O0QkAIAwD0eyqe3Q993AQ3cBSUKpygfsNTy" +
+ "N5ugbQpK0BAADgP0BRDWXWlwEAAAAAgPsA3rzDaAAAAHgPcGrpgAnzQ2FG" +
+ "bWRR9AAAAABJRU5ErkJggg%3D%3D";
+
+const base64jpeg = "data:image/jpeg;base64,%2F9j%2F4AAQSkZJRgABAQAAAQABAAD%2F" +
+ "2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCg" +
+ "sOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD%2F2wBDAQMDAwQDBAgEBAgQCw" +
+ "kLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ" +
+ "EBAQEBAQEBD%2FwAARCAAgACADAREAAhEBAxEB%2F8QAHwAAAQUBAQEBAQ" +
+ "EAAAAAAAAAAAECAwQFBgcICQoL%2F8QAtRAAAgEDAwIEAwUFBAQAAAF9AQ" +
+ "IDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRol" +
+ "JicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eX" +
+ "qDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJ" +
+ "ytLT1NXW19jZ2uHi4%2BTl5ufo6erx8vP09fb3%2BPn6%2F8QAHwEAAwEB" +
+ "AQEBAQEBAQAAAAAAAAECAwQFBgcICQoL%2F8QAtREAAgECBAQDBAcFBAQA" +
+ "AQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNO" +
+ "El8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0" +
+ "dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6ws" +
+ "PExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3%2BPn6%2F9oADAMB" +
+ "AAIRAxEAPwD5Kr8kP9CwoA5f4m%2F8iRqX%2FbH%2FANHJXr5F%2FwAjCn" +
+ "8%2F%2FSWfnnir%2FwAkji%2F%2B4f8A6dgeD1%2BiH8bn1BX5If6FmFqW" +
+ "pXtveyQwzbUXGBtB7D2r9l4U4UyjMsoo4rFUeacua75pLaUktFJLZH5NxN" +
+ "xNmmX5pVw2Gq8sI8tlyxe8U3q03uzD8S3dxqOi3NneSeZDJs3LgDOHBHI5" +
+ "6gV%2BkcG%2BH%2FDmJzuhSq4e8XzfbqfyS%2FvH5rx1xTm2MyDEUa1W8X" +
+ "yXXLFbTi%2BkThv7B0r%2FAJ9f%2FH2%2Fxr90%2FwCIVcI%2F9An%2FAJ" +
+ "Uq%2FwDyZ%2FO%2F16v%2FADfgv8j0r%2FhZvgj%2FAKDf%2FktN%2FwDE" +
+ "V%2Fnr%2FYWYf8%2B%2Fxj%2Fmf3R%2FxFXhH%2FoL%2FwDKdX%2F5Azrv" +
+ "xLouo3D3lne%2BZDJja3luM4GDwRnqDX9LeH%2FBud4nhzD1aVC8Xz%2Fa" +
+ "h%2Fz8l%2FePx%2FinjrIMZm1WtRxF4vls%2BSa2jFdYlDUdRsp7OSKKbc" +
+ "7YwNpHce1fqfCvCub5bm9HFYqjywjzXfNF7xklopN7s%2BC4l4lyvMMrq4" +
+ "fD1bzfLZcsltJPqktkYlfsZ%2BUnBV%2FnufVnXaD%2FAMgqD%2FgX%2Fo" +
+ "Rr%2BxvCr%2FkkcJ%2F3E%2F8ATsz5%2FHfx5fL8kX6%2FQjkCgD%2F%2F" +
+ "2Q%3D%3D";
+
+const canvasHTML = "data:text/html," + encodeURIComponent(
+ "<html>\
+ <body>\
+ <canvas width='32' height='32'></canvas>\
+ </body>\
+ </html>"
+);
+
+function comparePixelImages(imageA, imageB, callback) {
+ let tabs = require("sdk/tabs");
+
+ tabs.open({
+ url: canvasHTML,
+
+ onReady: function onReady(tab) {
+ let worker = tab.attach({
+ contentScript: "new " + function() {
+ let canvas = document.querySelector("canvas");
+ let context = canvas.getContext("2d");
+
+ self.port.on("draw-image", function(imageURI) {
+ let img = new Image();
+
+ img.onload = function() {
+ context.drawImage(this, 0, 0);
+
+ let pixels = Array.join(context.getImageData(0, 0, 32, 32).data);
+ self.port.emit("image-pixels", pixels);
+ }
+
+ img.src = imageURI;
+ });
+ }
+ });
+
+ let compared = "";
+
+ worker.port.on("image-pixels", function (pixels) {
+ if (!compared) {
+ compared = pixels;
+ this.emit("draw-image", imageB);
+ } else {
+ callback(compared === pixels);
+ tab.close()
+ }
+ });
+
+ worker.port.emit("draw-image", imageA);
+ }
+ });
+}
+
+
+// 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("sdk/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("sdk/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("sdk/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("sdk/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);
+};
+
+exports.testSetImage = function(test) {
+ var clip = require("sdk/clipboard");
+ var flavor = "image";
+ var fullFlavor = "image/png";
+
+ test.assert(clip.set(base64png, flavor), "clipboard set");
+ test.assertEqual(clip.currentFlavors[0], flavor, "flavor is set");
+};
+
+exports.testGetImage = function(test) {
+ test.waitUntilDone();
+
+ var clip = require("sdk/clipboard");
+
+ clip.set(base64png, "image");
+
+ var contents = clip.get();
+
+ comparePixelImages(base64png, contents, function (areEquals) {
+ test.assert(areEquals,
+ "Image gets from clipboard equals to image sets to the clipboard");
+
+ test.done();
+ });
+}
+
+exports.testSetImageTypeNotSupported = function(test) {
+ var clip = require("sdk/clipboard");
+ var flavor = "image";
+
+ test.assertRaises(function () {
+ clip.set(base64jpeg, flavor);
+ }, "Invalid flavor for image/jpeg");
+
+};
+
+// Notice that `imageTools.decodeImageData`, used by `clipboard.set` method for
+// images, write directly to the javascript console the error in case the image
+// is corrupt, even if the error is catched.
+//
+// See: http://mxr.mozilla.org/mozilla-central/source/image/src/Decoder.cpp#136
+exports.testSetImageTypeWrongData = function(test) {
+ var clip = require("sdk/clipboard");
+ var flavor = "image";
+
+ var wrongPNG = "data:image/png" + base64jpeg.substr(15);
+
+ test.assertRaises(function () {
+ clip.set(wrongPNG, flavor);
+ }, "Unable to decode data given in a valid image.");
+};
+
+// TODO: Test error cases.
diff --git a/tools/addon-sdk-1.12/test/test-collection.js b/tools/addon-sdk-1.12/test/test-collection.js
new file mode 100644
index 0000000..cd533bf
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-collection.js
@@ -0,0 +1,127 @@
+/* -*- 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/. */
+
+const collection = require("sdk/util/collection");
+
+exports.testAddRemove = function (test) {
+ let coll = new collection.Collection();
+ compare(test, coll, []);
+ addRemove(test, coll, [], false);
+};
+
+exports.testAddRemoveBackingArray = function (test) {
+ let items = ["foo"];
+ let coll = new collection.Collection(items);
+ compare(test, coll, items);
+ addRemove(test, coll, items, true);
+
+ items = ["foo", "bar"];
+ coll = new collection.Collection(items);
+ compare(test, coll, items);
+ addRemove(test, coll, items, true);
+};
+
+exports.testProperty = function (test) {
+ let obj = makeObjWithCollProp();
+ compare(test, obj.coll, []);
+ addRemove(test, obj.coll, [], false);
+
+ // Test single-value set.
+ let items = ["foo"];
+ obj.coll = items[0];
+ compare(test, obj.coll, items);
+ addRemove(test, obj.coll, items, false);
+
+ // Test array set.
+ items = ["foo", "bar"];
+ obj.coll = items;
+ compare(test, obj.coll, items);
+ addRemove(test, obj.coll, items, false);
+};
+
+exports.testPropertyBackingArray = function (test) {
+ let items = ["foo"];
+ let obj = makeObjWithCollProp(items);
+ compare(test, obj.coll, items);
+ addRemove(test, obj.coll, items, true);
+
+ items = ["foo", "bar"];
+ obj = makeObjWithCollProp(items);
+ compare(test, obj.coll, items);
+ addRemove(test, obj.coll, items, true);
+};
+
+// Adds some values to coll and then removes them. initialItems is an array
+// containing coll's initial items. isBacking is true if initialItems is coll's
+// backing array; the point is that updates to coll should affect initialItems
+// if that's the case.
+function addRemove(test, coll, initialItems, isBacking) {
+ let items = isBacking ? initialItems : initialItems.slice(0);
+ let numInitialItems = items.length;
+
+ // Test add(val).
+ let numInsertions = 5;
+ for (let i = 0; i < numInsertions; i++) {
+ compare(test, coll, items);
+ coll.add(i);
+ if (!isBacking)
+ items.push(i);
+ }
+ compare(test, coll, items);
+
+ // Add the items we just added to make sure duplicates aren't added.
+ for (let i = 0; i < numInsertions; i++)
+ coll.add(i);
+ compare(test, coll, items);
+
+ // Test remove(val). Do a kind of shuffled remove. Remove item 1, then
+ // item 0, 3, 2, 5, 4, ...
+ for (let i = 0; i < numInsertions; i++) {
+ let val = i % 2 ? i - 1 :
+ i === numInsertions - 1 ? i : i + 1;
+ coll.remove(val);
+ if (!isBacking)
+ items.splice(items.indexOf(val), 1);
+ compare(test, coll, items);
+ }
+ test.assertEqual(coll.length, numInitialItems,
+ "All inserted items should be removed");
+
+ // Remove the items we just removed. coll should be unchanged.
+ for (let i = 0; i < numInsertions; i++)
+ coll.remove(i);
+ compare(test, coll, items);
+
+ // Test add and remove([val1, val2]).
+ let newItems = [0, 1];
+ coll.add(newItems);
+ compare(test, coll, isBacking ? items : items.concat(newItems));
+ coll.remove(newItems);
+ compare(test, coll, items);
+ test.assertEqual(coll.length, numInitialItems,
+ "All inserted items should be removed");
+}
+
+// Asserts that the items in coll are the items of array.
+function compare(test, coll, array) {
+ test.assertEqual(coll.length, array.length,
+ "Collection length should be correct");
+ let numItems = 0;
+ for (let item in coll) {
+ test.assertEqual(item, array[numItems], "Items should be equal");
+ numItems++;
+ }
+ test.assertEqual(numItems, array.length,
+ "Number of items in iteration should be correct");
+}
+
+// Returns a new object with a collection property named "coll". backingArray,
+// if defined, will create the collection with that backing array.
+function makeObjWithCollProp(backingArray) {
+ let obj = {};
+ collection.addCollectionProperty(obj, "coll", backingArray);
+ return obj;
+}
diff --git a/tools/addon-sdk-1.12/test/test-commonjs-test-adapter.js b/tools/addon-sdk-1.12/test/test-commonjs-test-adapter.js
new file mode 100644
index 0000000..936bea9
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-commonjs-test-adapter.js
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+exports["test custom `Assert`'s"] = require("./commonjs-test-adapter/asserts");
+
+// Disabling this check since it is not yet supported by jetpack.
+// if (module == require.main)
+ require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-content-loader.js b/tools/addon-sdk-1.12/test/test-content-loader.js
new file mode 100644
index 0000000..1804ac9
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-content-loader.js
@@ -0,0 +1,226 @@
+/* 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('sdk/content/loader');
+const self = require("sdk/self");
+
+exports['test:contentURL'] = function(test) {
+ let loader = Loader(),
+ value, emitted = 0, changes = 0;
+
+ test.assertRaises(
+ function() loader.contentURL = undefined,
+ 'The `contentURL` option must be a valid URL.',
+ 'Must throw an exception if `contentURL` is not URL.'
+ );
+ test.assertRaises(
+ function() loader.contentURL = null,
+ 'The `contentURL` option must be a valid URL.',
+ 'Must throw an exception if `contentURL` is not URL.'
+ );
+ test.assertRaises(
+ function() loader.contentURL = 4,
+ 'The `contentURL` option must be a valid URL.',
+ 'Must throw an exception if `contentURL` is not URL.'
+ );
+ test.assertRaises(
+ function() loader.contentURL = { toString: function() 'Oops' },
+ 'The `contentURL` option must be a valid URL.',
+ 'Must throw an exception if `contentURL` is not URL.'
+ );
+
+ function listener(e) {
+ emitted ++;
+ test.assert(
+ 'contentURL' in e,
+ 'emitted event must contain "content" property'
+ );
+ test.assert(
+ value,
+ '' + e.contentURL,
+ 'content property of an event must match value'
+ );
+ }
+ loader.on('propertyChange', listener);
+
+ test.assertEqual(
+ null,
+ loader.contentURL,
+ 'default value is `null`'
+ );
+ loader.contentURL = value = 'data:text/html,<html><body>Hi</body><html>';
+ test.assertEqual(
+ value,
+ '' + loader.contentURL,
+ 'data uri is ok'
+ );
+ test.assertEqual(
+ ++changes,
+ emitted,
+ 'had to emit `propertyChange`'
+ );
+ loader.contentURL = value;
+ test.assertEqual(
+ changes,
+ emitted,
+ 'must not emit `propertyChange` if same value is set'
+ );
+
+ loader.contentURL = value = 'http://google.com/';
+ test.assertEqual(
+ value,
+ '' + loader.contentURL,
+ 'value must be set'
+ );
+ test.assertEqual(
+ ++ changes,
+ emitted,
+ 'had to emit `propertyChange`'
+ );
+ loader.contentURL = value;
+ test.assertEqual(
+ changes,
+ emitted,
+ 'must not emit `propertyChange` if same value is set'
+ );
+
+ loader.removeListener('propertyChange', listener);
+ loader.contentURL = value = 'about:blank';
+ test.assertEqual(
+ value,
+ '' + loader.contentURL,
+ 'contentURL must be an actual value'
+ );
+ test.assertEqual(
+ changes,
+ emitted,
+ 'listener had to be romeved'
+ );
+};
+
+exports['test:contentScriptWhen'] = function(test) {
+ let loader = Loader();
+ test.assertEqual(
+ 'end',
+ loader.contentScriptWhen,
+ '`contentScriptWhen` defaults to "end"'
+ );
+ loader.contentScriptWhen = "end";
+ test.assertEqual(
+ "end",
+ loader.contentScriptWhen
+ );
+ try {
+ loader.contentScriptWhen = 'boom';
+ test.fail('must throw when wrong value is set');
+ } catch(e) {
+ test.assertEqual(
+ 'The `contentScriptWhen` option must be either "start", "ready" or "end".',
+ e.message
+ );
+ }
+ loader.contentScriptWhen = null;
+ test.assertEqual(
+ 'end',
+ loader.contentScriptWhen,
+ '`contentScriptWhen` defaults to "end"'
+ );
+ loader.contentScriptWhen = "ready";
+ test.assertEqual(
+ "ready",
+ loader.contentScriptWhen
+ );
+ loader.contentScriptWhen = "start";
+ test.assertEqual(
+ 'start',
+ loader.contentScriptWhen
+ );
+};
+
+exports['test:contentScript'] = function(test) {
+ let loader = Loader(), value;
+ test.assertEqual(
+ null,
+ loader.contentScript,
+ '`contentScript` defaults to `null`'
+ );
+ loader.contentScript = value = 'let test = {};';
+ test.assertEqual(
+ value,
+ loader.contentScript
+ );
+ try {
+ loader.contentScript = { 1: value }
+ test.fail('must throw when wrong value is set');
+ } catch(e) {
+ test.assertEqual(
+ 'The `contentScript` option must be a string or an array of strings.',
+ e.message
+ );
+ }
+ try {
+ loader.contentScript = ['oue', 2]
+ test.fail('must throw when wrong value is set');
+ } catch(e) {
+ test.assertEqual(
+ 'The `contentScript` option must be a string or an array of strings.',
+ e.message
+ );
+ }
+ loader.contentScript = undefined;
+ test.assertEqual(
+ null,
+ loader.contentScript
+ );
+ loader.contentScript = value = ["1;", "2;"];
+ test.assertEqual(
+ value,
+ loader.contentScript
+ );
+};
+
+exports['test:contentScriptFile'] = function(test) {
+ let loader = Loader(), value, uri = self.data.url("test-content-loader.js");
+ test.assertEqual(
+ null,
+ loader.contentScriptFile,
+ '`contentScriptFile` defaults to `null`'
+ );
+ loader.contentScriptFile = value = uri;
+ test.assertEqual(
+ value,
+ loader.contentScriptFile
+ );
+ try {
+ loader.contentScriptFile = { 1: uri }
+ test.fail('must throw when wrong value is set');
+ } catch(e) {
+ test.assertEqual(
+ 'The `contentScriptFile` option must be a local URL or an array of URLs.',
+ e.message
+ );
+ }
+
+ try {
+ loader.contentScriptFile = [ 'oue', uri ]
+ test.fail('must throw when wrong value is set');
+ } catch(e) {
+ test.assertEqual(
+ 'The `contentScriptFile` option must be a local URL or an array of URLs.',
+ e.message
+ );
+ }
+
+ loader.contentScriptFile = undefined;
+ test.assertEqual(
+ null,
+ loader.contentScriptFile
+ );
+ loader.contentScriptFile = value = [uri];
+ test.assertEqual(
+ value,
+ loader.contentScriptFile
+ );
+};
diff --git a/tools/addon-sdk-1.12/test/test-content-proxy.js b/tools/addon-sdk-1.12/test/test-content-proxy.js
new file mode 100644
index 0000000..b447df9
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-content-proxy.js
@@ -0,0 +1,915 @@
+/* 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 hiddenFrames = require("sdk/frame/hidden-frame");
+const xulApp = require("sdk/system/xul-app");
+
+const USE_JS_PROXIES = !xulApp.versionInRange(xulApp.platformVersion,
+ "17.0a2", "*");
+
+const { Loader } = require('sdk/test/loader');
+
+/*
+ * Utility function that allow to easily run a proxy test with a clean
+ * new HTML document. See first unit test for usage.
+ */
+function createProxyTest(html, callback) {
+ return function (test) {
+ test.waitUntilDone();
+
+ let url = 'data:text/html;charset=utf-8,' + encodeURI(html);
+
+ let hiddenFrame = hiddenFrames.add(hiddenFrames.HiddenFrame({
+ onReady: function () {
+
+ function onDOMReady() {
+ hiddenFrame.element.removeEventListener("DOMContentLoaded", onDOMReady,
+ false);
+
+ let xrayWindow = hiddenFrame.element.contentWindow;
+ let rawWindow = xrayWindow.wrappedJSObject;
+
+ let done = false;
+ let helper = {
+ xrayWindow: xrayWindow,
+ rawWindow: rawWindow,
+ createWorker: function (contentScript) {
+ return createWorker(test, xrayWindow, contentScript, helper.done);
+ },
+ done: function () {
+ if (done)
+ return;
+ done = true;
+ hiddenFrames.remove(hiddenFrame);
+ test.done();
+ }
+ }
+ callback(helper, test);
+ }
+
+ hiddenFrame.element.addEventListener("DOMContentLoaded", onDOMReady, false);
+ hiddenFrame.element.setAttribute("src", url);
+
+ }
+ }));
+ };
+}
+
+function createWorker(test, xrayWindow, contentScript, done) {
+ // We have to use Sandboxed loader in order to get access to the private
+ // unlock key `PRIVATE_KEY`. This key should not be used anywhere else.
+ // See `PRIVATE_KEY` definition in worker.js
+ let loader = Loader(module);
+ let Worker = loader.require("sdk/content/worker").Worker;
+ let key = loader.sandbox("sdk/content/worker").PRIVATE_KEY;
+ let worker = Worker({
+ exposeUnlockKey : USE_JS_PROXIES ? key : null,
+ window: xrayWindow,
+ contentScript: [
+ 'new ' + function () {
+ assert = function assert(v, msg) {
+ self.port.emit("assert", {assertion:v, msg:msg});
+ }
+ done = function done() {
+ self.port.emit("done");
+ }
+ },
+ contentScript
+ ]
+ });
+
+ worker.port.on("done", done);
+ worker.port.on("assert", function (data) {
+ test.assert(data.assertion, data.msg);
+ });
+
+ return worker;
+}
+
+/* Examples for the `createProxyTest` uses */
+
+let html = "<script>var documentGlobal = true</script>";
+exports.testCreateProxyTest = createProxyTest(html, function (helper, test) {
+ // You can get access to regular `test` object in second argument of
+ // `createProxyTest` method:
+ test.assert(helper.rawWindow.documentGlobal,
+ "You have access to a raw window reference via `helper.rawWindow`");
+ test.assert(!("documentGlobal" in helper.xrayWindow),
+ "You have access to an XrayWrapper reference via `helper.xrayWindow`");
+
+ // If you do not create a Worker, you have to call helper.done(),
+ // in order to say when your test is finished
+ helper.done();
+});
+
+exports.testCreateProxyTestWithWorker = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ "new " + function WorkerScope() {
+ assert(true, "You can do assertions in your content script");
+ // And if you create a worker, you either have to call `done`
+ // from content script or helper.done()
+ done();
+ }
+ );
+
+});
+
+exports.testCreateProxyTestWithEvents = createProxyTest("", function (helper, test) {
+
+ let worker = helper.createWorker(
+ "new " + function WorkerScope() {
+ self.port.emit("foo");
+ }
+ );
+
+ worker.port.on("foo", function () {
+ test.pass("You can use events");
+ // And terminate your test with helper.done:
+ helper.done();
+ });
+
+});
+
+if (USE_JS_PROXIES) {
+ // Verify that the attribute `exposeUnlockKey`, that allow this test
+ // to identify proxies, works correctly.
+ // See `PRIVATE_KEY` definition in worker.js
+ exports.testKeyAccess = createProxyTest("", function(helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ assert("UNWRAP_ACCESS_KEY" in window, "have access to `UNWRAP_ACCESS_KEY`");
+ done();
+ }
+ );
+
+ });
+}
+
+// Bug 714778: There was some issue around `toString` functions
+// that ended up being shared between content scripts
+exports.testSharedToStringProxies = createProxyTest("", function(helper) {
+
+ let worker = helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // We ensure that `toString` can't be modified so that nothing could
+ // leak to/from the document and between content scripts
+ // It only applies to JS proxies, there isn't any such issue with xrays.
+ //document.location.toString = function foo() {};
+ document.location.toString.foo = "bar";
+ if ('UNWRAP_ACCESS_KEY' in window)
+ assert(!("foo" in document.location.toString), "document.location.toString can't be modified");
+ else
+ assert("foo" in document.location.toString, "document.location.toString can be modified");
+ assert(document.location.toString() == "data:text/html;charset=utf-8,",
+ "First document.location.toString()");
+ self.postMessage("next");
+ }
+ );
+ worker.on("message", function () {
+ helper.createWorker(
+ 'new ' + function ContentScriptScope2() {
+ assert(!("foo" in document.location.toString),
+ "document.location.toString is different for each content script");
+ assert(document.location.toString() == "data:text/html;charset=utf-8,",
+ "Second document.location.toString()");
+ done();
+ }
+ );
+ });
+});
+
+
+// Ensure that postMessage is working correctly across documents with an iframe
+let html = '<iframe id="iframe" name="test" src="data:text/html;charset=utf-8," />';
+exports.testPostMessage = createProxyTest(html, function (helper, test) {
+ let ifWindow = helper.xrayWindow.document.getElementById("iframe").contentWindow;
+ // Listen without proxies, to check that it will work in regular case
+ // simulate listening from a web document.
+ ifWindow.addEventListener("message", function listener(event) {
+ ifWindow.removeEventListener("message", listener, false);
+ // As we are in system principal, event is an XrayWrapper
+ if (USE_JS_PROXIES) {
+ test.assertEqual(event.source, ifWindow,
+ "event.source is the iframe window");
+ }
+ else {
+ // JS proxies had different behavior than xrays, xrays use current
+ // compartments when calling postMessage method. Whereas js proxies
+ // was using postMessage method compartment, not the caller one.
+ test.assertEqual(event.source, helper.xrayWindow,
+ "event.source is the top window");
+ }
+ test.assertEqual(event.origin, "null", "origin is null");
+
+ test.assertEqual(event.data, "{\"foo\":\"bar\\n \\\"escaped\\\".\"}",
+ "message data is correct");
+
+ helper.done();
+ }, false);
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ assert(postMessage === postMessage,
+ "verify that we doesn't generate multiple functions for the same method");
+
+ var json = JSON.stringify({foo : "bar\n \"escaped\"."});
+
+ document.getElementById("iframe").contentWindow.postMessage(json, "*");
+ }
+ );
+});
+
+let html = '<input id="input2" type="checkbox" />';
+exports.testObjectListener = createProxyTest(html, function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Test objects being given as event listener
+ let input = document.getElementById("input2");
+ let myClickListener = {
+ called: false,
+ handleEvent: function(event) {
+ assert(this === myClickListener, "`this` is the original object");
+ assert(!this.called, "called only once");
+ this.called = true;
+ if ('UNWRAP_ACCESS_KEY' in window)
+ assert(event.valueOf() !== event.valueOf(UNWRAP_ACCESS_KEY), "event is wrapped");
+ assert(event.target, input, "event.target is the wrapped window");
+ done();
+ }
+ };
+
+ window.addEventListener("click", myClickListener, true);
+ input.click();
+ window.removeEventListener("click", myClickListener, true);
+ }
+ );
+
+});
+
+exports.testObjectListener2 = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Verify object as DOM event listener
+ let myMessageListener = {
+ called: false,
+ handleEvent: function(event) {
+ window.removeEventListener("message", myMessageListener, true);
+
+ assert(this == myMessageListener, "`this` is the original object");
+ assert(!this.called, "called only once");
+ this.called = true;
+ if ('UNWRAP_ACCESS_KEY' in window)
+ assert(event.valueOf() !== event.valueOf(UNWRAP_ACCESS_KEY), "event is wrapped");
+ assert(event.target == document.defaultView, "event.target is the wrapped window");
+ assert(event.source == document.defaultView, "event.source is the wrapped window");
+ assert(event.origin == "null", "origin is null");
+ assert(event.data == "ok", "message data is correct");
+ done();
+ }
+ };
+
+ window.addEventListener("message", myMessageListener, true);
+ document.defaultView.postMessage("ok", '*');
+ }
+ );
+
+});
+
+let html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' +
+ '<input id="input2" type="checkbox" />';
+
+/* Disable test to keep tree green until Bug 756214 is fixed.
+exports.testStringOverload = createProxyTest(html, function (helper, test) {
+ // Proxy - toString error
+ let originalString = "string";
+ let p = Proxy.create({
+ get: function(receiver, name) {
+ if (name == "binded")
+ return originalString.toString.bind(originalString);
+ return originalString[name];
+ }
+ });
+ test.assertRaises(function () {
+ p.toString();
+ },
+ /String.prototype.toString called on incompatible Proxy/,
+ "toString can't be called with this being the proxy");
+ test.assertEqual(p.binded(), "string", "but it works if we bind this to the original string");
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // RightJS is hacking around String.prototype, and do similar thing:
+ // Pass `this` from a String prototype method.
+ // It is funny because typeof this == "object"!
+ // So that when we pass `this` to a native method,
+ // our proxy code can fail on another even more crazy thing.
+ // See following test to see what fails around proxies.
+ String.prototype.update = function () {
+ assert(typeof this == "object", "in update, `this` is an object");
+ assert(this.toString() == "input", "in update, `this.toString works");
+ return document.querySelectorAll(this);
+ };
+ assert("input".update().length == 3, "String.prototype overload works");
+ done();
+ }
+ );
+});
+*/
+
+exports.testMozMatchedSelector = createProxyTest("", function (helper) {
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Check mozMatchesSelector XrayWrappers bug:
+ // mozMatchesSelector returns bad results when we are not calling it from the node itself
+ // SEE BUG 658909: mozMatchesSelector returns incorrect results with XrayWrappers
+ assert(document.createElement( "div" ).mozMatchesSelector("div"),
+ "mozMatchesSelector works while being called from the node");
+ assert(document.documentElement.mozMatchesSelector.call(
+ document.createElement( "div" ),
+ "div"
+ ),
+ "mozMatchesSelector works while being called from a " +
+ "function reference to " +
+ "document.documentElement.mozMatchesSelector.call");
+ done();
+ }
+ );
+});
+
+exports.testEventsOverload = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // If we add a "____proxy" attribute on XrayWrappers in order to store
+ // the related proxy to create an unique proxy for each wrapper;
+ // we end up setting this attribute to prototype objects :x
+ // And so, instances created with such prototype will be considered
+ // as equal to the prototype ...
+ // // Internal method that return the proxy for a given XrayWrapper
+ // function proxify(obj) {
+ // if (obj._proxy) return obj._proxy;
+ // return obj._proxy = Proxy.create(...);
+ // }
+ //
+ // // Get a proxy of an XrayWrapper prototype object
+ // let proto = proxify(xpcProto);
+ //
+ // // Use this proxy as a prototype
+ // function Constr() {}
+ // Constr.proto = proto;
+ //
+ // // Try to create an instance using this prototype
+ // let xpcInstance = new Constr();
+ // let wrapper = proxify(xpcInstance)
+ //
+ // xpcProto._proxy = proto and as xpcInstance.__proto__ = xpcProto,
+ // xpcInstance._proxy = proto ... and profixy(xpcInstance) = proto :(
+ //
+ let proto = window.document.createEvent('HTMLEvents').__proto__;
+ window.Event.prototype = proto;
+ let event = document.createEvent('HTMLEvents');
+ assert(event !== proto, "Event should not be equal to its prototype");
+ event.initEvent('dataavailable', true, true);
+ assert(event.type === 'dataavailable', "Events are working fine");
+ done();
+ }
+ );
+
+});
+
+exports.testNestedAttributes = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // XrayWrappers has a bug when you set an attribute on it,
+ // in some cases, it creates an unnecessary wrapper that introduces
+ // a different object that refers to the same original object
+ // Check that our wrappers don't reproduce this bug
+ // SEE BUG 658560: Fix identity problem with CrossOriginWrappers
+ let o = {sandboxObject:true};
+ window.nested = o;
+ o.foo = true;
+ assert(o === window.nested, "Nested attribute to sandbox object should not be proxified");
+ window.nested = document;
+ assert(window.nested === document, "Nested attribute to proxy should not be double proxified");
+ done();
+ }
+ );
+
+});
+
+exports.testFormNodeName = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ let body = document.body;
+ // Check form[nodeName]
+ let form = document.createElement("form");
+ let input = document.createElement("input");
+ input.setAttribute("name", "test");
+ form.appendChild(input);
+ body.appendChild(form);
+ assert(form.test == input, "form[nodeName] is valid");
+ body.removeChild(form);
+ done();
+ }
+ );
+
+});
+
+exports.testLocalStorage = createProxyTest("", function (helper, test) {
+
+ let worker = helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Check localStorage:
+ assert(window.localStorage, "has access to localStorage");
+ window.localStorage.name = 1;
+ assert(window.localStorage.name == 1, "localStorage appears to work");
+
+ self.port.on("step2", function () {
+ window.localStorage.clear();
+ assert(window.localStorage.name == undefined, "localStorage really, really works");
+ done();
+ });
+ self.port.emit("step1");
+ }
+ );
+
+ worker.port.on("step1", function () {
+ test.assertEqual(helper.rawWindow.localStorage.name, 1, "localStorage really works");
+ worker.port.emit("step2");
+ });
+
+});
+
+exports.testAutoUnwrapCustomAttributes = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ let body = document.body;
+ // Setting a custom object to a proxy attribute is not wrapped when we get it afterward
+ let object = {custom: true, enumerable: false};
+ body.customAttribute = object;
+ if ('UNWRAP_ACCESS_KEY' in window)
+ assert(body.customAttribute.valueOf() === body.customAttribute.valueOf(UNWRAP_ACCESS_KEY), "custom JS attributes are not wrapped");
+ assert(object === body.customAttribute, "custom JS attributes are not wrapped");
+ done();
+ }
+ );
+
+});
+
+exports.testObjectTag = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // <object>, <embed> and other tags return typeof 'function'
+ let flash = document.createElement("object");
+ assert(typeof flash == "function", "<object> is typeof 'function'");
+ assert(flash.toString().match(/\[object HTMLObjectElement.*\]/), "<object> is HTMLObjectElement");
+ assert("setAttribute" in flash, "<object> has a setAttribute method");
+ done();
+ }
+ );
+
+});
+
+exports.testHighlightToStringBehavior = createProxyTest("", function (helper, test) {
+ // We do not have any workaround this particular use of toString
+ // applied on <object> elements. So disable this test until we found one!
+ //test.assertEqual(helper.rawWindow.Object.prototype.toString.call(flash), "[object HTMLObjectElement]", "<object> is HTMLObjectElement");
+ function f() {};
+ test.assertMatches(Object.prototype.toString.call(f), /\[object Function.*\]/, "functions are functions 1");
+ // This is how jquery call toString:
+ test.assertMatches(helper.rawWindow.Object.prototype.toString.call(""), /\[object String.*\]/, "strings are strings");
+ let o = {__exposedProps__:{}};
+ test.assertMatches(helper.rawWindow.Object.prototype.toString.call(o), /\[object Object.*\]/, "objects are objects");
+
+ // Make sure to pass a function from the same compartments
+ // or toString will return [object Object] on FF8+
+ let f2 = helper.rawWindow.eval("(function () {})");
+ test.assertMatches(helper.rawWindow.Object.prototype.toString.call(f2), /\[object Function.*\]/, "functions are functions 2");
+
+ helper.done();
+});
+
+exports.testDocumentTagName = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ let body = document.body;
+ // Check document[tagName]
+ let div = document.createElement("div");
+ div.setAttribute("name", "test");
+ body.appendChild(div);
+ assert(!document.test, "document[divName] is undefined");
+ body.removeChild(div);
+
+ let form = document.createElement("form");
+ form.setAttribute("name", "test");
+ body.appendChild(form);
+ assert(document.test == form, "document[formName] is valid");
+ body.removeChild(form);
+
+ let img = document.createElement("img");
+ img.setAttribute("name", "test");
+ body.appendChild(img);
+ assert(document.test == img, "document[imgName] is valid");
+ body.removeChild(img);
+ done();
+ }
+ );
+
+});
+
+let html = '<iframe id="iframe" name="test" src="data:text/html;charset=utf-8," />';
+exports.testWindowFrames = createProxyTest(html, function (helper) {
+
+ helper.createWorker(
+ 'let glob = this; new ' + function ContentScriptScope() {
+ // Check window[frameName] and window.frames[i]
+ let iframe = document.getElementById("iframe");
+ //assert(window.frames.length == 1, "The iframe is reported in window.frames check1");
+ //assert(window.frames[0] == iframe.contentWindow, "The iframe is reported in window.frames check2");
+ //console.log(window.test+ "-"+iframe.contentWindow);
+ //console.log(window);
+ assert(window.test == iframe.contentWindow, "window[frameName] is valid");
+ done();
+ }
+ );
+
+});
+
+exports.testCollections = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Highlight XPCNativeWrapper bug with HTMLCollection
+ // tds[0] is only defined on first access :o
+ let body = document.body;
+ let div = document.createElement("div");
+ body.appendChild(div);
+ div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
+ let tds = div.getElementsByTagName("td");
+ assert(tds[0] == tds[0], "We can get array element multiple times");
+ body.removeChild(div);
+ done();
+ }
+ );
+
+});
+
+let html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' +
+ '<input id="input2" type="checkbox" />';
+exports.testCollections2 = createProxyTest(html, function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Verify that NodeList/HTMLCollection are working fine
+ let body = document.body;
+ let inputs = body.getElementsByTagName("input");
+ assert(body.childNodes.length == 3, "body.childNodes length is correct");
+ assert(inputs.length == 3, "inputs.length is correct");
+ assert(body.childNodes[0] == inputs[0], "body.childNodes[0] is correct");
+ assert(body.childNodes[1] == inputs[1], "body.childNodes[1] is correct");
+ assert(body.childNodes[2] == inputs[2], "body.childNodes[2] is correct");
+ let count = 0;
+ for(let i in body.childNodes) {
+ count++;
+ }
+ // JS proxies were broken, we can iterate over some other items:
+ // length, item and iterator
+ let expectedCount;
+ if ('UNWRAP_ACCESS_KEY' in window)
+ expectedCount = 3;
+ else
+ expectedCount = 6;
+ assert(count == expectedCount, "body.childNodes is iterable");
+ done();
+ }
+ );
+
+});
+
+exports.testValueOf = createProxyTest("", function (helper) {
+
+ if (USE_JS_PROXIES) {
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Check internal use of valueOf() for JS proxies API
+ assert(window.valueOf().toString().match(/\[object Window.*\]/),
+ "proxy.valueOf() returns the wrapped version");
+ assert(window.valueOf({}).toString().match(/\[object Window.*\]/),
+ "proxy.valueOf({}) returns the wrapped version");
+ done();
+ }
+ );
+ }
+ else {
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Bug 787013: Until this bug is fixed, we are missing some methods
+ // on JS objects that comes from global `Object` object
+ assert(!('valueOf' in window), "valueOf is missing");
+ assert(!('toLocateString' in window), "toLocaleString is missing");
+ done();
+ }
+ );
+ }
+
+});
+
+exports.testXMLHttpRequest = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // XMLHttpRequest doesn't support XMLHttpRequest.apply,
+ // that may break our proxy code
+ assert(window.XMLHttpRequest(), "we are able to instantiate XMLHttpRequest object");
+ done();
+ }
+ );
+
+});
+
+exports.testXPathResult = createProxyTest("", function (helper, test) {
+ const XPathResultTypes = ["ANY_TYPE",
+ "NUMBER_TYPE", "STRING_TYPE", "BOOLEAN_TYPE",
+ "UNORDERED_NODE_ITERATOR_TYPE",
+ "ORDERED_NODE_ITERATOR_TYPE",
+ "UNORDERED_NODE_SNAPSHOT_TYPE",
+ "ORDERED_NODE_SNAPSHOT_TYPE",
+ "ANY_UNORDERED_NODE_TYPE",
+ "FIRST_ORDERED_NODE_TYPE"];
+
+ // Check XPathResult bug with constants being undefined on XPCNativeWrapper
+ let xpcXPathResult = helper.xrayWindow.XPathResult;
+
+ XPathResultTypes.forEach(function(type, i) {
+ test.assertEqual(xpcXPathResult.wrappedJSObject[type],
+ helper.rawWindow.XPathResult[type],
+ "XPathResult's constants are valid on unwrapped node");
+
+ test.assertEqual(xpcXPathResult[type], i,
+ "XPathResult's constants are defined on " +
+ "XPCNativeWrapper (platform bug #)");
+ });
+
+ let value = helper.rawWindow.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE;
+ let worker = helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ self.port.on("value", function (value) {
+ // Check that our work around is working:
+ assert(window.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE === value,
+ "XPathResult works correctly on Proxies");
+ done();
+ });
+ }
+ );
+ worker.port.emit("value", value);
+});
+
+exports.testPrototypeInheritance = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Verify that inherited prototype function like initEvent
+ // are handled correctly. (e2.type will return an error if it's not the case)
+ let event1 = document.createEvent( 'MouseEvents' );
+ event1.initEvent( "click", true, true );
+ let event2 = document.createEvent( 'MouseEvents' );
+ event2.initEvent( "click", true, true );
+ assert(event2.type == "click", "We are able to create an event");
+ done();
+ }
+ );
+
+});
+
+exports.testFunctions = createProxyTest("", function (helper) {
+
+ helper.rawWindow.callFunction = function callFunction(f) f();
+ helper.rawWindow.isEqual = function isEqual(a, b) a == b;
+ // bug 784116: workaround in order to allow proxy code to cache proxies on
+ // these functions:
+ helper.rawWindow.callFunction.__exposedProps__ = {__proxy: 'rw'};
+ helper.rawWindow.isEqual.__exposedProps__ = {__proxy: 'rw'};
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Check basic usage of functions
+ let closure2 = function () {return "ok";};
+ assert(window.wrappedJSObject.callFunction(closure2) == "ok", "Function references work");
+
+ // Ensure that functions are cached when being wrapped to native code
+ let closure = function () {};
+ assert(window.wrappedJSObject.isEqual(closure, closure), "Function references are cached before being wrapped to native");
+ done();
+ }
+ );
+
+});
+
+let html = '<input id="input2" type="checkbox" />';
+exports.testListeners = createProxyTest(html, function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Verify listeners:
+ let input = document.getElementById("input2");
+ assert(input, "proxy.getElementById works");
+
+ function onclick() {};
+ input.onclick = onclick;
+ assert(input.onclick === onclick, "on* attributes are equal to original function set");
+
+ let addEventListenerCalled = false;
+ let expandoCalled = false;
+ input.addEventListener("click", function onclick(event) {
+ input.removeEventListener("click", onclick, true);
+
+ assert(!addEventListenerCalled, "closure given to addEventListener is called once");
+ if (addEventListenerCalled)
+ return;
+ addEventListenerCalled = true;
+
+ assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals");
+ if ('UNWRAP_ACCESS_KEY' in window)
+ assert("__isWrappedProxy" in event.target, "event object is a proxy");
+
+ let input2 = document.getElementById("input2");
+
+ input.onclick = function (event) {
+ input.onclick = null;
+ assert(!expandoCalled, "closure set to expando is called once");
+ if (expandoCalled) return;
+ expandoCalled = true;
+
+ assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals");
+ if ('UNWRAP_ACCESS_KEY' in window)
+ assert("__isWrappedProxy" in event.target, "event object is a proxy");
+
+ setTimeout(function () {
+ input.click();
+ done();
+ }, 0);
+
+ }
+
+ setTimeout(function () {
+ input.click();
+ }, 0);
+
+ }, true);
+
+ input.click();
+ }
+ );
+
+});
+
+exports.testMozRequestAnimationFrame = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ window.mozRequestAnimationFrame(function callback() {
+ assert(callback == this, "callback is equal to `this`");
+ done();
+ });
+ }
+ );
+
+});
+
+exports.testGlobalScope = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'let toplevelScope = true;' +
+ 'assert(window.toplevelScope, "variables in toplevel scope are set to `window` object");' +
+ 'assert(this.toplevelScope, "variables in toplevel scope are set to `this` object");' +
+ 'done();'
+ );
+
+});
+
+if (USE_JS_PROXIES) {
+ // Bug 671016: Typed arrays should not be proxified
+ exports.testTypedArraysX = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ let canvas = document.createElement("canvas");
+ let context = canvas.getContext("2d");
+ let imageData = context.getImageData(0,0, 1, 1);
+ let unwrappedData;
+ if ('UNWRAP_ACCESS_KEY' in window)
+ unwrappedData = imageData.valueOf(UNWRAP_ACCESS_KEY).data
+ else
+ unwrappedData = imageData.wrappedJSObject.data;
+ let data = imageData.data;
+ dump(unwrappedData+" === "+data+"\n");
+ assert(unwrappedData === data, "Typed array isn't proxified")
+ done();
+ }
+ );
+
+ });
+}
+
+// Bug 715755: proxy code throw an exception on COW
+// Create an http server in order to simulate real cross domain documents
+exports.testCrossDomainIframe = createProxyTest("", function (helper) {
+ let serverPort = 8099;
+ let server = require("sdk/test/httpd").startServerAsync(serverPort);
+ server.registerPathHandler("/", function handle(request, response) {
+ // Returns the webpage that receive a message and forward it back to its
+ // parent document by appending ' world'.
+ let content = "<html><head><meta charset='utf-8'></head>\n";
+ content += "<script>\n";
+ content += " window.addEventListener('message', function (event) {\n";
+ content += " parent.postMessage(event.data + ' world', '*');\n";
+ content += " }, true);\n";
+ content += "</script>\n";
+ content += "<body></body>\n";
+ content += "</html>\n";
+ response.write(content);
+ });
+
+ let worker = helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Waits for the server page url
+ self.on("message", function (url) {
+ // Creates an iframe with this page
+ let iframe = document.createElement("iframe");
+ iframe.addEventListener("load", function onload() {
+ iframe.removeEventListener("load", onload, true);
+ try {
+ // Try to communicate with iframe's content
+ window.addEventListener("message", function onmessage(event) {
+ window.removeEventListener("message", onmessage, true);
+
+ assert(event.data == "hello world", "COW works properly");
+ self.port.emit("end");
+ }, true);
+ iframe.contentWindow.postMessage("hello", "*");
+ } catch(e) {
+ assert(false, "COW fails : "+e.message);
+ }
+ }, true);
+ iframe.setAttribute("src", url);
+ document.body.appendChild(iframe);
+ });
+ }
+ );
+
+ worker.port.on("end", function () {
+ server.stop(helper.done);
+ });
+
+ worker.postMessage("http://localhost:" + serverPort + "/");
+
+});
+
+// Bug 769006: Ensure that MutationObserver works fine with proxies
+let html = '<a href="foo">link</a>';
+exports.testMutationObvserver = createProxyTest(html, function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ if (typeof MutationObserver == "undefined") {
+ assert(true, "No MutationObserver for this FF version");
+ done();
+ return;
+ }
+ let link = document.getElementsByTagName("a")[0];
+
+ // Register a Mutation observer
+ let obs = new MutationObserver(function(mutations){
+ // Ensure that mutation data are valid
+ assert(mutations.length == 1, "only one attribute mutation");
+ let mutation = mutations[0];
+ assert(mutation.type == "attributes", "check `type`");
+ assert(mutation.target == link, "check `target`");
+ assert(mutation.attributeName == "href", "check `attributeName`");
+ assert(mutation.oldValue == "foo", "check `oldValue`");
+ obs.disconnect();
+ done();
+ });
+ obs.observe(document, {
+ subtree: true,
+ attributes: true,
+ attributeOldValue: true,
+ attributeFilter: ["href"]
+ });
+
+ // Modify the DOM
+ link.setAttribute("href", "bar");
+ }
+ );
+
+});
diff --git a/tools/addon-sdk-1.12/test/test-content-symbiont.js b/tools/addon-sdk-1.12/test/test-content-symbiont.js
new file mode 100644
index 0000000..470cf7e
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-content-symbiont.js
@@ -0,0 +1,184 @@
+/* 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 { Symbiont } = require('sdk/content/symbiont');
+const self = require("sdk/self");
+
+function makeWindow() {
+ let content =
+ '<?xml version="1.0"?>' +
+ '<window ' +
+ 'xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">' +
+ '<iframe id="content" type="content"/>' +
+ '</window>';
+ var url = "data:application/vnd.mozilla.xul+xml;charset=utf-8," +
+ encodeURIComponent(content);
+ var features = ["chrome", "width=10", "height=10"];
+
+ return Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher).
+ openWindow(null, url, null, features.join(","), null);
+}
+
+exports['test:constructing symbiont && validating API'] = function(test) {
+ let contentScript = ["1;", "2;"];
+ let contentScriptFile = self.data.url("test-content-symbiont.js");
+
+ // We can avoid passing a `frame` argument. Symbiont will create one
+ // by using HiddenFrame module
+ let contentSymbiont = Symbiont({
+ contentScriptFile: contentScriptFile,
+ contentScript: contentScript,
+ contentScriptWhen: "start"
+ });
+
+ test.assertEqual(
+ contentScriptFile,
+ contentSymbiont.contentScriptFile,
+ "There is one contentScriptFile, as specified in options."
+ );
+ test.assertEqual(
+ contentScript.length,
+ contentSymbiont.contentScript.length,
+ "There are two contentScripts, as specified in options."
+ );
+ test.assertEqual(
+ contentScript[0],
+ contentSymbiont.contentScript[0],
+ "There are two contentScripts, as specified in options."
+ );
+ test.assertEqual(
+ contentScript[1],
+ contentSymbiont.contentScript[1],
+ "There are two contentScripts, as specified in options."
+ )
+ test.assertEqual(
+ contentSymbiont.contentScriptWhen,
+ "start",
+ "contentScriptWhen is as specified in options."
+ );
+
+ contentSymbiont.destroy();
+};
+
+exports["test:communication with worker global scope"] = function(test) {
+ let window = makeWindow();
+ let contentSymbiont;
+
+ function onMessage1(message) {
+ test.assertEqual(message, 1, "Program gets message via onMessage.");
+ contentSymbiont.removeListener('message', onMessage1);
+ contentSymbiont.on('message', onMessage2);
+ contentSymbiont.postMessage(2);
+ };
+
+ function onMessage2(message) {
+ if (5 == message) {
+ test.done();
+ } else {
+ test.assertEqual(message, 3, "Program gets message via onMessage2.");
+ contentSymbiont.postMessage(4)
+ }
+ }
+
+ window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad, false);
+ let frame = window.document.getElementById("content");
+ contentSymbiont = Symbiont({
+ frame: frame,
+ contentScript: 'new ' + function() {
+ self.postMessage(1);
+ self.on("message", function onMessage(message) {
+ if (message === 2)
+ self.postMessage(3);
+ if (message === 4)
+ self.postMessage(5);
+ });
+ } + '()',
+ contentScriptWhen: 'ready',
+ onMessage: onMessage1
+ });
+
+ frame.setAttribute("src", "data:text/html;charset=utf-8,<html><body></body></html>");
+ }, false);
+ test.waitUntilDone();
+};
+
+exports['test:pageWorker'] = function(test) {
+ test.waitUntilDone();
+ let worker = Symbiont({
+ contentURL: 'about:buildconfig',
+ contentScript: 'new ' + function WorkerScope() {
+ self.on('message', function(data) {
+ if (data.valid)
+ self.postMessage('bye!');
+ })
+ self.postMessage(window.location.toString());
+ },
+ onMessage: function(msg) {
+ if (msg == 'bye!') {
+ test.done()
+ } else {
+ test.assertEqual(
+ worker.contentURL + '',
+ msg
+ );
+ worker.postMessage({ valid: true });
+ }
+ }
+ });
+};
+
+exports["test:document element present on 'start'"] = function(test) {
+ test.waitUntilDone();
+ let xulApp = require("sdk/system/xul-app");
+ let worker = Symbiont({
+ contentURL: "about:buildconfig",
+ contentScript: "self.postMessage(!!document.documentElement)",
+ contentScriptWhen: "start",
+ onMessage: function(message) {
+ if (xulApp.versionInRange(xulApp.platformVersion, "2.0b6", "*"))
+ test.assert(message, "document element present on 'start'");
+ else
+ test.pass("document element not necessarily present on 'start'");
+ test.done();
+ }
+ });
+};
+
+exports["test:direct communication with trusted document"] = function(test) {
+ test.waitUntilDone();
+
+ let worker = Symbiont({
+ contentURL: require("sdk/self").data.url("test-trusted-document.html")
+ });
+
+ worker.port.on('document-to-addon', function (arg) {
+ test.assertEqual(arg, "ok", "Received an event from the document");
+ worker.destroy();
+ test.done();
+ });
+ worker.port.emit('addon-to-document', 'ok');
+};
+
+exports["test:`addon` is not available when a content script is set"] = function(test) {
+ test.waitUntilDone();
+
+ let worker = Symbiont({
+ contentURL: require("sdk/self").data.url("test-trusted-document.html"),
+ contentScript: "new " + function ContentScriptScope() {
+ self.port.emit("cs-to-addon", "addon" in unsafeWindow);
+ }
+ });
+
+ worker.port.on('cs-to-addon', function (hasAddon) {
+ test.assertEqual(hasAddon, false,
+ "`addon` is not available");
+ worker.destroy();
+ test.done();
+ });
+};
diff --git a/tools/addon-sdk-1.12/test/test-content-worker.js b/tools/addon-sdk-1.12/test/test-content-worker.js
new file mode 100644
index 0000000..f1c8983
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-content-worker.js
@@ -0,0 +1,657 @@
+/* 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 stirct";
+
+const { Cc, Ci } = require("chrome");
+const { setTimeout } = require("sdk/timers");
+const { Loader, Require, override } = require("sdk/test/loader");
+const { Worker } = require("sdk/content/worker");
+
+const DEFAULT_CONTENT_URL = "data:text/html;charset=utf-8,foo";
+
+function makeWindow(contentURL) {
+ let content =
+ "<?xml version=\"1.0\"?>" +
+ "<window " +
+ "xmlns=\"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul\">" +
+ "<script>var documentValue=true;</script>" +
+ "</window>";
+ var url = "data:application/vnd.mozilla.xul+xml;charset=utf-8," +
+ encodeURIComponent(content);
+ var features = ["chrome", "width=10", "height=10"];
+
+ return Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher).
+ openWindow(null, url, null, features.join(","), null);
+}
+
+// Listen for only first one occurence of DOM event
+function listenOnce(node, eventName, callback) {
+ node.addEventListener(eventName, function onevent(event) {
+ node.removeEventListener(eventName, onevent, true);
+ callback(node);
+ }, true);
+}
+
+// Load a given url in a given browser and fires the callback when it is loaded
+function loadAndWait(browser, url, callback) {
+ listenOnce(browser, "load", callback);
+ // We have to wait before calling `loadURI` otherwise, if we call
+ // `loadAndWait` during browser load event, the history will be broken
+ setTimeout(function () {
+ browser.loadURI(url);
+ }, 0);
+}
+
+// Returns a test function that will automatically open a new chrome window
+// with a <browser> element loaded on a given content URL
+// The callback receive 3 arguments:
+// - test: reference to the jetpack test object
+// - browser: a reference to the <browser> xul node
+// - done: a callback to call when test is over
+function WorkerTest(url, callback) {
+ return function testFunction(test) {
+ test.waitUntilDone();
+ let chromeWindow = makeWindow();
+ chromeWindow.addEventListener("load", function onload() {
+ chromeWindow.removeEventListener("load", onload, true);
+ let browser = chromeWindow.document.createElement("browser");
+ browser.setAttribute("type", "content");
+ chromeWindow.document.documentElement.appendChild(browser);
+ // Wait for about:blank load event ...
+ listenOnce(browser, "load", function onAboutBlankLoad() {
+ // ... before loading the expected doc and waiting for its load event
+ loadAndWait(browser, url, function onDocumentLoaded() {
+ callback(test, browser, function onTestDone() {
+ chromeWindow.close();
+ test.done();
+ });
+ });
+ });
+ }, true);
+ };
+}
+
+exports["test:sample"] = WorkerTest(
+ DEFAULT_CONTENT_URL,
+ function(test, browser, done) {
+
+ test.assertNotEqual(browser.contentWindow.location.href, "about:blank",
+ "window is now on the right document");
+
+ let window = browser.contentWindow
+ let worker = Worker({
+ window: window,
+ contentScript: "new " + function WorkerScope() {
+ // window is accessible
+ let myLocation = window.location.toString();
+ self.on("message", function(data) {
+ if (data == "hi!")
+ self.postMessage("bye!");
+ });
+ },
+ contentScriptWhen: "ready",
+ onMessage: function(msg) {
+ test.assertEqual("bye!", msg);
+ test.assertEqual(worker.url, window.location.href,
+ "worker.url still works");
+ done();
+ }
+ });
+
+ test.assertEqual(worker.url, window.location.href,
+ "worker.url works");
+ worker.postMessage("hi!");
+ }
+);
+
+exports["test:emit"] = WorkerTest(
+ DEFAULT_CONTENT_URL,
+ function(test, browser, done) {
+
+ let worker = Worker({
+ window: browser.contentWindow,
+ contentScript: "new " + function WorkerScope() {
+ // Validate self.on and self.emit
+ self.port.on("addon-to-content", function (data) {
+ self.port.emit("content-to-addon", data);
+ });
+
+ // Check for global pollution
+ //if (typeof on != "undefined")
+ // self.postMessage("`on` is in globals");
+ if (typeof once != "undefined")
+ self.postMessage("`once` is in globals");
+ if (typeof emit != "undefined")
+ self.postMessage("`emit` is in globals");
+
+ },
+ onMessage: function(msg) {
+ test.fail("Got an unexpected message : "+msg);
+ }
+ });
+
+ // Validate worker.port
+ worker.port.on("content-to-addon", function (data) {
+ test.assertEqual(data, "event data");
+ done();
+ });
+ worker.port.emit("addon-to-content", "event data");
+ }
+);
+
+exports["test:emit hack message"] = WorkerTest(
+ DEFAULT_CONTENT_URL,
+ function(test, browser, done) {
+ let worker = Worker({
+ window: browser.contentWindow,
+ contentScript: "new " + function WorkerScope() {
+ // Validate self.port
+ self.port.on("message", function (data) {
+ self.port.emit("message", data);
+ });
+ // We should not receive message on self, but only on self.port
+ self.on("message", function (data) {
+ self.postMessage("message", data);
+ });
+ },
+ onError: function(e) {
+ test.fail("Got exception: "+e);
+ }
+ });
+
+ worker.port.on("message", function (data) {
+ test.assertEqual(data, "event data");
+ done();
+ });
+ worker.on("message", function (data) {
+ test.fail("Got an unexpected message : "+msg);
+ });
+ worker.port.emit("message", "event data");
+ }
+);
+
+exports["test:n-arguments emit"] = WorkerTest(
+ DEFAULT_CONTENT_URL,
+ function(test, browser, done) {
+ let worker = Worker({
+ window: browser.contentWindow,
+ contentScript: "new " + function WorkerScope() {
+ // Validate self.on and self.emit
+ self.port.on("addon-to-content", function (a1, a2, a3) {
+ self.port.emit("content-to-addon", a1, a2, a3);
+ });
+ }
+ });
+
+ // Validate worker.port
+ worker.port.on("content-to-addon", function (arg1, arg2, arg3) {
+ test.assertEqual(arg1, "first argument");
+ test.assertEqual(arg2, "second");
+ test.assertEqual(arg3, "third");
+ done();
+ });
+ worker.port.emit("addon-to-content", "first argument", "second", "third");
+ }
+);
+
+exports["test:post-json-values-only"] = WorkerTest(
+ DEFAULT_CONTENT_URL,
+ function(test, browser, done) {
+
+ let worker = Worker({
+ window: browser.contentWindow,
+ contentScript: "new " + function WorkerScope() {
+ self.on("message", function (message) {
+ self.postMessage([ message.fun === undefined,
+ typeof message.w,
+ message.w && "port" in message.w,
+ message.w.url,
+ Array.isArray(message.array),
+ JSON.stringify(message.array)]);
+ });
+ }
+ });
+
+ // Validate worker.onMessage
+ let array = [1, 2, 3];
+ worker.on("message", function (message) {
+ test.assert(message[0], "function becomes undefined");
+ test.assertEqual(message[1], "object", "object stays object");
+ test.assert(message[2], "object's attributes are enumerable");
+ test.assertEqual(message[3], DEFAULT_CONTENT_URL,
+ "jsonable attributes are accessible");
+ // See bug 714891, Arrays may be broken over compartements:
+ test.assert(message[4], "Array keeps being an array");
+ test.assertEqual(message[5], JSON.stringify(array),
+ "Array is correctly serialized");
+ done();
+ });
+ worker.postMessage({ fun: function () {}, w: worker, array: array });
+ }
+);
+
+exports["test:emit-json-values-only"] = WorkerTest(
+ DEFAULT_CONTENT_URL,
+ function(test, browser, done) {
+
+ let worker = Worker({
+ window: browser.contentWindow,
+ contentScript: "new " + function WorkerScope() {
+ // Validate self.on and self.emit
+ self.port.on("addon-to-content", function (fun, w, obj, array) {
+ self.port.emit("content-to-addon", [
+ fun === null,
+ typeof w,
+ "port" in w,
+ w.url,
+ "fun" in obj,
+ Object.keys(obj.dom).length,
+ Array.isArray(array),
+ JSON.stringify(array)
+ ]);
+ });
+ }
+ });
+
+ // Validate worker.port
+ let array = [1, 2, 3];
+ worker.port.on("content-to-addon", function (result) {
+ test.assert(result[0], "functions become null");
+ test.assertEqual(result[1], "object", "objects stay objects");
+ test.assert(result[2], "object's attributes are enumerable");
+ test.assertEqual(result[3], DEFAULT_CONTENT_URL,
+ "json attribute is accessible");
+ test.assert(!result[4], "function as object attribute is removed");
+ test.assertEqual(result[5], 0, "DOM nodes are converted into empty object");
+ // See bug 714891, Arrays may be broken over compartments:
+ test.assert(result[6], "Array keeps being an array");
+ test.assertEqual(result[7], JSON.stringify(array),
+ "Array is correctly serialized");
+ done();
+ });
+
+ let obj = {
+ fun: function () {},
+ dom: browser.contentWindow.document.createElement("div")
+ };
+ worker.port.emit("addon-to-content", function () {}, worker, obj, array);
+ }
+);
+
+exports["test:content is wrapped"] = WorkerTest(
+ "data:text/html;charset=utf-8,<script>var documentValue=true;</script>",
+ function(test, browser, done) {
+
+ let worker = Worker({
+ window: browser.contentWindow,
+ contentScript: "new " + function WorkerScope() {
+ self.postMessage(!window.documentValue);
+ },
+ contentScriptWhen: "ready",
+ onMessage: function(msg) {
+ test.assert(msg,
+ "content script has a wrapped access to content document");
+ done();
+ }
+ });
+ }
+);
+
+exports["test:chrome is unwrapped"] = function(test) {
+ let window = makeWindow();
+ test.waitUntilDone();
+
+ listenOnce(window, "load", function onload() {
+
+ let worker = Worker({
+ window: window,
+ contentScript: "new " + function WorkerScope() {
+ self.postMessage(window.documentValue);
+ },
+ contentScriptWhen: "ready",
+ onMessage: function(msg) {
+ test.assert(msg,
+ "content script has an unwrapped access to chrome document");
+ window.close();
+ test.done();
+ }
+ });
+
+ });
+}
+
+exports["test:nothing is leaked to content script"] = WorkerTest(
+ DEFAULT_CONTENT_URL,
+ function(test, browser, done) {
+
+ let worker = Worker({
+ window: browser.contentWindow,
+ contentScript: "new " + function WorkerScope() {
+ self.postMessage([
+ "ContentWorker" in window,
+ "UNWRAP_ACCESS_KEY" in window,
+ "getProxyForObject" in window
+ ]);
+ },
+ contentScriptWhen: "ready",
+ onMessage: function(list) {
+ test.assert(!list[0], "worker API contrustor isn't leaked");
+ test.assert(!list[1], "Proxy API stuff isn't leaked 1/2");
+ test.assert(!list[2], "Proxy API stuff isn't leaked 2/2");
+ done();
+ }
+ });
+ }
+);
+
+exports["test:ensure console.xxx works in cs"] = WorkerTest(
+ DEFAULT_CONTENT_URL,
+ function(test, browser, done) {
+
+ // Create a new module loader in order to be able to create a `console`
+ // module mockup:
+ let loader = Loader(module, {
+ console: {
+ log: hook.bind("log"),
+ info: hook.bind("info"),
+ warn: hook.bind("warn"),
+ error: hook.bind("error"),
+ debug: hook.bind("debug"),
+ exception: hook.bind("exception")
+ }
+ });
+
+ // Intercept all console method calls
+ let calls = [];
+ function hook(msg) {
+ test.assertEqual(this, msg,
+ "console.xxx(\"xxx\"), i.e. message is equal to the " +
+ "console method name we are calling");
+ calls.push(msg);
+ }
+
+ // Finally, create a worker that will call all console methods
+ let worker = loader.require("sdk/content/worker").Worker({
+ window: browser.contentWindow,
+ contentScript: "new " + function WorkerScope() {
+ console.log("log");
+ console.info("info");
+ console.warn("warn");
+ console.error("error");
+ console.debug("debug");
+ console.exception("exception");
+ self.postMessage();
+ },
+ onMessage: function() {
+ // Ensure that console methods are called in the same execution order
+ test.assertEqual(JSON.stringify(calls),
+ JSON.stringify(["log", "info", "warn", "error", "debug", "exception"]),
+ "console has been called successfully, in the expected order");
+ done();
+ }
+ });
+ }
+);
+
+
+exports["test:setTimeout can\"t be cancelled by content"] = WorkerTest(
+ "data:text/html;charset=utf-8,<script>var documentValue=true;</script>",
+ function(test, browser, done) {
+
+ let worker = Worker({
+ window: browser.contentWindow,
+ contentScript: "new " + function WorkerScope() {
+ let id = setTimeout(function () {
+ self.postMessage("timeout");
+ }, 100);
+ unsafeWindow.eval("clearTimeout("+id+");");
+ },
+ contentScriptWhen: "ready",
+ onMessage: function(msg) {
+ test.assert(msg,
+ "content didn't managed to cancel our setTimeout");
+ done();
+ }
+ });
+ }
+);
+
+exports["test:clearTimeout"] = WorkerTest(
+ "data:text/html;charset=utf-8,clear timeout",
+ function(test, browser, done) {
+ let worker = Worker({
+ window: browser.contentWindow,
+ contentScript: "new " + function WorkerScope() {
+ let id1 = setTimeout(function() {
+ self.postMessage("failed");
+ }, 10);
+ let id2 = setTimeout(function() {
+ self.postMessage("done");
+ }, 100);
+ clearTimeout(id1);
+ },
+ contentScriptWhen: "ready",
+ onMessage: function(msg) {
+ if (msg === "failed") {
+ test.fail("failed to cancel timer");
+ } else {
+ test.pass("timer cancelled");
+ done();
+ }
+ }
+ });
+ }
+);
+
+exports["test:clearInterval"] = WorkerTest(
+ "data:text/html;charset=utf-8,clear timeout",
+ function(test, browser, done) {
+ let called = 0;
+ let worker = Worker({
+ window: browser.contentWindow,
+ contentScript: "new " + function WorkerScope() {
+ let id = setInterval(function() {
+ self.postMessage("intreval")
+ clearInterval(id)
+ setTimeout(function() {
+ self.postMessage("done")
+ }, 100)
+ }, 10);
+ },
+ contentScriptWhen: "ready",
+ onMessage: function(msg) {
+ if (msg === "intreval") {
+ called = called + 1;
+ if (called > 1) test.fail("failed to cancel timer");
+ } else {
+ test.pass("interval cancelled");
+ done();
+ }
+ }
+ });
+ }
+)
+
+exports["test:setTimeout are unregistered on content unload"] = WorkerTest(
+ DEFAULT_CONTENT_URL,
+ function(test, browser, done) {
+
+ let originalWindow = browser.contentWindow;
+ let worker = Worker({
+ window: browser.contentWindow,
+ contentScript: "new " + function WorkerScope() {
+ document.title = "ok";
+ let i = 0;
+ setInterval(function () {
+ document.title = i++;
+ }, 10);
+ },
+ contentScriptWhen: "ready"
+ });
+
+ // Change location so that content script is destroyed,
+ // and all setTimeout/setInterval should be unregistered.
+ // Wait some cycles in order to execute some intervals.
+ setTimeout(function () {
+ // Bug 689621: Wait for the new document load so that we are sure that
+ // previous document cancelled its intervals
+ let url2 = "data:text/html;charset=utf-8,<title>final</title>";
+ loadAndWait(browser, url2, function onload() {
+ let titleAfterLoad = originalWindow.document.title;
+ // Wait additional cycles to verify that intervals are really cancelled
+ setTimeout(function () {
+ test.assertEqual(browser.contentDocument.title, "final",
+ "New document has not been modified");
+ test.assertEqual(originalWindow.document.title, titleAfterLoad,
+ "Nor previous one");
+
+ done();
+ }, 100);
+ });
+ }, 100);
+ }
+);
+
+exports['test:check window attribute in iframes'] = WorkerTest(
+ DEFAULT_CONTENT_URL,
+ function(test, browser, done) {
+
+ // Create a first iframe and wait for its loading
+ let contentWin = browser.contentWindow;
+ let contentDoc = contentWin.document;
+ let iframe = contentDoc.createElement("iframe");
+ contentDoc.body.appendChild(iframe);
+
+ listenOnce(iframe, "load", function onload() {
+
+ // Create a second iframe inside the first one and wait for its loading
+ let iframeDoc = iframe.contentWindow.document;
+ let subIframe = iframeDoc.createElement("iframe");
+ iframeDoc.body.appendChild(subIframe);
+
+ listenOnce(subIframe, "load", function onload() {
+ subIframe.removeEventListener("load", onload, true);
+
+ // And finally create a worker against this second iframe
+ let worker = Worker({
+ window: subIframe.contentWindow,
+ contentScript: 'new ' + function WorkerScope() {
+ self.postMessage([
+ window.top !== window,
+ frameElement,
+ window.parent !== window,
+ top.location.href,
+ parent.location.href,
+ ]);
+ },
+ onMessage: function(msg) {
+ test.assert(msg[0], "window.top != window");
+ test.assert(msg[1], "window.frameElement is defined");
+ test.assert(msg[2], "window.parent != window");
+ test.assertEqual(msg[3], contentWin.location.href,
+ "top.location refers to the toplevel content doc");
+ test.assertEqual(msg[4], iframe.contentWindow.location.href,
+ "parent.location refers to the first iframe doc");
+ done();
+ }
+ });
+
+ });
+ subIframe.setAttribute("src", "data:text/html;charset=utf-8,bar");
+
+ });
+ iframe.setAttribute("src", "data:text/html;charset=utf-8,foo");
+ }
+);
+
+exports['test:check window attribute in toplevel documents'] = WorkerTest(
+ DEFAULT_CONTENT_URL,
+ function(test, browser, done) {
+
+ let worker = Worker({
+ window: browser.contentWindow,
+ contentScript: 'new ' + function WorkerScope() {
+ self.postMessage([
+ window.top === window,
+ frameElement,
+ window.parent === window
+ ]);
+ },
+ onMessage: function(msg) {
+ test.assert(msg[0], "window.top == window");
+ test.assert(!msg[1], "window.frameElement is null");
+ test.assert(msg[2], "window.parent == window");
+ done();
+ }
+ });
+ }
+);
+
+exports["test:check worker API with page history"] = WorkerTest(
+ DEFAULT_CONTENT_URL,
+ function(test, browser, done) {
+ let url2 = "data:text/html;charset=utf-8,bar";
+
+ loadAndWait(browser, url2, function () {
+ let worker = Worker({
+ window: browser.contentWindow,
+ contentScript: "new " + function WorkerScope() {
+ // Just before the content script is disable, we register a timeout
+ // that will be disable until the page gets visible again
+ self.on("pagehide", function () {
+ setTimeout(function () {
+ self.postMessage("timeout restored");
+ }, 0);
+ });
+ },
+ contentScriptWhen: "start"
+ });
+
+ // postMessage works correctly when the page is visible
+ worker.postMessage("ok");
+
+ // We have to wait before going back into history,
+ // otherwise `goBack` won't do anything.
+ setTimeout(function () {
+ browser.goBack();
+ }, 0);
+
+ // Wait for the document to be hidden
+ browser.addEventListener("pagehide", function onpagehide() {
+ browser.removeEventListener("pagehide", onpagehide, false);
+ // Now any event sent to this worker should throw
+ test.assertRaises(
+ function () { worker.postMessage("data"); },
+ "The page is currently hidden and can no longer be used until it" +
+ " is visible again.",
+ "postMessage should throw when the page is hidden in history"
+ );
+ test.assertRaises(
+ function () { worker.port.emit("event"); },
+ "The page is currently hidden and can no longer be used until it" +
+ " is visible again.",
+ "port.emit should throw when the page is hidden in history"
+ );
+
+ // Display the page with attached content script back in order to resume
+ // its timeout and receive the expected message.
+ // We have to delay this in order to not break the history.
+ // We delay for a non-zero amount of time in order to ensure that we
+ // do not receive the message immediatly, so that the timeout is
+ // actually disabled
+ setTimeout(function () {
+ worker.on("message", function (data) {
+ test.assert(data, "timeout restored");
+ done();
+ });
+ browser.goForward();
+ }, 500);
+
+ }, false);
+ });
+
+ }
+);
diff --git a/tools/addon-sdk-1.12/test/test-context-menu.html b/tools/addon-sdk-1.12/test/test-context-menu.html
new file mode 100644
index 0000000..b3f70a1
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-context-menu.html
@@ -0,0 +1,46 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <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;charset=utf-8,An iframe."
+ width="200" height="100">
+ </iframe>
+ </p>
+ </body>
+</html>
diff --git a/tools/addon-sdk-1.12/test/test-context-menu.js b/tools/addon-sdk-1.12/test/test-context-menu.js
new file mode 100644
index 0000000..727028f
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-context-menu.js
@@ -0,0 +1,2093 @@
+/* -*- 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('sdk/test/loader');
+const timer = require("sdk/timers");
+
+// 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("sdk/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) {' +
+ ' self.postMessage(node.tagName);' +
+ ' return false;' +
+ '});',
+ onMessage: function (tagName) {
+ test.assertEqual(tagName, "HTML", "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({' +
+ ' tagName: node.tagName,' +
+ ' data: data' +
+ ' });' +
+ '});',
+ onMessage: function (data) {
+ test.assertEqual(this, item, "`this` inside onMessage should be item");
+ test.assertEqual(data.tagName, "HTML", "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({' +
+ ' tagName: node.tagName,' +
+ ' data: data' +
+ ' });' +
+ '});',
+ onMessage: function (data) {
+ test.assertEqual(this, topMenu, "`this` inside top menu should be menu");
+ test.assertEqual(data.tagName, "A", "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();
+ });
+ });
+};
+
+// Bug 732716: Ensure that the node given in `click` event works fine
+// (i.e. is correctly wrapped)
+exports.testDrawImageOnClickNode = function (test) {
+ test = new TestHelper(test);
+ test.withTestDoc(function (window, doc) {
+ let loader = test.newLoader();
+ let item = new loader.cm.Item({
+ label: "item",
+ context: loader.cm.SelectorContext("img"),
+ contentScript: "new " + function() {
+ self.on("click", function (img, data) {
+ let ctx = document.createElement("canvas").getContext("2d");
+ ctx.drawImage(img, 1, 1, 1, 1);
+ self.postMessage("done");
+ });
+ },
+ onMessage: function (msg) {
+ if (msg === "done")
+ test.done();
+ }
+ });
+ test.showMenu(doc.getElementById("image"), 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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/system/xul-app").is("Firefox")) {
+ module.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);
+ 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("sdk/context-menu"),
+ globalScope: loader.sandbox("sdk/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("sdk/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.12/test/test-cortex.js b/tools/addon-sdk-1.12/test/test-cortex.js
new file mode 100644
index 0000000..ab3d194
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-cortex.js
@@ -0,0 +1,122 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// vim:set ts=2 sw=2 sts=2
+
+"use strict";
+
+var Cortex = require("sdk/deprecated/cortex").Cortex;
+
+exports["test property changes propagate"] = function (assert) {
+ var source = {
+ _foo: "secret",
+ get foo() {
+ return this._foo;
+ },
+ set foo(value) {
+ this._foo = value;
+ },
+ get getOnly() {
+ return this._foo;
+ },
+ set setOnly(value) {
+ this._setOnly = value;
+ },
+ bar: "public",
+ method: function method(a, b) {
+ return this._foo + a + b
+ }
+ };
+ var fixture = Cortex(source);
+
+ assert.ok(!('_foo' in fixture),
+ "properties that start with `_` are omitted");
+ assert.equal(fixture.foo, "secret", "get accessor alias works");
+ fixture.foo = "new secret";
+ assert.equal(fixture.foo, "new secret", "set accessor alias works");
+ assert.equal(source.foo, "new secret", "accessor delegates to the source");
+ assert.equal(fixture.bar, "public", "data property alias works");
+ fixture.bar = "bar";
+ assert.equal(source.bar, "bar", "data property change propagates");
+ source.bar = "foo"
+ assert.equal(fixture.bar, "foo", "data property change probagets back");
+ assert.equal(fixture.method("a", "b"), "new secretab",
+ "public methods are callable");
+ assert.equal(fixture.method.call({ _foo: "test" }, " a,", "b"),
+ "new secret a,b",
+ "`this` pseudo-variable can not be passed through call.");
+ assert.equal(fixture.method.apply({ _foo: "test" }, [" a,", "b"]),
+ "new secret a,b",
+ "`this` pseudo-variable can not be passed through apply.");
+ assert.equal(fixture.getOnly, source._foo,
+ "getter returned property of wrapped object");
+ fixture.setOnly = 'bar'
+ assert.equal(source._setOnly, 'bar', "setter modified wrapped object")
+};
+
+
+exports["test immunity of inheritance"] = function(assert) {
+ function Type() {}
+ Type.prototype = {
+ constructor: Type,
+ _bar: 2,
+ bar: 3,
+ get_Foo: function getFoo() {
+ return this._foo;
+ }
+ }
+ var source = Object.create(Type.prototype, {
+ _foo: { value: 'secret' },
+ getBar: { value: function get_Bar() {
+ return this.bar
+ }},
+ get_Bar: { value: function getBar() {
+ return this._bar
+ }}
+ });
+
+ var fixture = Cortex(source);
+
+ assert.ok(Cortex({}, null, Type.prototype) instanceof Type,
+ "if custom prototype is providede cortex will inherit from it");
+ assert.ok(fixture instanceof Type,
+ "if no prototype is given cortex inherits from object's prototype");
+
+ source.bar += 1;
+ assert.notEqual(fixture.bar, source.bar,
+ "chages of properties don't propagate to non-aliases");
+ assert.equal(fixture.getBar(), source.bar,
+ "methods accessing public properties are bound to the source");
+
+ fixture._bar += 1;
+ assert.notEqual(fixture._bar, source._bar,
+ "changes of non aliased properties don't propagate");
+ assert.equal(fixture.get_Bar(), source._bar,
+ "methods accessing privates are bound to the source");
+ assert.notEqual(fixture.get_Foo(), source._foo,
+ "prototoype methods are not bound to the source");
+}
+
+exports["test customized public properties"] = function(assert) {
+ var source = {
+ _a: 'a',
+ b: 'b',
+ get: function get(name) {
+ return this[name];
+ }
+ };
+
+ var fixture = Cortex(source, ['_a', 'get']);
+ fixture._a += "#change";
+
+
+ assert.ok(!("b" in fixture), "non-public own property is not defined");
+ assert.equal(fixture.get("b"), source.b,
+ "public methods preserve access to the private properties");
+ assert.equal(fixture._a, source._a,
+ "custom public property changes propagate");
+}
+
+//if (require.main == module)
+ require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-cuddlefish.js b/tools/addon-sdk-1.12/test/test-cuddlefish.js
new file mode 100644
index 0000000..f160469
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-cuddlefish.js
@@ -0,0 +1,49 @@
+/* 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, unload, override } = require('sdk/loader/cuddlefish');
+const packaging = require('@loader/options');
+
+exports['test loader'] = function(assert) {
+ var prints = [];
+ function print(message) {
+ prints.push(message);
+ }
+
+ let options = JSON.parse(JSON.stringify(packaging));
+
+ let loader = Loader(override(options, {
+ globals: {
+ print: print,
+ foo: 1
+ }
+ }));
+ let require = Require(loader, module);
+
+ var fixture = require('./loader/fixture');
+
+ assert.equal(fixture.foo, 1, 'custom globals must work.');
+ assert.equal(fixture.bar, 2, 'exports are set');
+
+ assert.equal(prints[0], 'testing', 'global print must be injected.');
+
+ var unloadsCalled = '';
+
+ require("sdk/system/unload").when(function(reason) {
+ assert.equal(reason, 'test', 'unload reason is passed');
+ unloadsCalled += 'a';
+ });
+ require('sdk/system/unload.js').when(function() {
+ unloadsCalled += 'b';
+ });
+
+ unload(loader, 'test');
+
+ assert.equal(unloadsCalled, 'ba',
+ 'loader.unload() must call listeners in LIFO order.');
+};
+
+require('test').run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-deprecate.js b/tools/addon-sdk-1.12/test/test-deprecate.js
new file mode 100644
index 0000000..b1e0f32
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-deprecate.js
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const deprecate = require("sdk/util/deprecate");
+var { Loader } = require("sdk/test/loader");
+
+function LoaderWithHookedConsole() {
+ let errors = [];
+ let loader = Loader(module, {
+ console: Object.create(console, {
+ error: { value: function(error) {
+ errors.push(error);
+ }}
+ })
+ });
+
+ return {
+ loader: loader,
+ deprecate: loader.require("sdk/util/deprecate"),
+ errors: errors
+ }
+}
+
+exports["test Deprecate Usage"] = function testDeprecateUsage(assert) {
+ let { loader, deprecate, errors } = LoaderWithHookedConsole();
+
+ function functionIsDeprecated() {
+ deprecate.deprecateUsage("foo");
+ }
+
+ functionIsDeprecated();
+
+ assert.equal(errors.length, 1, "only one error is dispatched");
+
+ let msg = errors[0];
+ assert.ok(msg.indexOf("foo") !== -1,
+ "message contains the given message");
+ assert.ok(msg.indexOf("functionIsDeprecated") !== -1,
+ "message contains name of the caller function");
+ assert.ok(msg.indexOf(module.uri) !== -1,
+ "message contains URI of the caller module");
+
+ loader.unload();
+}
+
+exports["test Deprecate Function"] = function testDeprecateFunction(assert) {
+ let { loader, deprecate, errors } = LoaderWithHookedConsole();
+
+ let self = {};
+ let arg1 = "foo";
+ let arg2 = {};
+
+ function originalFunction(a1, a2) {
+ assert.equal(this, self);
+ assert.equal(a1, arg1);
+ assert.equal(a2, arg2);
+ };
+
+ let deprecateFunction = deprecate.deprecateFunction(originalFunction,
+ "bar");
+
+ deprecateFunction.call(self, arg1, arg2);
+
+ assert.equal(errors.length, 1,
+ "only one error is dispatched");
+
+ let msg = errors[0];
+ assert.ok(msg.indexOf("bar") !== -1, "message contains the given message");
+ assert.ok(msg.indexOf("testDeprecateFunction") !== -1,
+ "message contains name of the caller function");
+ assert.ok(msg.indexOf(module.uri) !== -1,
+ "message contains URI of the caller module");
+
+ loader.unload();
+}
+
+require("test").run(exports)
diff --git a/tools/addon-sdk-1.12/test/test-deprecated-list.js b/tools/addon-sdk-1.12/test/test-deprecated-list.js
new file mode 100644
index 0000000..4b5f598
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-deprecated-list.js
@@ -0,0 +1,196 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+function assertList(test, array, list) {
+ for (let i = 0, ii = array.length; i < ii; i < ii, i++) {
+ test.assertEqual(
+ array.length,
+ list.length,
+ 'list must contain same amount of elements as array'
+ );
+ test.assertEqual(
+ 'List(' + array + ')',
+ list + '',
+ 'toString must output array like result'
+ );
+ test.assert(
+ i in list,
+ 'must contain element with index: ' + i
+ );
+ test.assertEqual(
+ array[i],
+ list[i],
+ 'element with index: ' + i + ' should match'
+ );
+ }
+}
+
+const { List } = require('sdk/deprecated/list');
+
+exports['test:test for'] = function(test) {
+ let fixture = List(3, 2, 1);
+
+ test.assertEqual(3, fixture.length, 'length is 3');
+ let i = 0;
+ for (let key in fixture) {
+ test.assertEqual(i++, key, 'key should match');
+ }
+};
+
+exports['test:test for each'] = function(test) {
+ let fixture = new List(3, 2, 1);
+
+ test.assertEqual(3, fixture.length, 'length is 3');
+ let i = 3;
+ for each (let value in fixture) {
+ test.assertEqual(i--, value, 'value should match');
+ }
+};
+
+exports['test:test toString'] = function(test) {
+ let fixture = List(3, 2, 1);
+
+ test.assertEqual(
+ 'List(3,2,1)',
+ fixture + '',
+ 'toString must output array like result'
+ )
+};
+
+exports['test:test constructor with apply'] = function(test) {
+ let array = ['a', 'b', 'c'];
+ let fixture = List.apply(null, array);
+
+ test.assertEqual(
+ 3,
+ fixture.length,
+ 'should have applied arguments'
+ );
+};
+
+exports['test:direct element access'] = function(test) {
+ let array = [1, 'foo', 2, 'bar', {}, 'bar', function a() {}, test, 1];
+ let fixture = List.apply(null, array);
+ array.splice(5, 1);
+ array.splice(7, 1);
+
+ test.assertEqual(
+ array.length,
+ fixture.length,
+ 'list should omit duplicate elements'
+ );
+
+ test.assertEqual(
+ 'List(' + array + ')',
+ fixture.toString(),
+ 'elements should not be rearranged'
+ );
+
+ for (let key in array) {
+ test.assert(key in fixture,'should contain key for index:' + key);
+ test.assertEqual(
+ array[key],
+ fixture[key],
+ 'values should match for: ' + key
+ );
+ }
+};
+
+exports['test:removing adding elements'] = function(test) {
+ let array = [1, 'foo', 2, 'bar', {}, 'bar', function a() {}, test, 1];
+ let fixture = List.compose({
+ add: function() this._add.apply(this, arguments),
+ remove: function() this._remove.apply(this, arguments),
+ clear: function() this._clear()
+ }).apply(null, array);
+ array.splice(5, 1);
+ array.splice(7, 1);
+
+ assertList(test, array, fixture);
+
+ array.splice(array.indexOf(2), 1);
+ fixture.remove(2);
+ assertList(test, array, fixture);
+
+ array.splice(array.indexOf('foo'), 1);
+ fixture.remove('foo');
+ array.splice(array.indexOf(1), 1);
+ fixture.remove(1);
+ array.push('foo');
+ fixture.add('foo');
+ assertList(test, array, fixture);
+
+ array.splice(0);
+ fixture.clear(0);
+ assertList(test, array, fixture);
+
+ array.push(1, 'foo', 2, 'bar', 3);
+ fixture.add(1);
+ fixture.add('foo');
+ fixture.add(2);
+ fixture.add('bar');
+ fixture.add(3);
+
+ assertList(test, array, fixture);
+};
+
+exports['test: remove does not leave invalid numerical properties'] = function(test) {
+ let fixture = List.compose({
+ remove: function() this._remove.apply(this, arguments),
+ }).apply(null, [1, 2, 3]);
+
+ fixture.remove(1);
+ test.assertEqual(fixture[fixture.length], undefined);
+}
+
+exports['test:add list item from Iterator'] = function(test) {
+ let array = [1, 2, 3, 4], sum = 0, added = false;
+
+ let fixture = List.compose({
+ add: function() this._add.apply(this, arguments),
+ }).apply(null, array);
+
+ for each (let item in fixture) {
+ sum += item;
+
+ if (!added) {
+ fixture.add(5);
+ added = true;
+ }
+ }
+
+ test.assertEqual(sum, 1 + 2 + 3 + 4);
+};
+
+exports['test:remove list item from Iterator'] = function(test) {
+ let array = [1, 2, 3, 4], sum = 0;
+
+ let fixture = List.compose({
+ remove: function() this._remove.apply(this, arguments),
+ }).apply(null, array);
+
+ for each (let item in fixture) {
+ sum += item;
+ fixture.remove(item);
+ }
+
+ test.assertEqual(sum, 1 + 2 + 3 + 4);
+};
+
+exports['test:clear list from Iterator'] = function(test) {
+ let array = [1, 2, 3, 4], sum = 0;
+
+ let fixture = List.compose({
+ clear: function() this._clear()
+ }).apply(null, array);
+
+ for each (let item in fixture) {
+ sum += item;
+ fixture.clear();
+ }
+
+ test.assertEqual(sum, 1 + 2 + 3 + 4);
+};
diff --git a/tools/addon-sdk-1.12/test/test-dom.js b/tools/addon-sdk-1.12/test/test-dom.js
new file mode 100644
index 0000000..87c8372
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-dom.js
@@ -0,0 +1,88 @@
+/* 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 events = require("sdk/dom/events");
+const { activeBrowserWindow: { document } } = require("sdk/deprecated/window-utils");
+const window = document.window;
+
+exports["test on / emit"] = function (assert, done) {
+ let element = document.createElement("div");
+ events.on(element, "click", function listener(event) {
+ assert.equal(event.target, element, "event has correct target");
+ events.removeListener(element, "click", listener);
+ done();
+ });
+
+ events.emit(element, "click", {
+ category: "MouseEvents",
+ settings: [
+ true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null
+ ]
+ });
+};
+
+exports["test remove"] = function (assert, done) {
+ let element = document.createElement("span");
+ let l1 = 0;
+ let l2 = 0;
+ let options = {
+ category: "MouseEvents",
+ settings: [
+ true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null
+ ]
+ };
+
+ events.on(element, "click", function listener1(event) {
+ l1 ++;
+ assert.equal(event.target, element, "event has correct target");
+ events.removeListener(element, "click", listener1);
+ });
+
+ events.on(element, "click", function listener2(event) {
+ l2 ++;
+ if (l1 < l2) {
+ assert.equal(l1, 1, "firs listener was called and then romeved");
+ events.removeListener(element, "click", listener2);
+ done();
+ }
+ events.emit(element, "click", options);
+ });
+
+ events.emit(element, "click", options);
+};
+
+exports["test once"] = function (assert, done) {
+ let element = document.createElement("h1");
+ let l1 = 0;
+ let l2 = 0;
+ let options = {
+ category: "MouseEvents",
+ settings: [
+ true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null
+ ]
+ };
+
+
+ events.once(element, "click", function listener(event) {
+ assert.equal(event.target, element, "event target is a correct element");
+ l1 ++;
+ });
+
+ events.on(element, "click", function listener(event) {
+ l2 ++;
+ if (l2 > 3) {
+ events.removeListener(element, "click", listener);
+ assert.equal(event.target, element, "event has correct target");
+ assert.equal(l1, 1, "once was called only once");
+ done();
+ }
+ events.emit(element, "click", options);
+ });
+
+ events.emit(element, "click", options);
+}
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-environment.js b/tools/addon-sdk-1.12/test/test-environment.js
new file mode 100644
index 0000000..413997f
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-environment.js
@@ -0,0 +1,50 @@
+/* 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 { env } = require('sdk/system/environment');
+const { Cc, Ci } = require('chrome');
+const { get, set, exists } = Cc['@mozilla.org/process/environment;1'].
+ getService(Ci.nsIEnvironment);
+
+exports['test exists'] = function(assert) {
+ assert.equal('PATH' in env, exists('PATH'),
+ 'PATH environment variable is defined');
+ assert.equal('FOO1' in env, exists('FOO1'),
+ 'FOO1 environment variable is not defined');
+ set('FOO1', 'foo');
+ assert.equal('FOO1' in env, true,
+ 'FOO1 environment variable was set');
+ set('FOO1', null);
+ assert.equal('FOO1' in env, false,
+ 'FOO1 environment variable was unset');
+};
+
+exports['test get'] = function(assert) {
+ assert.equal(env.PATH, get('PATH'), 'PATH env variable matches');
+ assert.equal(env.BAR2, undefined, 'BAR2 env variable is not defined');
+ set('BAR2', 'bar');
+ assert.equal(env.BAR2, 'bar', 'BAR2 env variable was set');
+ set('BAR2', null);
+ assert.equal(env.BAR2, undefined, 'BAR2 env variable was unset');
+};
+
+exports['test set'] = function(assert) {
+ assert.equal(get('BAZ3'), '', 'BAZ3 env variable is not set');
+ assert.equal(env.BAZ3, undefined, 'BAZ3 is not set');
+ env.BAZ3 = 'baz';
+ assert.equal(env.BAZ3, get('BAZ3'), 'BAZ3 env variable is set');
+ assert.equal(get('BAZ3'), 'baz', 'BAZ3 env variable was set to "baz"');
+};
+
+exports['test unset'] = function(assert) {
+ env.BLA4 = 'bla';
+ assert.equal(env.BLA4, 'bla', 'BLA4 env varibale is set');
+ delete env.BLA4;
+ assert.equal(env.BLA4, undefined, 'BLA4 env variable is unset');
+ assert.equal('BLA4' in env, false, 'BLA4 env variable no longer exists' );
+};
+
+require('test').run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-errors.js b/tools/addon-sdk-1.12/test/test-errors.js
new file mode 100644
index 0000000..91ffd78
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-errors.js
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var errors = require("sdk/deprecated/errors");
+
+exports.testCatchAndLog = function(test) {
+ var caught = [];
+ function dummyLog(e) { caught.push(e); }
+
+ var wrapped = errors.catchAndLog(function(x) {
+ throw Error("blah" + x + this);
+ },
+ "boop",
+ dummyLog);
+ test.assertEqual(wrapped.call("hi", 1), "boop",
+ "exceptions should be trapped, def. resp. returned");
+ test.assertEqual(caught.length, 1,
+ "logging function should be called");
+ test.assertEqual(caught[0].message, "blah1hi",
+ "args and this should be passed to wrapped func");
+};
+
+exports.testCatchAndLogProps = function(test) {
+ var caught = [];
+ function dummyLog(e) { caught.push(e); }
+
+ var thing = {
+ foo: function(x) { throw Error("nowai" + x); },
+ bar: function() { throw Error("blah"); },
+ baz: function() { throw Error("fnarg"); }
+ };
+
+ errors.catchAndLogProps(thing, "foo", "ugh", dummyLog);
+
+ test.assertEqual(thing.foo(1), "ugh",
+ "props should be wrapped");
+ test.assertEqual(caught.length, 1,
+ "logging function should be called");
+ test.assertEqual(caught[0].message, "nowai1",
+ "args should be passed to wrapped func");
+ test.assertRaises(function() { thing.bar(); },
+ "blah",
+ "non-wrapped props should be wrapped");
+
+ errors.catchAndLogProps(thing, ["bar", "baz"], "err", dummyLog);
+ test.assert((thing.bar() == thing.baz()) &&
+ (thing.bar() == "err"),
+ "multiple props should be wrapped if array passed in");
+};
+
+exports.testCatchAndReturn = function(test) {
+ var wrapped = errors.catchAndReturn(function(x) {
+ if (x == 1)
+ return "one";
+ if (x == 2)
+ throw new Error("two");
+ return this + x;
+ });
+
+ test.assertEqual(wrapped(1).returnValue, "one",
+ "arg should be passed; return value should be returned");
+ test.assert(wrapped(2).exception, "exception should be returned");
+ test.assertEqual(wrapped(2).exception.message, "two", "message is correct");
+ test.assert(wrapped(2).exception.fileName.indexOf("test-errors.js") != -1,
+ "filename is present");
+ test.assert(wrapped(2).exception.stack, "stack is available");
+ test.assertEqual(wrapped.call("hi", 3).returnValue, "hi3",
+ "`this` should be set correctly");
+};
diff --git a/tools/addon-sdk-1.12/test/test-event-core.js b/tools/addon-sdk-1.12/test/test-event-core.js
new file mode 100644
index 0000000..2d94137
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-event-core.js
@@ -0,0 +1,216 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+const { on, once, off, emit, count, amass } = require('sdk/event/core');
+const { Loader } = require('sdk/test/loader');
+
+exports['test add a listener'] = function(assert) {
+ let events = [ { name: 'event#1' }, 'event#2' ];
+ let target = { name: 'target' };
+
+ on(target, 'message', function(message) {
+ assert.equal(this, target, 'this is a target object');
+ assert.equal(message, events.shift(), 'message is emitted event');
+ });
+ emit(target, 'message', events[0]);
+ emit(target, 'message', events[0]);
+};
+
+exports['test that listener is unique per type'] = function(assert) {
+ let actual = []
+ let target = {}
+ function listener() { actual.push(1) }
+ on(target, 'message', listener);
+ on(target, 'message', listener);
+ on(target, 'message', listener);
+ on(target, 'foo', listener);
+ on(target, 'foo', listener);
+
+ emit(target, 'message');
+ assert.deepEqual([ 1 ], actual, 'only one message listener added');
+ emit(target, 'foo');
+ assert.deepEqual([ 1, 1 ], actual, 'same listener added for other event');
+};
+
+exports['test event type matters'] = function(assert) {
+ let target = { name: 'target' }
+ on(target, 'message', function() {
+ assert.fail('no event is expected');
+ });
+ on(target, 'done', function() {
+ assert.pass('event is emitted');
+ });
+ emit(target, 'foo')
+ emit(target, 'done');
+};
+
+exports['test all arguments are pasesd'] = function(assert) {
+ let foo = { name: 'foo' }, bar = 'bar';
+ let target = { name: 'target' };
+ on(target, 'message', function(a, b) {
+ assert.equal(a, foo, 'first argument passed');
+ assert.equal(b, bar, 'second argument passed');
+ });
+ emit(target, 'message', foo, bar);
+};
+
+exports['test no side-effects in emit'] = function(assert) {
+ let target = { name: 'target' };
+ on(target, 'message', function() {
+ assert.pass('first listener is called');
+ on(target, 'message', function() {
+ assert.fail('second listener is called');
+ });
+ });
+ emit(target, 'message');
+};
+
+exports['test order of propagation'] = function(assert) {
+ let actual = [];
+ let target = { name: 'target' };
+ on(target, 'message', function() { actual.push(1); });
+ on(target, 'message', function() { actual.push(2); });
+ on(target, 'message', function() { actual.push(3); });
+ emit(target, 'message');
+ assert.deepEqual([ 1, 2, 3 ], actual, 'called in order they were added');
+};
+
+exports['test remove a listener'] = function(assert) {
+ let target = { name: 'target' };
+ let actual = [];
+ on(target, 'message', function listener() {
+ actual.push(1);
+ on(target, 'message', function() {
+ off(target, 'message', listener);
+ actual.push(2);
+ })
+ });
+
+ emit(target, 'message');
+ assert.deepEqual([ 1 ], actual, 'first listener called');
+ emit(target, 'message');
+ assert.deepEqual([ 1, 1, 2 ], actual, 'second listener called');
+
+ emit(target, 'message');
+ assert.deepEqual([ 1, 1, 2, 2, 2 ], actual, 'first listener removed');
+};
+
+exports['test remove all listeners for type'] = function(assert) {
+ let actual = [];
+ let target = { name: 'target' }
+ on(target, 'message', function() { actual.push(1); });
+ on(target, 'message', function() { actual.push(2); });
+ on(target, 'message', function() { actual.push(3); });
+ on(target, 'bar', function() { actual.push('b') });
+ off(target, 'message');
+
+ emit(target, 'message');
+ emit(target, 'bar');
+
+ assert.deepEqual([ 'b' ], actual, 'all message listeners were removed');
+};
+
+exports['test remove all listeners'] = function(assert) {
+ let actual = [];
+ let target = { name: 'target' }
+ on(target, 'message', function() { actual.push(1); });
+ on(target, 'message', function() { actual.push(2); });
+ on(target, 'message', function() { actual.push(3); });
+ on(target, 'bar', function() { actual.push('b') });
+ off(target);
+
+ emit(target, 'message');
+ emit(target, 'bar');
+
+ assert.deepEqual([], actual, 'all listeners events were removed');
+};
+
+exports['test falsy arguments are fine'] = function(assert) {
+ let type, listener, actual = [];
+ let target = { name: 'target' }
+ on(target, 'bar', function() { actual.push(0) });
+
+ off(target, 'bar', listener);
+ emit(target, 'bar');
+ assert.deepEqual([ 0 ], actual, '3rd bad ard will keep listeners');
+
+ off(target, type);
+ emit(target, 'bar');
+ assert.deepEqual([ 0, 0 ], actual, '2nd bad arg will keep listener');
+
+ off(target, type, listener);
+ emit(target, 'bar');
+ assert.deepEqual([ 0, 0, 0 ], actual, '2nd&3rd bad args will keep listener');
+};
+
+exports['test error handling'] = function(assert) {
+ let target = Object.create(null);
+ let error = Error('boom!');
+
+ on(target, 'message', function() { throw error; })
+ on(target, 'error', function(boom) {
+ assert.equal(boom, error, 'thrown exception causes error event');
+ });
+ emit(target, 'message');
+};
+
+exports['test unhandled errors'] = function(assert) {
+ let exceptions = [];
+ let loader = Loader(module, {
+ console: Object.create(console, {
+ exception: { value: function(e) {
+ exceptions.push(e);
+ }}
+ })
+ });
+ let { emit, on } = loader.require('sdk/event/core');
+ let target = {};
+ let boom = Error('Boom!');
+ let drax = Error('Draax!!');
+
+ on(target, 'message', function() { throw boom; });
+
+ emit(target, 'message');
+ assert.ok(~String(exceptions[0]).indexOf('Boom!'),
+ 'unhandled exception is logged');
+
+ on(target, 'error', function() { throw drax; });
+ emit(target, 'message');
+ assert.ok(~String(exceptions[1]).indexOf('Draax!'),
+ 'error in error handler is logged');
+};
+
+exports['test count'] = function(assert) {
+ let target = {};
+
+ assert.equal(count(target, 'foo'), 0, 'no listeners for "foo" events');
+ on(target, 'foo', function() {});
+ assert.equal(count(target, 'foo'), 1, 'listener registered');
+ on(target, 'foo', function() {}, 2, 'another listener registered');
+ off(target)
+ assert.equal(count(target, 'foo'), 0, 'listeners unregistered');
+};
+
+exports['test emit.lazy'] = function(assert) {
+ let target = {}, boom = Error('boom!'), errors = [], actual = []
+
+ on(target, 'error', function error(e) errors.push(e))
+
+ on(target, 'a', function() 1);
+ on(target, 'a', function() {});
+ on(target, 'a', function() 2);
+ on(target, 'a', function() { throw boom });
+ on(target, 'a', function() 3);
+
+ for each (let value in emit.lazy(target, 'a'))
+ actual.push(value);
+
+ assert.deepEqual(actual, [ 1, undefined, 2, 3 ],
+ 'all results were collected');
+ assert.deepEqual(errors, [ boom ], 'errors reporetd');
+};
+
+require('test').run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-event-target.js b/tools/addon-sdk-1.12/test/test-event-target.js
new file mode 100644
index 0000000..d972454
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-event-target.js
@@ -0,0 +1,167 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+const { emit } = require('sdk/event/core');
+const { EventTarget } = require('sdk/event/target');
+const { Loader } = require('sdk/test/loader');
+
+exports['test add a listener'] = function(assert) {
+ let events = [ { name: 'event#1' }, 'event#2' ];
+ let target = EventTarget();
+
+ target.on('message', function(message) {
+ assert.equal(this, target, 'this is a target object');
+ assert.equal(message, events.shift(), 'message is emitted event');
+ });
+
+ emit(target, 'message', events[0]);
+ emit(target, 'message', events[0]);
+};
+
+exports['test pass in listeners'] = function(assert) {
+ let actual = [ ];
+ let target = EventTarget({
+ onMessage: function onMessage(message) {
+ assert.equal(this, target, 'this is an event target');
+ actual.push(1);
+ },
+ onFoo: null,
+ onbla: function() {
+ assert.fail('`onbla` is not supposed to be called');
+ }
+ });
+ target.on('message', function(message) {
+ assert.equal(this, target, 'this is an event target');
+ actual.push(2);
+ });
+
+ emit(target, 'message');
+ emit(target, 'missing');
+
+ assert.deepEqual([ 1, 2 ], actual, 'all listeners trigerred in right order');
+};
+
+exports['test that listener is unique per type'] = function(assert) {
+ let actual = []
+ let target = EventTarget();
+ function listener() { actual.push(1) }
+ target.on('message', listener);
+ target.on('message', listener);
+ target.on('message', listener);
+ target.on('foo', listener);
+ target.on('foo', listener);
+
+ emit(target, 'message');
+ assert.deepEqual([ 1 ], actual, 'only one message listener added');
+ emit(target, 'foo');
+ assert.deepEqual([ 1, 1 ], actual, 'same listener added for other event');
+};
+
+exports['test event type matters'] = function(assert) {
+ let target = EventTarget();
+ target.on('message', function() {
+ assert.fail('no event is expected');
+ });
+ target.on('done', function() {
+ assert.pass('event is emitted');
+ });
+
+ emit(target, 'foo');
+ emit(target, 'done');
+};
+
+exports['test all arguments are pasesd'] = function(assert) {
+ let foo = { name: 'foo' }, bar = 'bar';
+ let target = EventTarget();
+ target.on('message', function(a, b) {
+ assert.equal(a, foo, 'first argument passed');
+ assert.equal(b, bar, 'second argument passed');
+ });
+ emit(target, 'message', foo, bar);
+};
+
+exports['test no side-effects in emit'] = function(assert) {
+ let target = EventTarget();
+ target.on('message', function() {
+ assert.pass('first listener is called');
+ target.on('message', function() {
+ assert.fail('second listener is called');
+ });
+ });
+ emit(target, 'message');
+};
+
+exports['test order of propagation'] = function(assert) {
+ let actual = [];
+ let target = EventTarget();
+ target.on('message', function() { actual.push(1); });
+ target.on('message', function() { actual.push(2); });
+ target.on('message', function() { actual.push(3); });
+ emit(target, 'message');
+ assert.deepEqual([ 1, 2, 3 ], actual, 'called in order they were added');
+};
+
+exports['test remove a listener'] = function(assert) {
+ let target = EventTarget();
+ let actual = [];
+ target.on('message', function listener() {
+ actual.push(1);
+ target.on('message', function() {
+ target.removeListener('message', listener);
+ actual.push(2);
+ })
+ });
+
+ target.removeListener('message'); // must do nothing.
+ emit(target, 'message');
+ assert.deepEqual([ 1 ], actual, 'first listener called');
+ emit(target, 'message');
+ assert.deepEqual([ 1, 1, 2 ], actual, 'second listener called');
+ emit(target, 'message');
+ assert.deepEqual([ 1, 1, 2, 2, 2 ], actual, 'first listener removed');
+};
+
+exports['test error handling'] = function(assert) {
+ let target = EventTarget();
+ let error = Error('boom!');
+
+ target.on('message', function() { throw error; })
+ target.on('error', function(boom) {
+ assert.equal(boom, error, 'thrown exception causes error event');
+ });
+ emit(target, 'message');
+};
+
+exports['test unhandled errors'] = function(assert) {
+ let exceptions = [];
+ let loader = Loader(module);
+ let { emit } = loader.require('sdk/event/core');
+ let { EventTarget } = loader.require('sdk/event/target');
+ Object.defineProperties(loader.sandbox('sdk/event/core'), {
+ console: { value: {
+ exception: function(e) {
+ exceptions.push(e);
+ }
+ }}
+ });
+ let target = EventTarget();
+ let boom = Error('Boom!');
+ let drax = Error('Draax!!');
+
+ target.on('message', function() { throw boom; });
+
+ emit(target, 'message');
+ assert.ok(~String(exceptions[0]).indexOf('Boom!'),
+ 'unhandled exception is logged');
+
+ target.on('error', function() { throw drax; });
+ emit(target, 'message');
+ assert.ok(~String(exceptions[1]).indexOf('Draax!'),
+ 'error in error handler is logged');
+};
+
+require('test').run(exports);
+
diff --git a/tools/addon-sdk-1.12/test/test-events.js b/tools/addon-sdk-1.12/test/test-events.js
new file mode 100644
index 0000000..a1f87bf
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-events.js
@@ -0,0 +1,267 @@
+/* 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';
+
+// Exposing private methods as public in order to test
+const EventEmitter = require('sdk/deprecated/events').EventEmitter.compose({
+ listeners: function(type) this._listeners(type),
+ emit: function() this._emit.apply(this, arguments),
+ emitOnObject: function() this._emitOnObject.apply(this, arguments),
+ removeAllListeners: function(type) this._removeAllListeners(type)
+});
+
+exports['test:add listeners'] = function(test) {
+ let e = new EventEmitter();
+
+ let events_new_listener_emited = [];
+ let times_hello_emited = 0;
+
+ e.on("newListener", function (event, listener) {
+ events_new_listener_emited.push(event)
+ })
+
+ e.on("hello", function (a, b) {
+ times_hello_emited += 1
+ test.assertEqual("a", a)
+ test.assertEqual("b", b)
+ test.assertEqual(this, e, '`this` pseudo-variable is bound to instance');
+ })
+
+ e.emit("hello", "a", "b")
+};
+
+exports['test:removeListener'] = function(test) {
+ let count = 0;
+
+ function listener1 () {
+ count++;
+ }
+ function listener2 () {
+ count++;
+ }
+
+ // test adding and removing listener
+ let e1 = new EventEmitter();
+ test.assertEqual(0, e1.listeners('hello').length);
+ e1.on("hello", listener1);
+ test.assertEqual(1, e1.listeners('hello').length);
+ test.assertEqual(listener1, e1.listeners('hello')[0]);
+ e1.removeListener("hello", listener1);
+ test.assertEqual(0, e1.listeners('hello').length);
+ e1.emit("hello", "");
+ test.assertEqual(0, count);
+
+ // test adding one listener and removing another which was not added
+ let e2 = new EventEmitter();
+ test.assertEqual(0, e2.listeners('hello').length);
+ e2.on("hello", listener1);
+ test.assertEqual(1, e2.listeners('hello').length);
+ e2.removeListener("hello", listener2);
+ test.assertEqual(1, e2.listeners('hello').length);
+ test.assertEqual(listener1, e2.listeners('hello')[0]);
+ e2.emit("hello", "");
+ test.assertEqual(1, count);
+
+ // test adding 2 listeners, and removing one
+ let e3 = new EventEmitter();
+ test.assertEqual(0, e3.listeners('hello').length);
+ e3.on("hello", listener1);
+ test.assertEqual(1, e3.listeners('hello').length);
+ e3.on("hello", listener2);
+ test.assertEqual(2, e3.listeners('hello').length);
+ e3.removeListener("hello", listener1);
+ test.assertEqual(1, e3.listeners('hello').length);
+ test.assertEqual(listener2, e3.listeners('hello')[0]);
+ e3.emit("hello", "");
+ test.assertEqual(2, count);
+};
+
+exports['test:removeAllListeners'] = function(test) {
+ let count = 0;
+
+ function listener1 () {
+ count++;
+ }
+ function listener2 () {
+ count++;
+ }
+
+ // test adding a listener and removing all of that type
+ let e1 = new EventEmitter();
+ e1.on("hello", listener1);
+ test.assertEqual(1, e1.listeners('hello').length);
+ e1.removeAllListeners("hello");
+ test.assertEqual(0, e1.listeners('hello').length);
+ e1.emit("hello", "");
+ test.assertEqual(0, count);
+
+ // test adding a listener and removing all of another type
+ let e2 = new EventEmitter();
+ e2.on("hello", listener1);
+ test.assertEqual(1, e2.listeners('hello').length);
+ e2.removeAllListeners('goodbye');
+ test.assertEqual(1, e2.listeners('hello').length);
+ e2.emit("hello", "");
+ test.assertEqual(1, count);
+
+ // test adding 1+ listeners and removing all of that type
+ let e3 = new EventEmitter();
+ e3.on("hello", listener1);
+ test.assertEqual(1, e3.listeners('hello').length);
+ e3.on("hello", listener2);
+ test.assertEqual(2, e3.listeners('hello').length);
+ e3.removeAllListeners("hello");
+ test.assertEqual(0, e3.listeners('hello').length);
+ e3.emit("hello", "");
+ test.assertEqual(1, count);
+
+ // test adding 2 listeners for 2 types and removing all listeners
+ let e4 = new EventEmitter();
+ e4.on("hello", listener1);
+ test.assertEqual(1, e4.listeners('hello').length);
+ e4.on('goodbye', listener2);
+ test.assertEqual(1, e4.listeners('goodbye').length);
+ e4.emit("goodbye", "");
+ e4.removeAllListeners();
+ test.assertEqual(0, e4.listeners('hello').length);
+ test.assertEqual(0, e4.listeners('goodbye').length);
+ e4.emit("hello", "");
+ e4.emit("goodbye", "");
+ test.assertEqual(2, count);
+};
+
+exports['test: modify in emit'] = function(test) {
+ let callbacks_called = [ ];
+ let e = new EventEmitter();
+
+ function callback1() {
+ callbacks_called.push("callback1");
+ e.on("foo", callback2);
+ e.on("foo", callback3);
+ e.removeListener("foo", callback1);
+ }
+ function callback2() {
+ callbacks_called.push("callback2");
+ e.removeListener("foo", callback2);
+ }
+ function callback3() {
+ callbacks_called.push("callback3");
+ e.removeListener("foo", callback3);
+ }
+
+ e.on("foo", callback1);
+ test.assertEqual(1, e.listeners("foo").length);
+
+ e.emit("foo");
+ test.assertEqual(2, e.listeners("foo").length);
+ test.assertEqual(1, callbacks_called.length);
+ test.assertEqual('callback1', callbacks_called[0]);
+
+ e.emit("foo");
+ test.assertEqual(0, e.listeners("foo").length);
+ test.assertEqual(3, callbacks_called.length);
+ test.assertEqual('callback1', callbacks_called[0]);
+ test.assertEqual('callback2', callbacks_called[1]);
+ test.assertEqual('callback3', callbacks_called[2]);
+
+ e.emit("foo");
+ test.assertEqual(0, e.listeners("foo").length);
+ test.assertEqual(3, callbacks_called.length);
+ test.assertEqual('callback1', callbacks_called[0]);
+ test.assertEqual('callback2', callbacks_called[1]);
+ test.assertEqual('callback3', callbacks_called[2]);
+
+ e.on("foo", callback1);
+ e.on("foo", callback2);
+ test.assertEqual(2, e.listeners("foo").length);
+ e.removeAllListeners("foo");
+ test.assertEqual(0, e.listeners("foo").length);
+
+ // Verify that removing callbacks while in emit allows emits to propagate to
+ // all listeners
+ callbacks_called = [ ];
+
+ e.on("foo", callback2);
+ e.on("foo", callback3);
+ test.assertEqual(2, e.listeners("foo").length);
+ e.emit("foo");
+ test.assertEqual(2, callbacks_called.length);
+ test.assertEqual('callback2', callbacks_called[0]);
+ test.assertEqual('callback3', callbacks_called[1]);
+ test.assertEqual(0, e.listeners("foo").length);
+};
+
+exports['test:adding same listener'] = function(test) {
+ function foo() {}
+ let e = new EventEmitter();
+ e.on("foo", foo);
+ e.on("foo", foo);
+ test.assertEqual(
+ 1,
+ e.listeners("foo").length,
+ "listener reregistration is ignored"
+ );
+}
+
+exports['test:errors are reported if listener throws'] = function(test) {
+ let e = new EventEmitter(),
+ reported = false;
+ e.on('error', function(e) reported = true)
+ e.on('boom', function() { throw new Error('Boom!') });
+ e.emit('boom', 3);
+ test.assert(reported, 'error should be reported through event');
+};
+
+exports['test:emitOnObject'] = function(test) {
+ let e = new EventEmitter();
+
+ e.on("foo", function() {
+ test.assertEqual(this, e, "`this` should be emitter");
+ });
+ e.emitOnObject(e, "foo");
+
+ e.on("bar", function() {
+ test.assertEqual(this, obj, "`this` should be other object");
+ });
+ let obj = {};
+ e.emitOnObject(obj, "bar");
+};
+
+exports['test:once'] = function(test) {
+ let e = new EventEmitter();
+ let called = false;
+
+ e.once('foo', function(value) {
+ test.assert(!called, "listener called only once");
+ test.assertEqual(value, "bar", "correct argument was passed");
+ });
+
+ e.emit('foo', 'bar');
+ e.emit('foo', 'baz');
+};
+
+exports["test:removing once"] = function(test) {
+ let e = require("sdk/deprecated/events").EventEmitterTrait.create();
+ e.once("foo", function() { test.pass("listener was called"); });
+ e.once("error", function() { test.fail("error event was emitted"); });
+ e._emit("foo", "bug-656684");
+};
+
+// Bug 726967: Ensure that `emit` doesn't do an infinite loop when `error`
+// listener throws an exception
+exports['test:emitLoop'] = function(test) {
+ let e = new EventEmitter();
+
+ e.on("foo", function() {
+ throw new Error("foo");
+ });
+
+ e.on("error", function() {
+ throw new Error("error");
+ });
+ e.emit("foo");
+
+ test.pass("emit didn't looped");
+};
diff --git a/tools/addon-sdk-1.12/test/test-file.js b/tools/addon-sdk-1.12/test/test-file.js
new file mode 100644
index 0000000..33344b2
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-file.js
@@ -0,0 +1,273 @@
+/* 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 { pathFor } = require('sdk/system');
+const file = require("sdk/io/file");
+const url = require("sdk/url");
+
+const byteStreams = require("sdk/io/byte-streams");
+const textStreams = require("sdk/io/text-streams");
+
+const ERRORS = {
+ FILE_NOT_FOUND: /^path does not exist: .+$/,
+ NOT_A_DIRECTORY: /^path is not a directory: .+$/,
+ NOT_A_FILE: /^path is not a file: .+$/,
+};
+
+// Use profile directory to list / read / write files.
+const profilePath = pathFor('ProfD');
+const fileNameInProfile = 'compatibility.ini';
+const dirNameInProfile = 'extensions';
+const filePathInProfile = file.join(profilePath, fileNameInProfile);
+const dirPathInProfile = file.join(profilePath, dirNameInProfile);
+
+exports.testDirName = function(test) {
+ test.assertEqual(file.dirname(dirPathInProfile), profilePath,
+ "file.dirname() of dir should return parent dir");
+
+ test.assertEqual(file.dirname(filePathInProfile), profilePath,
+ "file.dirname() of file should return its dir");
+
+ let dir = profilePath;
+ while (dir)
+ dir = file.dirname(dir);
+
+ test.assertEqual(dir, "",
+ "dirname should return empty string when dir has no parent");
+};
+
+exports.testBasename = function(test) {
+ // Get the top-most path -- the path with no basename. E.g., on Unix-like
+ // systems this will be /. We'll use it below to build up some test paths.
+ // We have to go to this trouble because file.join() needs a legal path as a
+ // base case; join("foo", "bar") doesn't work unfortunately.
+ let topPath = profilePath;
+ let parentPath = file.dirname(topPath);
+ while (parentPath) {
+ topPath = parentPath;
+ parentPath = file.dirname(topPath);
+ }
+
+ let path = topPath;
+ test.assertEqual(file.basename(path), "",
+ "basename should work on paths with no components");
+
+ path = file.join(path, "foo");
+ test.assertEqual(file.basename(path), "foo",
+ "basename should work on paths with a single component");
+
+ path = file.join(path, "bar");
+ test.assertEqual(file.basename(path), "bar",
+ "basename should work on paths with multiple components");
+};
+
+exports.testList = function(test) {
+ let list = file.list(profilePath);
+ let found = [ true for each (name in list)
+ if (name === fileNameInProfile) ];
+
+ if (found.length > 1)
+ test.fail("a dir can't contain two files of the same name!");
+ test.assertEqual(found[0], true, "file.list() should work");
+
+ test.assertRaises(function() {
+ file.list(filePathInProfile);
+ }, ERRORS.NOT_A_DIRECTORY, "file.list() on non-dir should raise error");
+
+ test.assertRaises(function() {
+ file.list(file.join(dirPathInProfile, "does-not-exist"));
+ }, ERRORS.FILE_NOT_FOUND, "file.list() on nonexistent should raise error");
+};
+
+exports.testRead = function(test) {
+ let contents = file.read(filePathInProfile);
+ test.assertMatches(contents, /Compatibility/,
+ "file.read() should work");
+
+ test.assertRaises(function() {
+ file.read(file.join(dirPathInProfile, "does-not-exists"));
+ }, ERRORS.FILE_NOT_FOUND, "file.read() on nonexistent file should throw");
+
+ test.assertRaises(function() {
+ file.read(dirPathInProfile);
+ }, ERRORS.NOT_A_FILE, "file.read() on dir should throw");
+};
+
+exports.testJoin = function(test) {
+ let baseDir = file.dirname(filePathInProfile);
+
+ test.assertEqual(file.join(baseDir, fileNameInProfile),
+ filePathInProfile, "file.join() should work");
+};
+
+exports.testOpenNonexistentForRead = function (test) {
+ var filename = file.join(profilePath, 'does-not-exists');
+ test.assertRaises(function() {
+ file.open(filename);
+ }, ERRORS.FILE_NOT_FOUND, "file.open() on nonexistent file should throw");
+
+ test.assertRaises(function() {
+ file.open(filename, "r");
+ }, ERRORS.FILE_NOT_FOUND, "file.open('r') on nonexistent file should throw");
+
+ test.assertRaises(function() {
+ file.open(filename, "zz");
+ }, ERRORS.FILE_NOT_FOUND, "file.open('zz') on nonexistent file should throw");
+};
+
+exports.testOpenNonexistentForWrite = function (test) {
+ let filename = file.join(profilePath, 'open.txt');
+
+ let stream = file.open(filename, "w");
+ stream.close();
+
+ test.assert(file.exists(filename),
+ "file.exists() should return true after file.open('w')");
+ file.remove(filename);
+ test.assert(!file.exists(filename),
+ "file.exists() should return false after file.remove()");
+
+ stream = file.open(filename, "rw");
+ stream.close();
+
+ test.assert(file.exists(filename),
+ "file.exists() should return true after file.open('rw')");
+ file.remove(filename);
+ test.assert(!file.exists(filename),
+ "file.exists() should return false after file.remove()");
+};
+
+exports.testOpenDirectory = function (test) {
+ let dir = dirPathInProfile;
+ test.assertRaises(function() {
+ file.open(dir);
+ }, ERRORS.NOT_A_FILE, "file.open() on directory should throw");
+
+ test.assertRaises(function() {
+ file.open(dir, "w");
+ }, ERRORS.NOT_A_FILE, "file.open('w') on directory should throw");
+};
+
+exports.testOpenTypes = function (test) {
+ let filename = file.join(profilePath, 'open-types.txt');
+
+
+ // Do the opens first to create the data file.
+ var stream = file.open(filename, "w");
+ test.assert(stream instanceof textStreams.TextWriter,
+ "open(w) should return a TextWriter");
+ stream.close();
+
+ stream = file.open(filename, "wb");
+ test.assert(stream instanceof byteStreams.ByteWriter,
+ "open(wb) should return a ByteWriter");
+ stream.close();
+
+ stream = file.open(filename);
+ test.assert(stream instanceof textStreams.TextReader,
+ "open() should return a TextReader");
+ stream.close();
+
+ stream = file.open(filename, "r");
+ test.assert(stream instanceof textStreams.TextReader,
+ "open(r) should return a TextReader");
+ stream.close();
+
+ stream = file.open(filename, "b");
+ test.assert(stream instanceof byteStreams.ByteReader,
+ "open(b) should return a ByteReader");
+ stream.close();
+
+ stream = file.open(filename, "rb");
+ test.assert(stream instanceof byteStreams.ByteReader,
+ "open(rb) should return a ByteReader");
+ stream.close();
+
+ file.remove(filename);
+};
+
+exports.testMkpathRmdir = function (test) {
+ let basePath = profilePath;
+ let dirs = [];
+ for (let i = 0; i < 3; i++)
+ dirs.push("test-file-dir");
+
+ let paths = [];
+ for (let i = 0; i < dirs.length; i++) {
+ let args = [basePath].concat(dirs.slice(0, i + 1));
+ paths.unshift(file.join.apply(null, args));
+ }
+
+ for (let i = 0; i < paths.length; i++) {
+ test.assert(!file.exists(paths[i]),
+ "Sanity check: path should not exist: " + paths[i]);
+ }
+
+ file.mkpath(paths[0]);
+ test.assert(file.exists(paths[0]), "mkpath should create path: " + paths[0]);
+
+ for (let i = 0; i < paths.length; i++) {
+ file.rmdir(paths[i]);
+ test.assert(!file.exists(paths[i]),
+ "rmdir should remove path: " + paths[i]);
+ }
+};
+
+exports.testMkpathTwice = function (test) {
+ let dir = profilePath;
+ let path = file.join(dir, "test-file-dir");
+ test.assert(!file.exists(path),
+ "Sanity check: path should not exist: " + path);
+ file.mkpath(path);
+ test.assert(file.exists(path), "mkpath should create path: " + path);
+ file.mkpath(path);
+ test.assert(file.exists(path),
+ "After second mkpath, path should still exist: " + path);
+ file.rmdir(path);
+ test.assert(!file.exists(path), "rmdir should remove path: " + path);
+};
+
+exports.testMkpathExistingNondirectory = function (test) {
+ var fname = file.join(profilePath, 'conflict.txt');
+ file.open(fname, "w").close();
+ test.assert(file.exists(fname), "File should exist");
+ test.assertRaises(function() file.mkpath(fname),
+ /^The path already exists and is not a directory: .+$/,
+ "mkpath on file should raise error");
+ file.remove(fname);
+};
+
+exports.testRmdirNondirectory = function (test) {
+ var fname = file.join(profilePath, 'not-a-dir')
+ file.open(fname, "w").close();
+ test.assert(file.exists(fname), "File should exist");
+ test.assertRaises(function() {
+ file.rmdir(fname);
+ }, ERRORS.NOT_A_DIRECTORY, "rmdir on file should raise error");
+ file.remove(fname);
+ test.assert(!file.exists(fname), "File should not exist");
+ test.assertRaises(function () file.rmdir(fname),
+ ERRORS.FILE_NOT_FOUND,
+ "rmdir on non-existing file should raise error");
+};
+
+exports.testRmdirNonempty = function (test) {
+ let dir = profilePath;
+ let path = file.join(dir, "test-file-dir");
+ test.assert(!file.exists(path),
+ "Sanity check: path should not exist: " + path);
+ file.mkpath(path);
+ let filePath = file.join(path, "file");
+ file.open(filePath, "w").close();
+ test.assert(file.exists(filePath),
+ "Sanity check: path should exist: " + filePath);
+ test.assertRaises(function () file.rmdir(path),
+ /^The directory is not empty: .+$/,
+ "rmdir on non-empty directory should raise error");
+ file.remove(filePath);
+ file.rmdir(path);
+ test.assert(!file.exists(path), "Path should not exist");
+};
diff --git a/tools/addon-sdk-1.12/test/test-frame-utils.js b/tools/addon-sdk-1.12/test/test-frame-utils.js
new file mode 100644
index 0000000..6225f00
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-frame-utils.js
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+const { open } = require('sdk/window/utils');
+const { create } = require('sdk/frame/utils');
+
+exports['test frame creation'] = function(assert, done) {
+ let window = open('data:text/html;charset=utf-8,Window');
+ window.addEventListener('DOMContentLoaded', function windowReady() {
+
+ let frame = create(window.document);
+
+ assert.equal(frame.getAttribute('type'), 'content',
+ 'frame type is content');
+ assert.ok(frame.contentWindow, 'frame has contentWindow');
+ assert.equal(frame.contentWindow.location.href, 'about:blank',
+ 'by default "about:blank" is loaded');
+ assert.equal(frame.docShell.allowAuth, false, 'auth disabled by default');
+ assert.equal(frame.docShell.allowJavascript, false, 'js disabled by default');
+ assert.equal(frame.docShell.allowPlugins, false,
+ 'plugins disabled by default');
+ window.close();
+ done();
+ }, false);
+};
+
+exports['test fram has js disabled by default'] = function(assert, done) {
+ let window = open('data:text/html;charset=utf-8,window');
+ window.addEventListener('DOMContentLoaded', function windowReady() {
+ window.removeEventListener('DOMContentLoaded', windowReady, false);
+ let frame = create(window.document, {
+ uri: 'data:text/html;charset=utf-8,<script>document.documentElement.innerHTML' +
+ '= "J" + "S"</script>',
+ });
+ frame.contentWindow.addEventListener('DOMContentLoaded', function ready() {
+ frame.contentWindow.removeEventListener('DOMContentLoaded', ready, false);
+ assert.ok(!~frame.contentDocument.documentElement.innerHTML.indexOf('JS'),
+ 'JS was executed');
+
+ window.close();
+ done();
+ }, false);
+
+ }, false);
+};
+
+exports['test frame with js enabled'] = function(assert, done) {
+ let window = open('data:text/html;charset=utf-8,window');
+ window.addEventListener('DOMContentLoaded', function windowReady() {
+ window.removeEventListener('DOMContentLoaded', windowReady, false);
+ let frame = create(window.document, {
+ uri: 'data:text/html;charset=utf-8,<script>document.documentElement.innerHTML' +
+ '= "J" + "S"</script>',
+ allowJavascript: true
+ });
+ frame.contentWindow.addEventListener('DOMContentLoaded', function ready() {
+ frame.contentWindow.removeEventListener('DOMContentLoaded', ready, false);
+ assert.ok(~frame.contentDocument.documentElement.innerHTML.indexOf('JS'),
+ 'JS was executed');
+
+ window.close();
+ done();
+ }, false);
+
+ }, false);
+};
+
+require('test').run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-functional.js b/tools/addon-sdk-1.12/test/test-functional.js
new file mode 100644
index 0000000..81d36e9
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-functional.js
@@ -0,0 +1,170 @@
+/* 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 { setTimeout } = require('sdk/timers');
+const utils = require('sdk/lang/functional');
+const { invoke, defer, curry, compose, memoize, once, delay, wrap } = utils;
+
+exports['test forwardApply'] = function(assert) {
+ function sum(b, c) this.a + b + c
+ assert.equal(invoke(sum, [2, 3], { a: 1 }), 6,
+ 'passed arguments and pseoude-variable are used');
+
+ assert.equal(invoke(sum.bind({ a: 2 }), [2, 3], { a: 1 }), 7,
+ 'bounded `this` pseoudo variable is used');
+}
+
+exports['test deferred function'] = function(assert, done) {
+ let nextTurn = false;
+ function sum(b, c) {
+ assert.ok(nextTurn, 'enqueued is called in next turn of event loop');
+ assert.equal(this.a + b + c, 6,
+ 'passed arguments an pseoude-variable are used');
+ done();
+ }
+
+ let fixture = { a: 1, method: defer(sum) }
+ fixture.method(2, 3);
+ nextTurn = true;
+};
+
+exports['test curry function'] = function(assert) {
+ function sum(b, c) this.a + b + c;
+
+ let foo = { a : 5 };
+
+ foo.sum7 = curry(sum, 7);
+ foo.sum8and4 = curry(sum, 8, 4);
+
+ assert.equal(foo.sum7(2), 14, 'curry one arguments works');
+
+ assert.equal(foo.sum8and4(), 17, 'curry both arguments works');
+};
+
+exports['test compose'] = function(assert) {
+ let greet = function(name) { return 'hi: ' + name; };
+ let exclaim = function(sentence) { return sentence + '!'; };
+
+ assert.equal(compose(exclaim, greet)('moe'), 'hi: moe!',
+ 'can compose a function that takes another');
+
+ assert.equal(compose(greet, exclaim)('moe'), 'hi: moe!',
+ 'in this case, the functions are also commutative');
+
+ let target = {
+ name: 'Joe',
+ greet: compose(function exclaim(sentence) {
+ return sentence + '!'
+ }, function(title) {
+ return 'hi : ' + title + ' ' + this.name;
+ })
+ }
+
+ assert.equal(target.greet('Mr'), 'hi : Mr Joe!',
+ 'this can be passed in');
+ assert.equal(target.greet.call({ name: 'Alex' }, 'Dr'), 'hi : Dr Alex!',
+ 'this can be applied');
+
+ let single = compose(function(value) {
+ return value + ':suffix';
+ });
+
+ assert.equal(single('text'), 'text:suffix', 'works with single function');
+
+ let identity = compose();
+ assert.equal(identity('bla'), 'bla', 'works with zero functions');
+};
+
+exports['test wrap'] = function(assert) {
+ let greet = function(name) { return 'hi: ' + name; };
+ let backwards = wrap(greet, function(f, name) {
+ return f(name) + ' ' + name.split('').reverse().join('');
+ });
+
+ assert.equal(backwards('moe'), 'hi: moe eom',
+ 'wrapped the saluation function');
+
+ let inner = function () { return 'Hello '; };
+ let target = {
+ name: 'Matteo',
+ hi: wrap(inner, function(f) { return f() + this.name; })
+ };
+
+ assert.equal(target.hi(), 'Hello Matteo', 'works with this');
+
+ function noop() { };
+ let wrapped = wrap(noop, function(f) {
+ return Array.slice(arguments);
+ });
+
+ let actual = wrapped([ 'whats', 'your' ], 'vector', 'victor');
+ assert.deepEqual(actual, [ noop, ['whats', 'your'], 'vector', 'victor' ],
+ 'works with fancy stuff');
+};
+
+exports['test memoize'] = function(assert) {
+ function fib(n) n < 2 ? n : fib(n - 1) + fib(n - 2)
+ let fibnitro = memoize(fib);
+
+ assert.equal(fib(10), 55,
+ 'a memoized version of fibonacci produces identical results');
+ assert.equal(fibnitro(10), 55,
+ 'a memoized version of fibonacci produces identical results');
+
+ function o(key, value) { return value; };
+ let oo = memoize(o), v1 = {}, v2 = {};
+
+
+ assert.equal(oo(1, v1), v1, 'returns value back');
+ assert.equal(oo(1, v2), v1, 'memoized by a first argument');
+ assert.equal(oo(2, v2), v2, 'returns back value if not memoized');
+ assert.equal(oo(2), v2, 'memoized new value');
+ assert.notEqual(oo(1), oo(2), 'values do not override');
+ assert.equal(o(3, v2), oo(2, 3), 'returns same value as un-memoized');
+
+ let get = memoize(function(attribute) this[attribute])
+ let target = { name: 'Bob', get: get }
+
+ assert.equal(target.get('name'), 'Bob', 'has correct `this`');
+ assert.equal(target.get.call({ name: 'Jack' }, 'name'), 'Bob',
+ 'name is memoized')
+ assert.equal(get('name'), 'Bob', 'once memoized can be called without this');
+};
+
+exports['test delay'] = function(assert, done) {
+ let delayed = false;
+ delay(function() {
+ assert.ok(delayed, 'delayed the function');
+ done();
+ }, 1);
+ delayed = true;
+};
+
+exports['test delay with this'] = function(assert, done) {
+ let context = {}
+ delay.call(context, function(name) {
+ assert.equal(this, context, 'this was passed in');
+ assert.equal(name, 'Tom', 'argument was passed in');
+ done();
+ }, 10, 'Tom');
+}
+
+exports['test once'] = function(assert) {
+ let n = 0;
+ let increment = once(function() { n ++; });
+
+ increment();
+ increment();
+
+ assert.equal(n, 1, 'only incremented once');
+
+ let target = { state: 0, update: once(function() this.state ++ ) };
+
+ target.update();
+ target.update();
+
+ assert.equal(target.state, 1, 'this was passed in and called only once');
+};
+
+require('test').run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-globals.js b/tools/addon-sdk-1.12/test/test-globals.js
new file mode 100644
index 0000000..1d8caf0
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-globals.js
@@ -0,0 +1,22 @@
+/* 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/. */
+
+Object.defineProperty(this, "global", { value: this });
+
+exports.testGlobals = function(test) {
+ // the only globals in module scope should be:
+ // module, exports, require, dump, console
+ test.assertObject(module, "have 'module', good");
+ test.assertObject(exports, "have 'exports', good");
+ test.assertFunction(require, "have 'require', good");
+ test.assertFunction(dump, "have 'dump', good");
+ test.assertObject(console, "have 'console', good");
+
+ // in particular, these old globals should no longer be present
+ test.assert(!('packaging' in global), "no 'packaging', good");
+ test.assert(!('memory' in global), "no 'memory', good");
+
+ test.assertMatches(module.uri, /test-globals\.js$/,
+ 'should contain filename');
+};
diff --git a/tools/addon-sdk-1.12/test/test-heritage.js b/tools/addon-sdk-1.12/test/test-heritage.js
new file mode 100644
index 0000000..493ffdd
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-heritage.js
@@ -0,0 +1,375 @@
+/* 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 { Class, extend, mix, obscure } = require('sdk/core/heritage');
+
+exports['test extend'] = function(assert) {
+ let ancestor = { a: 1 };
+ let descendant = extend(ancestor, {
+ b: 2,
+ get c() { return 3 },
+ d: function() { return 4 }
+ });
+
+ assert.ok(ancestor.isPrototypeOf(descendant),
+ 'descendant inherits from ancestor');
+ assert.ok(descendant.b, 2, 'proprety was implemented');
+ assert.ok(descendant.c, 3, 'getter was implemented');
+ assert.ok(descendant.d(), 4, 'method was implemented');
+
+ /* Will be fixed once Bug 674195 is shipped.
+ assert.ok(Object.isFrozen(descendant),
+ 'extend returns frozen objects');
+ */
+};
+
+exports['test mix'] = function(assert) {
+ let ancestor = { a: 1 }
+ let mixed = mix(extend(ancestor, { b: 1, c: 1 }), { c: 2 }, { d: 3 });
+
+ assert.deepEqual(JSON.parse(JSON.stringify(mixed)), { b: 1, c: 2, d: 3 },
+ 'properties mixed as expected');
+ assert.ok(ancestor.isPrototypeOf(mixed),
+ 'first arguments ancestor is ancestor of result');
+};
+
+exports['test obscure'] = function(assert) {
+ let fixture = mix({ a: 1 }, obscure({ b: 2 }));
+
+ assert.equal(fixture.a, 1, 'a property is included');
+ assert.equal(fixture.b, 2, 'b proprety is included');
+ assert.ok(!Object.getOwnPropertyDescriptor(fixture, 'b').enumerable,
+ 'obscured properties are non-enumerable');
+};
+
+exports['test inheritance'] = function(assert) {
+ let Ancestor = Class({
+ name: 'ancestor',
+ method: function () {
+ return 'hello ' + this.name;
+ }
+ });
+
+ assert.ok(Ancestor() instanceof Ancestor,
+ 'can be instantiated without new');
+ assert.ok(new Ancestor() instanceof Ancestor,
+ 'can also be instantiated with new');
+ assert.ok(Ancestor() instanceof Class,
+ 'if ancestor not specified than defaults to Class');
+ assert.ok(Ancestor.prototype.extends, Class.prototype,
+ 'extends of prototype points to ancestors prototype');
+
+
+ assert.equal(Ancestor().method(), 'hello ancestor',
+ 'instance inherits defined properties');
+
+ let Descendant = Class({
+ extends: Ancestor,
+ name: 'descendant'
+ });
+
+ assert.ok(Descendant() instanceof Descendant,
+ 'instantiates correctly');
+ assert.ok(Descendant() instanceof Ancestor,
+ 'Inherits for passed `extends`');
+ assert.equal(Descendant().method(), 'hello descendant',
+ 'propreties inherited');
+};
+
+exports['test prototype immutability'] = function(assert) {
+ let Foo = Class({
+ name: 'hello',
+ rename: function rename(name) {
+ this.name = name;
+ }
+ });
+
+ /* Disable until release with Bug 674195 fix is shipped
+ assert.ok(Object.isFrozen(Foo), 'Foo is frozen');
+ assert.ok(Object.isFrozen(Foo.prototype), 'Foo prototype is frozen');
+ assert.ok(Object.isFrozen(Object.getPrototypeOf(Foo.prototype)),
+ 'Class.prototype is frozen');
+ assert.equal(Object.getPrototypeOf(Object.getPrototypeOf(Foo.prototype)),
+ null, 'prototype of Class.prototype is null');
+ */
+
+ assert.throws(function() {
+ var override = function() {};
+ Foo.prototype.extend = override;
+ if (Foo.prototype.extend !== override)
+ throw Error('Property was not set');
+ }, 'Can not change prototype properties');
+
+ assert.throws(function() {
+ Foo.prototype.foo = 'bar';
+ if (Foo.prototype.foo !== 'bar')
+ throw Error('Property was not set');
+ }, 'Can not add prototype properties');
+
+ assert.throws(function() {
+ delete Foo.prototype.name;
+ if ('name' in Foo.prototype)
+ throw Error('Property was not deleted');
+ }, 'Can not remove prototype properties');
+
+ var Bar = Class({
+ extends: Foo,
+ rename: function rename() {
+ return this.name;
+ }
+ });
+
+ assert.equal(Bar().rename(), 'hello',
+ 'properties may be overided on decedents');
+};
+
+exports['test immunity against __proto__'] = function(assert) {
+ let Foo = Class({ name: 'foo', hacked: false });
+
+ let Bar = Class({ extends: Foo, name: 'bar' });
+
+ assert.throws(function() {
+ Foo.prototype.__proto__ = { hacked: true };
+ if (Foo() instanceof Base && !Foo().hacked)
+ throw Error('can not change prototype chain');
+ }, 'prototype chain is immune to __proto__ hacks');
+
+ assert.throws(function() {
+ Foo.prototype.__proto__ = { hacked: true };
+ if (Bar() instanceof Foo && !Bar().hacked)
+ throw Error('can not change prototype chain');
+ }, 'prototype chain of decedants immune to __proto__ hacks');
+};
+
+exports['test instance mutability'] = function(assert) {
+ let Foo = Class({
+ name: 'foo',
+ initialize: function initialize(number) {
+ this.number = number;
+ }
+ });
+
+ let f1 = Foo();
+
+ assert.throws(function() {
+ f1.name = 'f1';
+ if (f1.name !== 'f1')
+ throw Error('Property was not set');
+ }, 'can not change prototype properties');
+
+ f1.alias = 'f1';
+ assert.equal(f1.alias, 'f1', 'instance is mutable');
+
+ delete f1.alias;
+ assert.ok(!('alias' in f1), 'own properties are deletable');
+
+ f1.initialize(1);
+ assert.equal(f1.number, 1, 'method can mutate instances own properties');
+};
+
+exports['test super'] = function(assert) {
+ var Foo = Class({
+ initialize: function initialize(options) {
+ this.name = options.name;
+ }
+ });
+
+ var Bar = Class({
+ extends: Foo,
+ initialize: function Bar(options) {
+ Foo.prototype.initialize.call(this, options);
+ this.type = 'bar';
+ }
+ });
+
+ var bar = Bar({ name: 'test' });
+
+ assert.equal(bar.type, 'bar', 'bar initializer was called');
+ assert.equal(bar.name, 'test', 'bar initializer called Foo initializer');
+};
+
+exports['test initialize'] = function(assert) {
+ var Dog = Class({
+ initialize: function initialize(name) {
+ this.name = name;
+ },
+ type: 'dog',
+ bark: function bark() {
+ return 'Ruff! Ruff!'
+ }
+ });
+
+ var fluffy = Dog('Fluffy'); // instatiation
+ assert.ok(fluffy instanceof Dog,
+ 'instanceof works as expected');
+ assert.ok(fluffy instanceof Class,
+ 'inherits form Class if not specified otherwise');
+ assert.ok(fluffy.name, 'fluffy',
+ 'initialize unless specified otherwise');
+};
+
+exports['test complements regular inheritace'] = function(assert) {
+ let Base = Class({ name: 'base' });
+
+ function Type() {
+ // ...
+ }
+ Type.prototype = Object.create(Base.prototype);
+ Type.prototype.run = function() {
+ // ...
+ };
+
+ let value = new Type();
+
+ assert.ok(value instanceof Type, 'creates instance of Type');
+ assert.ok(value instanceof Base, 'inherits from Base');
+ assert.equal(value.name, 'base', 'inherits properties from Base');
+
+
+ let SubType = Class({
+ extends: Type,
+ sub: 'type'
+ });
+
+ let fixture = SubType();
+
+ assert.ok(fixture instanceof Base, 'is instance of Base');
+ assert.ok(fixture instanceof Type, 'is instance of Type');
+ assert.ok(fixture instanceof SubType, 'is instance of SubType');
+
+ assert.equal(fixture.sub, 'type', 'proprety is defined');
+ assert.equal(fixture.run, Type.prototype.run, 'proprety is inherited');
+ assert.equal(fixture.name, 'base', 'inherits base properties');
+};
+
+exports['test extends object'] = function(assert) {
+ let prototype = { constructor: function() { return this; }, name: 'me' };
+ let Foo = Class({
+ extends: prototype,
+ value: 2
+ });
+ let foo = new Foo();
+
+ assert.ok(foo instanceof Foo, 'instance of Foo');
+ assert.ok(!(foo instanceof Class), 'is not instance of Class');
+ assert.ok(prototype.isPrototypeOf(foo), 'inherits from given prototype');
+ assert.equal(Object.getPrototypeOf(Foo.prototype), prototype,
+ 'contsructor prototype inherits from extends option');
+ assert.equal(foo.value, 2, 'property is defined');
+ assert.equal(foo.name, 'me', 'prototype proprety is inherited');
+};
+
+
+var HEX = Class({
+ hex: function hex() {
+ return '#' + this.color;
+ }
+});
+
+var RGB = Class({
+ red: function red() {
+ return parseInt(this.color.substr(0, 2), 16);
+ },
+ green: function green() {
+ return parseInt(this.color.substr(2, 2), 16);
+ },
+ blue: function blue() {
+ return parseInt(this.color.substr(4, 2), 16);
+ }
+});
+
+var CMYK = Class({
+ black: function black() {
+ var color = Math.max(Math.max(this.red(), this.green()), this.blue());
+ return (1 - color / 255).toFixed(4);
+ },
+ magenta: function magenta() {
+ var K = this.black();
+ return (((1 - this.green() / 255).toFixed(4) - K) / (1 - K)).toFixed(4);
+ },
+ yellow: function yellow() {
+ var K = this.black();
+ return (((1 - this.blue() / 255).toFixed(4) - K) / (1 - K)).toFixed(4);
+ },
+ cyan: function cyan() {
+ var K = this.black();
+ return (((1 - this.red() / 255).toFixed(4) - K) / (1 - K)).toFixed(4);
+ }
+});
+
+var Color = Class({
+ implements: [ HEX, RGB, CMYK ],
+ initialize: function initialize(color) {
+ this.color = color;
+ }
+});
+
+exports['test composition'] = function(assert) {
+ var pink = Color('FFC0CB');
+
+ assert.equal(pink.red(), 255, 'red() works');
+ assert.equal(pink.green(), 192, 'green() works');
+ assert.equal(pink.blue(), 203, 'blue() works');
+
+ assert.equal(pink.magenta(), 0.2471, 'magenta() works');
+ assert.equal(pink.yellow(), 0.2039, 'yellow() works');
+ assert.equal(pink.cyan(), 0.0000, 'cyan() works');
+
+ assert.ok(pink instanceof Color, 'is instance of Color');
+ assert.ok(pink instanceof Class, 'is instance of Class');
+};
+
+var Point = Class({
+ initialize: function initialize(x, y) {
+ this.x = x;
+ this.y = y;
+ },
+ toString: function toString() {
+ return this.x + ':' + this.y;
+ }
+})
+
+var Pixel = Class({
+ extends: Point,
+ implements: [ Color ],
+ initialize: function initialize(x, y, color) {
+ Color.prototype.initialize.call(this, color);
+ Point.prototype.initialize.call(this, x, y);
+ },
+ toString: function toString() {
+ return this.hex() + '@' + Point.prototype.toString.call(this)
+ }
+});
+
+exports['test compostion with inheritance'] = function(assert) {
+ var pixel = Pixel(11, 23, 'CC3399');
+
+ assert.equal(pixel.toString(), '#CC3399@11:23', 'stringifies correctly');
+ assert.ok(pixel instanceof Pixel, 'instance of Pixel');
+ assert.ok(pixel instanceof Point, 'instance of Point');
+};
+
+exports['test composition with objects'] = function(assert) {
+ var A = { a: 1, b: 1 };
+ var B = Class({ b: 2, c: 2 });
+ var C = { c: 3 };
+ var D = { d: 4 };
+
+ var ABCD = Class({
+ implements: [ A, B, C, D ],
+ e: 5
+ });
+
+ var f = ABCD();
+
+ assert.equal(f.a, 1, 'inherits A.a');
+ assert.equal(f.b, 2, 'inherits B.b overrides A.b');
+ assert.equal(f.c, 3, 'inherits C.c overrides B.c');
+ assert.equal(f.d, 4, 'inherits D.d');
+ assert.equal(f.e, 5, 'implements e');
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-hidden-frame.js b/tools/addon-sdk-1.12/test/test-hidden-frame.js
new file mode 100644
index 0000000..cf99df5
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-hidden-frame.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/. */
+
+let tests = {}, hiddenFrames, HiddenFrame;
+
+tests.testFrame = function(test) {
+ let url = "data:text/html;charset=utf-8,<!DOCTYPE%20html>";
+ test.waitUntilDone();
+ let hiddenFrame = hiddenFrames.add(HiddenFrame({
+ onReady: function () {
+ test.assertEqual(this.element.contentWindow.location, "about:blank",
+ "HiddenFrame loads about:blank by default.");
+
+ function onDOMReady() {
+ hiddenFrame.element.removeEventListener("DOMContentLoaded", onDOMReady,
+ false);
+ test.assertEqual(hiddenFrame.element.contentWindow.location, url,
+ "HiddenFrame loads the specified content.");
+ test.done();
+ }
+ this.element.addEventListener("DOMContentLoaded", onDOMReady, false);
+ this.element.setAttribute("src", url);
+ }
+ }));
+};
+
+let hiddenFrameSupported = true;
+
+try {
+ hiddenFrames = require("sdk/frame/hidden-frame");
+ HiddenFrame = hiddenFrames.HiddenFrame;
+}
+catch(ex if ex.message == [
+ "The hidden-frame module currently supports only Firefox and Thunderbird. ",
+ "In the future, we would like it to support other applications, however. ",
+ "Please see https://bugzilla.mozilla.org/show_bug.cgi?id=546740 for more ",
+ "information."
+ ].join("")) {
+ hiddenFrameSupported = false;
+}
+
+if (hiddenFrameSupported) {
+ for (let test in tests)
+ exports[test] = tests[test];
+}
+else {
+ exports.testHiddenFrameNotSupported = function(test) {
+ test.pass("The hidden-frame module is not supported on this app.");
+ }
+}
diff --git a/tools/addon-sdk-1.12/test/test-hotkeys.js b/tools/addon-sdk-1.12/test/test-hotkeys.js
new file mode 100644
index 0000000..7dc590f
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-hotkeys.js
@@ -0,0 +1,162 @@
+/* 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("sdk/hotkeys");
+const { keyDown } = require("sdk/dom/events/keys");
+const { Loader } = require('sdk/test/loader');
+const timer = require("sdk/timers");
+const winUtils = require("sdk/deprecated/window-utils");
+
+exports["test hotkey: function key"] = function(assert, done) {
+ var element = winUtils.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 = winUtils.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 = winUtils.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 = winUtils.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 = winUtils.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("sdk/deprecated/window-utils").activeBrowserWindow.document.documentElement;
+ var hotkey = loader.require("sdk/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");
+
+ 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.12/test/test-httpd.js b/tools/addon-sdk-1.12/test/test-httpd.js
new file mode 100644
index 0000000..1ab9522
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-httpd.js
@@ -0,0 +1,72 @@
+/* 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 port = 8099;
+const file = require("sdk/io/file");
+const { pathFor } = require("sdk/system");
+
+exports.testBasicHTTPServer = function(test) {
+ let basePath = pathFor("TmpD");
+ let filePath = file.join(basePath, 'test-httpd.txt');
+ let content = "This is the HTTPD test file.\n";
+ let fileStream = file.open(filePath, 'w');
+ fileStream.write(content);
+ fileStream.close();
+
+ let { startServerAsync } = require("sdk/test/httpd");
+ let srv = startServerAsync(port, basePath);
+
+ test.waitUntilDone();
+
+ // Request this very file.
+ let Request = require('sdk/request').Request;
+ Request({
+ url: "http://localhost:" + port + "/test-httpd.txt",
+ onComplete: function (response) {
+ test.assertEqual(response.text, content);
+ done();
+ }
+ }).get();
+
+ function done() {
+ srv.stop(function() {
+ test.done();
+ });
+ }
+};
+
+exports.testDynamicServer = function (test) {
+ let content = "This is the HTTPD test file.\n";
+
+ let { startServerAsync } = require("sdk/test/httpd");
+ let srv = startServerAsync(port);
+
+ // See documentation here:
+ //http://doxygen.db48x.net/mozilla/html/interfacensIHttpServer.html#a81fc7e7e29d82aac5ce7d56d0bedfb3a
+ //http://doxygen.db48x.net/mozilla/html/interfacensIHttpRequestHandler.html
+ srv.registerPathHandler("/test-httpd.txt", function handle(request, response) {
+ // Add text content type, only to avoid error in `Request` API
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(content);
+ });
+
+ test.waitUntilDone();
+
+ // Request this very file.
+ let Request = require('sdk/request').Request;
+ Request({
+ url: "http://localhost:" + port + "/test-httpd.txt",
+ onComplete: function (response) {
+ test.assertEqual(response.text, content);
+ done();
+ }
+ }).get();
+
+ function done() {
+ srv.stop(function() {
+ test.done();
+ });
+ }
+
+}
diff --git a/tools/addon-sdk-1.12/test/test-keyboard-observer.js b/tools/addon-sdk-1.12/test/test-keyboard-observer.js
new file mode 100644
index 0000000..091ec73
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-keyboard-observer.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";
+
+const { keyPress } = require("sdk/dom/events/keys");
+const { Loader } = require("sdk/test/loader");
+const timer = require("sdk/timers");
+
+exports["test unload keyboard observer"] = function(assert, done) {
+ let loader = Loader(module);
+ let element = loader.require("sdk/deprecated/window-utils").
+ activeBrowserWindow.document.documentElement;
+ let observer = loader.require("sdk/keyboard/observer").
+ observer;
+ let called = 0;
+
+ observer.on("keypress", function () { called++; });
+
+ // dispatching "keypress" event to trigger observer listeners.
+ keyPress(element, "accel-%");
+
+ // Unload the module.
+ loader.unload();
+
+ // dispatching "keypress" even once again.
+ keyPress(element, "accel-%");
+
+ // Enqueuing asserts to make sure that assertion is not performed early.
+ timer.setTimeout(function () {
+ assert.equal(called, 1, "observer was called before unload only.");
+ done();
+ }, 0);
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-keyboard-utils.js b/tools/addon-sdk-1.12/test/test-keyboard-utils.js
new file mode 100644
index 0000000..19981de
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-keyboard-utils.js
@@ -0,0 +1,62 @@
+/* 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 utils = require("sdk/keyboard/utils");
+const runtime = require("sdk/system/runtime");
+
+const isMac = runtime.OS === "Darwin";
+
+exports["test toString"] = function(assert) {
+ assert.equal(utils.toString({
+ key: "B",
+ modifiers: [ "Shift", "Ctrl" ]
+ }), "Shift-Ctrl-B", "toString does not normalizes JSON");
+
+ assert.equal(utils.toString({
+ key: "C",
+ modifiers: [],
+ }), "C", "Works with objects with empty array of modifiers");
+
+ assert.equal(utils.toString(Object.create((function Type() {}).prototype, {
+ key: { value: "d" },
+ modifiers: { value: [ "alt" ] },
+ method: { value: function() {} }
+ })), "alt-d", "Works with non-json objects");
+
+ assert.equal(utils.toString({
+ modifiers: [ "shift", "alt" ]
+ }), "shift-alt-", "works with only modifiers");
+};
+
+exports["test toJSON"] = function(assert) {
+ assert.deepEqual(utils.toJSON("Shift-Ctrl-B"), {
+ key: "b",
+ modifiers: [ "control", "shift" ]
+ }, "toJSON normalizes input");
+
+ assert.deepEqual(utils.toJSON("Meta-Alt-option-C"), {
+ key: "c",
+ modifiers: [ "alt", "meta" ]
+ }, "removes dublicates");
+
+ assert.deepEqual(utils.toJSON("AccEl+sHiFt+Z", "+"), {
+ key: "z",
+ modifiers: isMac ? [ "meta", "shift" ] : [ "control", "shift" ]
+ }, "normalizes OS specific keys and adjustes seperator");
+};
+
+exports["test normalize"] = function assert(assert) {
+ assert.equal(utils.normalize("Shift Ctrl A control ctrl", " "),
+ "control shift a", "removes reapeted modifiers");
+ assert.equal(utils.normalize("shift-ctrl-left"), "control-shift-left",
+ "normilizes non printed characters");
+
+ assert.throws(function() {
+ utils.normalize("shift-alt-b-z");
+ }, "throws if contains more then on non-modifier key");
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-l10n-locale.js b/tools/addon-sdk-1.12/test/test-l10n-locale.js
new file mode 100644
index 0000000..19e716d
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-l10n-locale.js
@@ -0,0 +1,141 @@
+/* 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 { getPreferedLocales, findClosestLocale } = require("sdk/l10n/locale");
+const prefs = require("sdk/preferences/service");
+const { Cc, Ci, Cu } = require("chrome");
+const { Services } = Cu.import("resource://gre/modules/Services.jsm");
+const BundleService = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
+
+const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
+const PREF_SELECTED_LOCALE = "general.useragent.locale";
+const PREF_ACCEPT_LANGUAGES = "intl.accept_languages";
+
+function assertPrefered(test, expected, msg) {
+ test.assertEqual(JSON.stringify(getPreferedLocales()), JSON.stringify(expected),
+ msg);
+}
+
+exports.testGetPreferedLocales = function(test) {
+ prefs.set(PREF_MATCH_OS_LOCALE, false);
+ prefs.set(PREF_SELECTED_LOCALE, "");
+ prefs.set(PREF_ACCEPT_LANGUAGES, "");
+ assertPrefered(test, ["en-us"],
+ "When all preferences are empty, we only have en-us");
+
+ prefs.set(PREF_SELECTED_LOCALE, "fr");
+ prefs.set(PREF_ACCEPT_LANGUAGES, "jp");
+ assertPrefered(test, ["fr", "jp", "en-us"],
+ "We first have useragent locale, then web one and finally en-US");
+
+ prefs.set(PREF_SELECTED_LOCALE, "en-US");
+ prefs.set(PREF_ACCEPT_LANGUAGES, "en-US");
+ assertPrefered(test, ["en-us"],
+ "We do not have duplicates");
+
+ prefs.set(PREF_SELECTED_LOCALE, "en-US");
+ prefs.set(PREF_ACCEPT_LANGUAGES, "fr");
+ assertPrefered(test, ["en-us", "fr"],
+ "en-US can be first if specified by higher priority preference");
+
+ // Reset what we changed
+ prefs.reset(PREF_MATCH_OS_LOCALE);
+ prefs.reset(PREF_SELECTED_LOCALE);
+ prefs.reset(PREF_ACCEPT_LANGUAGES);
+}
+
+// In some cases, mainly on Fennec and on Linux version,
+// `general.useragent.locale` is a special 'localized' value, like:
+// "chrome://global/locale/intl.properties"
+exports.testPreferedLocalizedLocale = function(test) {
+ prefs.set(PREF_MATCH_OS_LOCALE, false);
+ let bundleURL = "chrome://global/locale/intl.properties";
+ prefs.setLocalized(PREF_SELECTED_LOCALE, bundleURL);
+ let contentLocale = "ja";
+ prefs.set(PREF_ACCEPT_LANGUAGES, contentLocale);
+
+ // Read manually the expected locale value from the property file
+ let expectedLocale = BundleService.createBundle(bundleURL).
+ GetStringFromName(PREF_SELECTED_LOCALE).
+ toLowerCase();
+
+ // First add the useragent locale
+ let expectedLocaleList = [expectedLocale];
+
+ // Then the content locale
+ if (expectedLocaleList.indexOf(contentLocale) == -1)
+ expectedLocaleList.push(contentLocale);
+
+ // Add default "en-us" fallback if the main language is not already en-us
+ if (expectedLocaleList.indexOf("en-us") == -1)
+ expectedLocaleList.push("en-us");
+
+ assertPrefered(test, expectedLocaleList, "test localized pref value");
+
+ // Reset what we have changed
+ prefs.reset(PREF_MATCH_OS_LOCALE);
+ prefs.reset(PREF_SELECTED_LOCALE);
+ prefs.reset(PREF_ACCEPT_LANGUAGES);
+}
+
+exports.testPreferedOsLocale = function(test) {
+ prefs.set(PREF_MATCH_OS_LOCALE, true);
+ prefs.set(PREF_SELECTED_LOCALE, "");
+ prefs.set(PREF_ACCEPT_LANGUAGES, "");
+
+ let expectedLocale = Services.locale.getLocaleComponentForUserAgent().
+ toLowerCase();
+ let expectedLocaleList = [expectedLocale];
+
+ // Add default "en-us" fallback if the main language is not already en-us
+ if (expectedLocale != "en-us")
+ expectedLocaleList.push("en-us");
+
+ assertPrefered(test, expectedLocaleList, "Ensure that we select OS locale when related preference is set");
+
+ // Reset what we have changed
+ prefs.reset(PREF_MATCH_OS_LOCALE);
+ prefs.reset(PREF_SELECTED_LOCALE);
+ prefs.reset(PREF_ACCEPT_LANGUAGES);
+}
+
+exports.testFindClosestLocale = function(test) {
+ // Second param of findClosestLocale (aMatchLocales) have to be in lowercase
+ test.assertEqual(findClosestLocale([], []), null,
+ "When everything is empty we get null");
+
+ test.assertEqual(findClosestLocale(["en", "en-US"], ["en"]),
+ "en", "We always accept exact match first 1/5");
+ test.assertEqual(findClosestLocale(["en-US", "en"], ["en"]),
+ "en", "We always accept exact match first 2/5");
+ test.assertEqual(findClosestLocale(["en", "en-US"], ["en-us"]),
+ "en-US", "We always accept exact match first 3/5");
+ test.assertEqual(findClosestLocale(["ja-JP-mac", "ja", "ja-JP"], ["ja-jp"]),
+ "ja-JP", "We always accept exact match first 4/5");
+ test.assertEqual(findClosestLocale(["ja-JP-mac", "ja", "ja-JP"], ["ja-jp-mac"]),
+ "ja-JP-mac", "We always accept exact match first 5/5");
+
+ test.assertEqual(findClosestLocale(["en", "en-GB"], ["en-us"]),
+ "en", "We accept more generic locale, when there is no exact match 1/2");
+ test.assertEqual(findClosestLocale(["en-ZA", "en"], ["en-gb"]),
+ "en", "We accept more generic locale, when there is no exact match 2/2");
+
+ test.assertEqual(findClosestLocale(["ja-JP"], ["ja"]),
+ "ja-JP", "We accept more specialized locale, when there is no exact match 1/2");
+ // Better to select "ja" in this case but behave same as current AddonManager
+ test.assertEqual(findClosestLocale(["ja-JP-mac", "ja"], ["ja-jp"]),
+ "ja-JP-mac", "We accept more specialized locale, when there is no exact match 2/2");
+
+ test.assertEqual(findClosestLocale(["en-US"], ["en-us"]),
+ "en-US", "We keep the original one as result 1/2");
+ test.assertEqual(findClosestLocale(["en-us"], ["en-us"]),
+ "en-us", "We keep the original one as result 2/2");
+
+ test.assertEqual(findClosestLocale(["ja-JP-mac"], ["ja-jp-mac"]),
+ "ja-JP-mac", "We accept locale with 3 parts");
+ test.assertEqual(findClosestLocale(["ja-JP"], ["ja-jp-mac"]),
+ "ja-JP", "We accept locale with 2 parts from locale with 3 parts");
+ test.assertEqual(findClosestLocale(["ja"], ["ja-jp-mac"]),
+ "ja", "We accept locale with 1 part from locale with 3 parts");
+}
diff --git a/tools/addon-sdk-1.12/test/test-l10n-plural-rules.js b/tools/addon-sdk-1.12/test/test-l10n-plural-rules.js
new file mode 100644
index 0000000..6ee8a13
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-l10n-plural-rules.js
@@ -0,0 +1,82 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { getRulesForLocale } = require("sdk/l10n/plural-rules");
+
+// For more information, please visit unicode website:
+// http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+
+function map(test, f, n, form) {
+ test.assertEqual(f(n), form, n + " maps to '" + form + "'");
+}
+
+exports.testFrench = function(test) {
+ let f = getRulesForLocale("fr");
+ map(test, f, -1, "other");
+ map(test, f, 0, "one");
+ map(test, f, 1, "one");
+ map(test, f, 1.5, "one");
+ map(test, f, 2, "other");
+ map(test, f, 100, "other");
+}
+
+exports.testEnglish = function(test) {
+ let f = getRulesForLocale("en");
+ map(test, f, -1, "other");
+ map(test, f, 0, "other");
+ map(test, f, 1, "one");
+ map(test, f, 1.5, "other");
+ map(test, f, 2, "other");
+ map(test, f, 100, "other");
+}
+
+exports.testArabic = function(test) {
+ let f = getRulesForLocale("ar");
+ map(test, f, -1, "other");
+ map(test, f, 0, "zero");
+ map(test, f, 0.5, "other");
+
+ map(test, f, 1, "one");
+ map(test, f, 1.5, "other");
+
+ map(test, f, 2, "two");
+ map(test, f, 2.5, "other");
+
+ map(test, f, 3, "few");
+ map(test, f, 3.5, "few"); // I'd expect it to be 'other', but the unicode.org
+ // algorithm computes 'few'.
+ map(test, f, 5, "few");
+ map(test, f, 10, "few");
+ map(test, f, 103, "few");
+ map(test, f, 105, "few");
+ map(test, f, 110, "few");
+ map(test, f, 203, "few");
+ map(test, f, 205, "few");
+ map(test, f, 210, "few");
+
+ map(test, f, 11, "many");
+ map(test, f, 50, "many");
+ map(test, f, 99, "many");
+ map(test, f, 111, "many");
+ map(test, f, 150, "many");
+ map(test, f, 199, "many");
+
+ map(test, f, 100, "other");
+ map(test, f, 101, "other");
+ map(test, f, 102, "other");
+ map(test, f, 200, "other");
+ map(test, f, 201, "other");
+ map(test, f, 202, "other");
+}
+
+exports.testJapanese = function(test) {
+ // Japanese doesn't have plural forms.
+ let f = getRulesForLocale("ja");
+ map(test, f, -1, "other");
+ map(test, f, 0, "other");
+ map(test, f, 1, "other");
+ map(test, f, 1.5, "other");
+ map(test, f, 2, "other");
+ map(test, f, 100, "other");
+}
diff --git a/tools/addon-sdk-1.12/test/test-layout-change.js b/tools/addon-sdk-1.12/test/test-layout-change.js
new file mode 100644
index 0000000..a262251
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-layout-change.js
@@ -0,0 +1,178 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// This test makes sure that require statements used by all AMO hosted
+// add-ons will be able to use old require statements.
+// Tests are based on following usage data:
+// https://docs.google.com/spreadsheet/ccc?key=0ApEBy-GRnGxzdHlRMHJ5RXN1aWJ4RGhINkxSd0FCQXc#gid=0
+
+exports["test compatibility"] = function(assert) {
+ assert.equal(require("self"),
+ require("sdk/self"), "sdk/self -> self");
+
+ assert.equal(require("tabs"),
+ require("sdk/tabs"), "sdk/tabs -> tabs");
+
+ assert.equal(require("widget"),
+ require("sdk/widget"), "sdk/widget -> widget");
+
+ assert.equal(require("page-mod"),
+ require("sdk/page-mod"), "sdk/page-mod -> page-mod");
+
+ assert.equal(require("panel"),
+ require("sdk/panel"), "sdk/panel -> panel");
+
+ assert.equal(require("request"),
+ require("sdk/request"), "sdk/request -> request");
+
+ assert.equal(require("chrome"),
+ require("chrome"), "chrome -> chrome");
+
+ assert.equal(require("simple-storage"),
+ require("sdk/simple-storage"), "sdk/simple-storage -> simple-storage");
+
+ assert.equal(require("context-menu"),
+ require("sdk/context-menu"), "sdk/context-menu -> context-menu");
+
+ assert.equal(require("notifications"),
+ require("sdk/notifications"), "sdk/notifications -> notifications");
+
+ assert.equal(require("preferences-service"),
+ require("sdk/preferences/service"), "sdk/preferences/service -> preferences-service");
+
+ assert.equal(require("window-utils"),
+ require("sdk/deprecated/window-utils"), "sdk/deprecated/window-utils -> window-utils");
+
+ assert.equal(require("url"),
+ require("sdk/url"), "sdk/url -> url");
+
+ assert.equal(require("selection"),
+ require("sdk/selection"), "sdk/selection -> selection");
+
+ assert.equal(require("timers"),
+ require("sdk/timers"), "sdk/timers -> timers");
+
+ assert.equal(require("simple-prefs"),
+ require("sdk/simple-prefs"), "sdk/simple-prefs -> simple-prefs");
+
+ assert.equal(require("traceback"),
+ require("sdk/console/traceback"), "sdk/console/traceback -> traceback");
+
+ assert.equal(require("unload"),
+ require("sdk/system/unload"), "sdk/system/unload -> unload");
+
+ assert.equal(require("hotkeys"),
+ require("sdk/hotkeys"), "sdk/hotkeys -> hotkeys");
+
+ assert.equal(require("clipboard"),
+ require("sdk/clipboard"), "sdk/clipboard -> clipboard");
+
+ assert.equal(require("windows"),
+ require("sdk/windows"), "sdk/windows -> windows");
+
+ assert.equal(require("page-worker"),
+ require("sdk/page-worker"), "sdk/page-worker -> page-worker");
+
+ assert.equal(require("timer"),
+ require("sdk/timers"), "sdk/timers -> timer");
+
+ assert.equal(require("xhr"),
+ require("sdk/net/xhr"), "sdk/io/xhr -> xhr");
+
+ assert.equal(require("observer-service"),
+ require("sdk/deprecated/observer-service"), "sdk/deprecated/observer-service -> observer-service");
+
+ assert.equal(require("private-browsing"),
+ require("sdk/private-browsing"), "sdk/private-browsing -> private-browsing");
+
+ assert.equal(require("passwords"),
+ require("sdk/passwords"), "sdk/passwords -> passwords");
+
+ assert.equal(require("events"),
+ require("sdk/deprecated/events"), "sdk/deprecated/events -> events");
+
+ assert.equal(require("match-pattern"),
+ require("sdk/page-mod/match-pattern"), "sdk/page-mod/match-pattern -> match-pattern");
+
+ assert.equal(require("tab-browser"),
+ require("sdk/deprecated/tab-browser"), "sdk/deprecated/tab-browser -> tab-browser");
+
+ assert.equal(require("file"),
+ require("sdk/io/file"), "sdk/io/file -> file");
+
+ assert.equal(require("xul-app"),
+ require("sdk/system/xul-app"), "sdk/system/xul-app -> xul-app");
+
+ assert.equal(require("api-utils"),
+ require("sdk/deprecated/api-utils"), "sdk/deprecated/api-utils -> api-utils");
+
+ assert.equal(require("runtime"),
+ require("sdk/system/runtime"), "sdk/system/runtime -> runtime");
+
+ assert.equal(require("base64"),
+ require("sdk/base64"), "sdk/base64 -> base64");
+
+ assert.equal(require("xpcom"),
+ require("sdk/platform/xpcom"), "sdk/platform/xpcom -> xpcom");
+
+ assert.equal(require("traits"),
+ require("sdk/deprecated/traits"), "sdk/deprecated/traits -> traits");
+
+ assert.equal(require("keyboard/utils"),
+ require("sdk/keyboard/utils"), "sdk/keyboard/utils -> keyboard/utils");
+
+ assert.equal(require("system"),
+ require("sdk/system"), "sdk/system -> system");
+
+ assert.equal(require("querystring"),
+ require("sdk/querystring"), "sdk/querystring -> querystring");
+
+ assert.equal(require("addon-page"),
+ require("sdk/addon-page"), "sdk/addon-page -> addon-page");
+
+ assert.equal(require("tabs/utils"),
+ require("sdk/tabs/utils"), "sdk/tabs/utils -> tabs/utils");
+
+ assert.equal(require("app-strings"),
+ require("sdk/deprecated/app-strings"), "sdk/deprecated/app-strings -> app-strings");
+
+ assert.equal(require("dom/events"),
+ require("sdk/dom/events"), "sdk/dom/events -> dom/events");
+
+ assert.equal(require("tabs/tab.js"),
+ require("sdk/tabs/tab"), "sdk/tabs/tab -> tabs/tab.js");
+
+ assert.equal(require("memory"),
+ require("sdk/deprecated/memory"), "sdk/deprecated/memory -> memory");
+
+ assert.equal(require("light-traits"),
+ require("sdk/deprecated/light-traits"), "sdk/deprecated/light-traits -> light-traits");
+
+ assert.equal(require("environment"),
+ require("sdk/system/environment"), "sdk/system/environment -> environment");
+
+ assert.equal(require("utils/data"),
+ require("sdk/io/data"), "sdk/io/data -> utils/data");
+
+ assert.equal(require("test/assert"),
+ require("sdk/test/assert"), "sdk/test/assert -> test/assert");
+
+ assert.equal(require("hidden-frame"),
+ require("sdk/frame/hidden-frame"), "sdk/frame/hidden-frame -> hidden-frame");
+
+ assert.equal(require("collection"),
+ require("sdk/util/collection"), "sdk/util/collection -> collection");
+
+ assert.equal(require("array"),
+ require("sdk/util/array"), "sdk/util/array -> array");
+
+ assert.equal(require("api-utils/cortex"),
+ require("sdk/deprecated/cortex"),
+ "api-utils/cortex -> sdk/deprecated/cortex");
+};
+
+
+require('test').run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-light-traits.js b/tools/addon-sdk-1.12/test/test-light-traits.js
new file mode 100644
index 0000000..7c5ca42
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-light-traits.js
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+exports["test traits from objects"] = require("./traits/object-tests");
+exports["test traits from descriptors"] = require("./traits/descriptor-tests");
+exports["test inheritance"] = require("./traits/inheritance-tests");
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-list.js b/tools/addon-sdk-1.12/test/test-list.js
new file mode 100644
index 0000000..1e345d2
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-list.js
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+const { List, addListItem, removeListItem } = require('sdk/util/list');
+const { Class } = require('sdk/core/heritage');
+
+exports.testList = function(test) {
+ let list = List();
+ addListItem(list, 1);
+
+ for (let key in list) {
+ test.assertEqual(key, 0, 'key is correct');
+ test.assertEqual(list[key], 1, 'value is correct');
+ }
+
+ let count = 0;
+ for each (let ele in list) {
+ test.assertEqual(ele, 1, 'ele is correct');
+ test.assertEqual(++count, 1, 'count is correct');
+ }
+
+ removeListItem(list, 1);
+ test.assertEqual(list.length, 0, 'remove worked');
+};
+
+exports.testImplementsList = function(test) {
+ let List2 = Class({
+ implements: [List],
+ initialize: function() {
+ List.prototype.initialize.apply(this, [0, 1, 2]);
+ }
+ });
+ let list2 = List2();
+ let count = 0;
+ for each (let ele in list2) {
+ test.assertEqual(ele, count++, 'ele is correct');
+ }
+ addListItem(list2, 3);
+ test.assertEqual(list2.length, 4, '3 was added');
+ test.assertEqual(list2[list2.length-1], 3, '3 was added');
+}
diff --git a/tools/addon-sdk-1.12/test/test-loader.js b/tools/addon-sdk-1.12/test/test-loader.js
new file mode 100644
index 0000000..08c285f
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-loader.js
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+let { Loader, main, unload } = require('toolkit/loader');
+
+exports['test dependency cycles'] = function(assert) {
+ let uri = module.uri.substr(0, module.uri.lastIndexOf('/')) +
+ '/fixtures/loader/cycles/'
+
+ let loader = Loader({
+ paths: { '': uri }
+ });
+
+ let program = main(loader, 'main')
+
+ assert.equal(program.a.b, program.b, 'module `a` gets correct `b`')
+ assert.equal(program.b.a, program.a, 'module `b` gets correct `a`')
+ assert.equal(program.c.main, program, 'module `c` gets correct `main`')
+
+ unload(loader);
+};
+
+require('test').run(exports);
+
diff --git a/tools/addon-sdk-1.12/test/test-match-pattern.js b/tools/addon-sdk-1.12/test/test-match-pattern.js
new file mode 100644
index 0000000..60cdac2
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-match-pattern.js
@@ -0,0 +1,129 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { MatchPattern } = require("sdk/page-mod/match-pattern");
+
+exports.testMatchPatternTestTrue = function(test) {
+ function ok(pattern, url) {
+ let mp = new MatchPattern(pattern);
+ test.assert(mp.test(url), pattern + " should match " + url);
+ }
+
+ ok("*", "http://example.com");
+ ok("*", "https://example.com");
+ ok("*", "ftp://example.com");
+
+ ok("*.example.com", "http://example.com");
+ ok("*.example.com", "http://hamburger.example.com");
+ ok("*.example.com", "http://hotdog.hamburger.example.com");
+
+ ok("http://example.com*", "http://example.com");
+ ok("http://example.com*", "http://example.com/");
+ ok("http://example.com/*", "http://example.com/");
+ ok("http://example.com/*", "http://example.com/potato-salad");
+ ok("http://example.com/pickles/*", "http://example.com/pickles/");
+ ok("http://example.com/pickles/*", "http://example.com/pickles/lemonade");
+
+ ok("http://example.com", "http://example.com");
+ ok("http://example.com/ice-cream", "http://example.com/ice-cream");
+
+ ok(/.*zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753");
+ ok(/https:.*zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753");
+};
+
+exports.testMatchPatternTestFalse = function(test) {
+ function ok(pattern, url) {
+ let mp = new MatchPattern(pattern);
+ test.assert(!mp.test(url), pattern + " should not match " + url);
+ }
+
+ ok("*", null);
+ ok("*", "");
+ ok("*", "bogus");
+ ok("*", "chrome://browser/content/browser.xul");
+ ok("*", "nttp://example.com");
+
+ ok("*.example.com", null);
+ ok("*.example.com", "");
+ ok("*.example.com", "bogus");
+ ok("*.example.com", "http://example.net");
+ ok("*.example.com", "http://foo.com");
+ ok("*.example.com", "http://example.com.foo");
+ ok("*.example2.com", "http://example.com");
+
+ ok("http://example.com/*", null);
+ ok("http://example.com/*", "");
+ ok("http://example.com/*", "bogus");
+ ok("http://example.com/*", "http://example.com");
+ ok("http://example.com/*", "http://foo.com/");
+
+ ok("http://example.com", null);
+ ok("http://example.com", "");
+ ok("http://example.com", "bogus");
+ ok("http://example.com", "http://example.com/");
+
+ ok(/zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753");
+ ok(/.*zilla/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753");
+ ok(/.*Zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=655464"); // bug 655464
+ ok(/https:.*zilla/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753");
+};
+
+exports.testMatchPatternErrors = function(test) {
+ test.assertRaises(
+ function() new MatchPattern("*.google.com/*"),
+ /There can be at most one/,
+ "MatchPattern throws when supplied multiple '*'"
+ );
+
+ test.assertRaises(
+ function() new MatchPattern("google.com"),
+ /expected to be either an exact URL/,
+ "MatchPattern throws when the wildcard doesn't use '*' and doesn't " +
+ "look like a URL"
+ );
+
+ test.assertRaises(
+ function() new MatchPattern("http://google*.com"),
+ /expected to be the first or the last/,
+ "MatchPattern throws when a '*' is in the middle of the wildcard"
+ );
+
+ test.assertRaises(
+ function() new MatchPattern(/ /g),
+ /^A RegExp match pattern cannot be set to `global` \(i\.e\. \/\/g\)\.$/,
+ "MatchPattern throws on a RegExp set to `global` (i.e. //g)."
+ );
+
+ test.assertRaises(
+ function() new MatchPattern(/ /i),
+ /^A RegExp match pattern cannot be set to `ignoreCase` \(i\.e\. \/\/i\)\.$/,
+ "MatchPattern throws on a RegExp set to `ignoreCase` (i.e. //i)."
+ );
+
+ test.assertRaises(
+ function() new MatchPattern( / /m ),
+ /^A RegExp match pattern cannot be set to `multiline` \(i\.e\. \/\/m\)\.$/,
+ "MatchPattern throws on a RegExp set to `multiline` (i.e. //m)."
+ );
+};
+
+exports.testMatchPatternInternals = function(test) {
+ test.assertEqual(
+ new MatchPattern("http://google.com/test").exactURL,
+ "http://google.com/test"
+ );
+
+ test.assertEqual(
+ new MatchPattern("http://google.com/test/*").urlPrefix,
+ "http://google.com/test/"
+ );
+
+ test.assertEqual(
+ new MatchPattern("*.example.com").domain,
+ "example.com"
+ );
+};
diff --git a/tools/addon-sdk-1.12/test/test-memory.js b/tools/addon-sdk-1.12/test/test-memory.js
new file mode 100644
index 0000000..e225707
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-memory.js
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var memory = require("sdk/deprecated/memory");
+
+exports.testMemory = function(test) {
+ test.pass("Skipping this test until Gecko memory debugging issues " +
+ "are resolved (see bug 592774).");
+ return;
+
+ var obj = {};
+ memory.track(obj, "testMemory.testObj");
+ var objs = memory.getObjects("testMemory.testObj");
+ test.assertEqual(objs[0].weakref.get(), obj);
+ obj = null;
+ memory.gc();
+ test.assertEqual(objs[0].weakref.get(), null);
+};
diff --git a/tools/addon-sdk-1.12/test/test-module.js b/tools/addon-sdk-1.12/test/test-module.js
new file mode 100644
index 0000000..6c9c6c2
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/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("sdk/hotkeys")),
+ "module exports are frozen");
+};
+
+exports["test redefine exported property"] = function(assert) {
+ let hotkeys = require("sdk/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("sdk/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("sdk/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.12/test/test-modules.js b/tools/addon-sdk-1.12/test/test-modules.js
new file mode 100644
index 0000000..2868560
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-modules.js
@@ -0,0 +1,148 @@
+/* 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/. */
+
+exports.testDefine = function(test) {
+ let tiger = require('./modules/tiger');
+ test.assertEqual(tiger.name, 'tiger', 'name proprety was exported properly');
+ test.assertEqual(tiger.type, 'cat', 'property form other module exported');
+};
+
+exports.testDefineInoresNonFactory = function(test) {
+ let mod = require('./modules/async2');
+ test.assertEqual(mod.name, 'async2', 'name proprety was exported properly');
+ test.assertNotEqual(mod.traditional2Name, 'traditional2', '1st is ignored');
+};
+/* Disable test that require AMD specific functionality:
+
+// define() that exports a function as the module value,
+// specifying a module name.
+exports.testDefExport = function(test) {
+ var add = require('modules/add');
+ test.assertEqual(add(1, 1), 2, 'Named define() exporting a function');
+};
+
+// define() that exports function as a value, but is anonymous
+exports.testAnonDefExport = function (test) {
+ var subtract = require('modules/subtract');
+ test.assertEqual(subtract(4, 2), 2,
+ 'Anonymous define() exporting a function');
+}
+
+// using require([], function () {}) to load modules.
+exports.testSimpleRequire = function (test) {
+ require(['modules/blue', 'modules/orange'], function (blue, orange) {
+ test.assertEqual(blue.name, 'blue', 'Simple require for blue');
+ test.assertEqual(orange.name, 'orange', 'Simple require for orange');
+ test.assertEqual(orange.parentType, 'color',
+ 'Simple require dependency check for orange');
+ });
+}
+
+// using nested require([]) calls.
+exports.testSimpleRequireNested = function (test) {
+ require(['modules/blue', 'modules/orange', 'modules/green'],
+ function (blue, orange, green) {
+
+ require(['modules/orange', 'modules/red'], function (orange, red) {
+ test.assertEqual(red.name, 'red', 'Simple require for red');
+ test.assertEqual(red.parentType, 'color',
+ 'Simple require dependency check for red');
+ test.assertEqual(blue.name, 'blue', 'Simple require for blue');
+ test.assertEqual(orange.name, 'orange', 'Simple require for orange');
+ test.assertEqual(orange.parentType, 'color',
+ 'Simple require dependency check for orange');
+ test.assertEqual(green.name, 'green', 'Simple require for green');
+ test.assertEqual(green.parentType, 'color',
+ 'Simple require dependency check for green');
+ });
+
+ });
+}
+
+// requiring a traditional module, that uses async, that use traditional and
+// async, with a circular reference
+exports.testMixedCircular = function (test) {
+ var t = require('modules/traditional1');
+ test.assertEqual(t.name, 'traditional1', 'Testing name');
+ test.assertEqual(t.traditional2Name, 'traditional2',
+ 'Testing dependent name');
+ test.assertEqual(t.traditional1Name, 'traditional1', 'Testing circular name');
+ test.assertEqual(t.async2Name, 'async2', 'Testing async2 name');
+ test.assertEqual(t.async2Traditional2Name, 'traditional2',
+ 'Testing nested traditional2 name');
+}
+
+// Testing define()(function(require) {}) with some that use exports,
+// some that use return.
+exports.testAnonExportsReturn = function (test) {
+ var lion = require('modules/lion');
+ require(['modules/tiger', 'modules/cheetah'], function (tiger, cheetah) {
+ test.assertEqual('lion', lion, 'Check lion name');
+ test.assertEqual('tiger', tiger.name, 'Check tiger name');
+ test.assertEqual('cat', tiger.type, 'Check tiger type');
+ test.assertEqual('cheetah', cheetah(), 'Check cheetah name');
+ });
+}
+
+// circular dependency
+exports.testCircular = function (test) {
+ var pollux = require('modules/pollux'),
+ castor = require('modules/castor');
+
+ test.assertEqual(pollux.name, 'pollux', 'Pollux\'s name');
+ test.assertEqual(pollux.getCastorName(),
+ 'castor', 'Castor\'s name from Pollux.');
+ test.assertEqual(castor.name, 'castor', 'Castor\'s name');
+ test.assertEqual(castor.getPolluxName(), 'pollux',
+ 'Pollux\'s name from Castor.');
+}
+
+// test a bad module that asks for exports but also does a define() return
+exports.testBadExportAndReturn = function (test) {
+ var passed = false;
+ try {
+ var bad = require('modules/badExportAndReturn');
+ } catch(e) {
+ passed = /cannot use exports and also return/.test(e.toString());
+ }
+ test.assertEqual(passed, true, 'Make sure exports and return fail');
+}
+
+// test a bad circular dependency, where an exported value is needed, but
+// the return value happens too late, a module already asked for the exported
+// value.
+exports.testBadExportAndReturnCircular = function (test) {
+ var passed = false;
+ try {
+ var bad = require('modules/badFirst');
+ } catch(e) {
+ passed = /after another module has referenced its exported value/
+ .test(e.toString());
+ }
+ test.assertEqual(passed, true, 'Make sure return after an exported ' +
+ 'value is grabbed by another module fails.');
+}
+
+// only allow one define call per file.
+exports.testOneDefine = function (test) {
+ var passed = false;
+ try {
+ var dupe = require('modules/dupe');
+ } catch(e) {
+ passed = /Only one call to define/.test(e.toString());
+ }
+ test.assertEqual(passed, true, 'Only allow one define call per module');
+}
+
+// only allow one define call per file, testing a bad nested define call.
+exports.testOneDefineNested = function (test) {
+ var passed = false;
+ try {
+ var dupe = require('modules/dupeNested');
+ } catch(e) {
+ passed = /Only one call to define/.test(e.toString());
+ }
+ test.assertEqual(passed, true, 'Only allow one define call per module');
+}
+*/
diff --git a/tools/addon-sdk-1.12/test/test-namespace.js b/tools/addon-sdk-1.12/test/test-namespace.js
new file mode 100644
index 0000000..dfe88da
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-namespace.js
@@ -0,0 +1,122 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { ns } = require("sdk/core/namespace");
+const { Cc, Ci, Cu } = require("chrome");
+const { setTimeout } = require("sdk/timers")
+
+exports["test post GC references"] = function (assert, done) {
+ var target = {}, local = ns()
+ local(target).there = true
+
+ assert.equal(local(target).there, true, "namespaced preserved");
+
+ setTimeout(function() {
+ Cu.forceGC();
+ assert.equal(local(target).there, true, "namespace is preserved post GC");
+ done();
+ }, 300);
+};
+
+exports["test namsepace basics"] = function(assert) {
+ var privates = ns();
+ var object = { foo: function foo() { return "hello foo"; } };
+
+ assert.notEqual(privates(object), object,
+ "namespaced object is not the same");
+ assert.ok(!('foo' in privates(object)),
+ "public properties are not in the namespace");
+
+ assert.equal(privates(object), privates(object),
+ "same namespaced object is returned on each call");
+};
+
+exports["test namespace overlays"] = function(assert) {
+ var _ = ns();
+ var object = { foo: 'foo' };
+
+ _(object).foo = 'bar';
+
+ assert.equal(_(object).foo, "bar",
+ "namespaced property `foo` changed value");
+
+ assert.equal(object.foo, "foo",
+ "public property `foo` has original value");
+
+ object.foo = "baz";
+ assert.equal(_(object).foo, "bar",
+ "property changes do not affect namespaced properties");
+
+ object.bar = "foo";
+ assert.ok(!("bar" in _(object)),
+ "new public properties are not reflected in namespace");
+};
+
+exports["test shared namespaces"] = function(assert) {
+ var _ = ns();
+
+ var f1 = { hello: 1 };
+ var f2 = { foo: 'foo', hello: 2 };
+ _(f1).foo = _(f2).foo = 'bar';
+
+ assert.equal(_(f1).hello, _(f2).hello, "namespace can be shared");
+ assert.notEqual(f1.hello, _(f1).hello, "shared namespace can overlay");
+ assert.notEqual(f2.hello, _(f2).hello, "target is not affected");
+
+ _(f1).hello = 3;
+
+ assert.notEqual(_(f1).hello, _(f2).hello,
+ "namespaced property can be overided");
+ assert.equal(_(f2).hello, _({}).hello, "namespace does not change");
+};
+
+exports["test multi namespace"] = function(assert) {
+ var n1 = ns();
+ var n2 = ns();
+ var object = { baz: 1 };
+ n1(object).foo = 1;
+ n2(object).foo = 2;
+ n1(object).bar = n2(object).bar = 3;
+
+ assert.notEqual(n1(object).foo, n2(object).foo,
+ "object can have multiple namespaces");
+ assert.equal(n1(object).bar, n2(object).bar,
+ "object can have matching props in diff namespaces");
+};
+
+exports["test ns alias"] = function(assert) {
+ assert.strictEqual(ns, require('sdk/core/namespace').Namespace,
+ "ns is an alias of Namespace");
+};
+
+exports["test ns inheritance"] = function(assert) {
+ let _ = ns();
+
+ let prototype = { level: 1 };
+ let object = Object.create(prototype);
+ let delegee = Object.create(object);
+
+ _(prototype).foo = {};
+
+ assert.ok(!Object.prototype.hasOwnProperty.call(_(delegee), "foo"),
+ "namespaced property is not copied to descendants");
+ assert.equal(_(delegee).foo, _(prototype).foo,
+ "namespaced properties are inherited by descendants");
+
+ _(object).foo = {};
+ assert.notEqual(_(object).foo, _(prototype).foo,
+ "namespaced properties may be shadowed");
+ assert.equal(_(object).foo, _(delegee).foo,
+ "shadwed properties are inherited by descendants");
+
+ _(object).bar = {};
+ assert.ok(!("bar" in _(prototype)),
+ "descendants properties are not copied to ancestors");
+ assert.ok(_(object).bar, _(delegee).bar,
+ "descendants properties are inherited");
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-net-url.js b/tools/addon-sdk-1.12/test/test-net-url.js
new file mode 100644
index 0000000..302c402
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-net-url.js
@@ -0,0 +1,212 @@
+/* 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 { readURI, readURISync } = require("sdk/net/url");
+const { data } = require("self");
+
+const utf8text = "Hello, ゼロ!";
+const latin1text = "Hello, ゼロ!";
+
+const dataURIutf8 = "data:text/plain;charset=utf-8," + encodeURIComponent(utf8text);
+const dataURIlatin1 = "data:text/plain;charset=ISO-8859-1," + escape(latin1text);
+const chromeURI = "chrome://global-platform/locale/accessible.properties";
+
+exports["test async readURI"] = function(assert, done) {
+ let content = "";
+
+ readURI(data.url("test-net-url.txt")).then(function(data) {
+ content = data;
+ assert.equal(content, utf8text, "The URL content is loaded properly");
+ done();
+ }, function() {
+ assert.fail("should not reject");
+ done();
+ })
+
+ assert.equal(content, "", "The URL content is not load yet");
+}
+
+exports["test sync readURI"] = function(assert) {
+ let content = "";
+
+ readURI(data.url("test-net-url.txt"), { sync: true }).then(function(data) {
+ content = data;
+ }, function() {
+ assert.fail("should not reject");
+ })
+
+ assert.equal(content, utf8text, "The URL content is loaded properly");
+}
+
+exports["test readURISync"] = function(assert) {
+ let content = readURISync(data.url("test-net-url.txt"));
+
+ assert.equal(content, utf8text, "The URL content is loaded properly");
+}
+
+exports["test async readURI with ISO-8859-1 charset"] = function(assert, done) {
+ let content = "";
+
+ readURI(data.url("test-net-url.txt"), { charset : "ISO-8859-1"}).then(function(data) {
+ content = data;
+ assert.equal(content, latin1text, "The URL content is loaded properly");
+ done();
+ }, function() {
+ assert.fail("should not reject");
+ done();
+ })
+
+ assert.equal(content, "", "The URL content is not load yet");
+}
+
+exports["test sync readURI with ISO-8859-1 charset"] = function(assert) {
+ let content = "";
+
+ readURI(data.url("test-net-url.txt"), {
+ sync: true,
+ charset: "ISO-8859-1"
+ }).then(function(data) {
+ content = data;
+ }, function() {
+ assert.fail("should not reject");
+ })
+
+ assert.equal(content, latin1text, "The URL content is loaded properly");
+}
+
+exports["test readURISync with ISO-8859-1 charset"] = function(assert) {
+ let content = readURISync(data.url("test-net-url.txt"), "ISO-8859-1");
+
+ assert.equal(content, latin1text, "The URL content is loaded properly");
+}
+
+exports["test async readURI with not existing file"] = function(assert, done) {
+ readURI(data.url("test-net-url-fake.txt")).then(function(data) {
+ assert.fail("should not resolve");
+ done();
+ }, function(reason) {
+ assert.ok(reason.indexOf("Failed to read:") === 0);
+ done();
+ })
+}
+
+exports["test sync readURI with not existing file"] = function(assert) {
+ readURI(data.url("test-net-url-fake.txt"), { sync: true }).then(function(data) {
+ assert.fail("should not resolve");
+ }, function(reason) {
+ assert.ok(reason.indexOf("Failed to read:") === 0);
+ })
+}
+
+exports["test readURISync with not existing file"] = function(assert) {
+ assert.throws(function() {
+ readURISync(data.url("test-net-url-fake.txt"));
+ }, /NS_ERROR_FILE_NOT_FOUND/);
+}
+
+exports["test async readURI with data URI"] = function(assert, done) {
+ let content = "";
+
+ readURI(dataURIutf8).then(function(data) {
+ content = data;
+ assert.equal(content, utf8text, "The URL content is loaded properly");
+ done();
+ }, function() {
+ assert.fail("should not reject");
+ done();
+ })
+
+ assert.equal(content, "", "The URL content is not load yet");
+}
+
+exports["test sync readURI with data URI"] = function(assert) {
+ let content = "";
+
+ readURI(dataURIutf8, { sync: true }).then(function(data) {
+ content = data;
+ }, function() {
+ assert.fail("should not reject");
+ })
+
+ assert.equal(content, utf8text, "The URL content is loaded properly");
+}
+
+exports["test readURISync with data URI"] = function(assert) {
+ let content = readURISync(dataURIutf8);
+
+ assert.equal(content, utf8text, "The URL content is loaded properly");
+}
+
+exports["test async readURI with data URI and ISO-8859-1 charset"] = function(assert, done) {
+ let content = "";
+
+ readURI(dataURIlatin1, { charset : "ISO-8859-1"}).then(function(data) {
+ content = unescape(data);
+ assert.equal(content, latin1text, "The URL content is loaded properly");
+ done();
+ }, function() {
+ assert.fail("should not reject");
+ done();
+ })
+
+ assert.equal(content, "", "The URL content is not load yet");
+}
+
+exports["test sync readURI with data URI and ISO-8859-1 charset"] = function(assert) {
+ let content = "";
+
+ readURI(dataURIlatin1, {
+ sync: true,
+ charset: "ISO-8859-1"
+ }).then(function(data) {
+ content = unescape(data);
+ }, function() {
+ assert.fail("should not reject");
+ })
+
+ assert.equal(content, latin1text, "The URL content is loaded properly");
+}
+
+exports["test readURISync with data URI and ISO-8859-1 charset"] = function(assert) {
+ let content = unescape(readURISync(dataURIlatin1, "ISO-8859-1"));
+
+ assert.equal(content, latin1text, "The URL content is loaded properly");
+}
+
+exports["test readURISync with chrome URI"] = function(assert) {
+ let content = readURISync(chromeURI);
+
+ assert.ok(content, "The URL content is loaded properly");
+}
+
+exports["test async readURI with chrome URI"] = function(assert, done) {
+ let content = "";
+
+ readURI(chromeURI).then(function(data) {
+ content = data;
+ assert.equal(content, readURISync(chromeURI), "The URL content is loaded properly");
+ done();
+ }, function() {
+ assert.fail("should not reject");
+ done();
+ })
+
+ assert.equal(content, "", "The URL content is not load yet");
+}
+
+exports["test sync readURI with chrome URI"] = function(assert) {
+ let content = "";
+
+ readURI(chromeURI, { sync: true }).then(function(data) {
+ content = data;
+ }, function() {
+ assert.fail("should not reject");
+ })
+
+ assert.equal(content, readURISync(chromeURI), "The URL content is loaded properly");
+}
+
+require("test").run(exports)
diff --git a/tools/addon-sdk-1.12/test/test-notifications.js b/tools/addon-sdk-1.12/test/test-notifications.js
new file mode 100644
index 0000000..3f29410
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/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('sdk/test/loader');
+
+exports.testOnClick = function (test) {
+ let [loader, mockAlertServ] = makeLoader(module);
+ let notifs = loader.require("sdk/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("sdk/notifications");
+ let scope = loader.sandbox("sdk/notifications");
+ scope.notify = mockAlertServ.showAlertNotification.bind(mockAlertServ);
+ return [loader, mockAlertServ];
+};
diff --git a/tools/addon-sdk-1.12/test/test-observer-service.js b/tools/addon-sdk-1.12/test/test-observer-service.js
new file mode 100644
index 0000000..bd4c903
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-observer-service.js
@@ -0,0 +1,80 @@
+/* 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 observers = require("sdk/deprecated/observer-service");
+const { Cc, Ci } = require("chrome");
+const { Loader } = require("sdk/test/loader");
+const { PlainTextConsole } = require("sdk/console/plain-text");
+
+exports.testUnloadAndErrorLogging = function(test) {
+ var prints = [];
+ var loader = Loader(module, {
+ console: new PlainTextConsole(function(_) {
+ prints.push(_);
+ })
+ });
+ var sbobsvc = loader.require("sdk/deprecated/observer-service");
+
+ var timesCalled = 0;
+ var cb = function(subject, data) {
+ timesCalled++;
+ };
+ var badCb = function(subject, data) {
+ throw new Error("foo");
+ };
+ sbobsvc.add("blarg", cb);
+ observers.notify("blarg", "yo yo");
+ test.assertEqual(timesCalled, 1);
+ sbobsvc.add("narg", badCb);
+ observers.notify("narg", "yo yo");
+ var lines = prints[0].split("\n");
+ test.assertEqual(lines[0], "error: " + require("sdk/self").name + ": An exception occurred.");
+ test.assertEqual(lines[1], "Traceback (most recent call last):");
+ test.assertEqual(lines.slice(-2)[0], "Error: foo");
+
+ loader.unload();
+ observers.notify("blarg", "yo yo");
+ test.assertEqual(timesCalled, 1);
+};
+
+exports.testObserverService = function(test) {
+ var ios = Cc['@mozilla.org/network/io-service;1']
+ .getService(Ci.nsIIOService);
+ var service = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ var uri = ios.newURI("http://www.foo.com", null, null);
+ var timesCalled = 0;
+ var lastSubject = null;
+ var lastData = null;
+
+ var cb = function(subject, data) {
+ timesCalled++;
+ lastSubject = subject;
+ lastData = data;
+ };
+
+ observers.add("blarg", cb);
+ service.notifyObservers(uri, "blarg", "some data");
+ test.assertEqual(timesCalled, 1,
+ "observer-service.add() should call callback");
+ test.assertEqual(lastSubject, uri,
+ "observer-service.add() should pass subject");
+ test.assertEqual(lastData, "some data",
+ "observer-service.add() should pass data");
+
+ function customSubject() {}
+ function customData() {}
+ observers.notify("blarg", customSubject, customData);
+ test.assertEqual(timesCalled, 2,
+ "observer-service.notify() should work");
+ test.assertEqual(lastSubject, customSubject,
+ "observer-service.notify() should pass+wrap subject");
+ test.assertEqual(lastData, customData,
+ "observer-service.notify() should pass data");
+
+ observers.remove("blarg", cb);
+ service.notifyObservers(null, "blarg", "some data");
+ test.assertEqual(timesCalled, 2,
+ "observer-service.remove() should work");
+};
diff --git a/tools/addon-sdk-1.12/test/test-packaging.js b/tools/addon-sdk-1.12/test/test-packaging.js
new file mode 100644
index 0000000..b7a96cd
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-packaging.js
@@ -0,0 +1,15 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var url = require("sdk/url");
+var file = require("sdk/io/file");
+var {Cm,Ci} = require("chrome");
+var options = require("@loader/options");
+
+exports.testPackaging = function(test) {
+
+ test.assertEqual(options.metadata.description,
+ "Add-on development made easy.",
+ "packaging metadata should be available");
+};
diff --git a/tools/addon-sdk-1.12/test/test-page-mod.js b/tools/addon-sdk-1.12/test/test-page-mod.js
new file mode 100644
index 0000000..9cb9265
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-page-mod.js
@@ -0,0 +1,918 @@
+/* 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("sdk/page-mod");
+var testPageMod = require("./pagemod-test-helpers").testPageMod;
+const { Loader } = require('sdk/test/loader');
+const tabs = require("sdk/tabs");
+const timer = require("sdk/timers");
+const { Cc, Ci } = require("chrome");
+const { open, getFrames, getMostRecentBrowserWindow } = require('sdk/window/utils');
+const windowUtils = require('sdk/deprecated/window-utils');
+const { getTabContentWindow, getActiveTab, openTab, closeTab } = require('sdk/tabs/utils');
+const { data } = require('self');
+
+/* 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);
+ timer.setTimeout(function() {test.done();}, 4000);
+ } else
+ test.pass();
+}
+
+function Isolate(worker) {
+ return "(" + worker + ")()";
+}
+
+/* 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:license", [{
+ 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;charset=utf-8,",
+ 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;charset=utf-8,", [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("sdk/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("sdk/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.testWorksWithExistingTabs = function(test) {
+ test.waitUntilDone();
+
+ let url = "data:text/html;charset=utf-8," + encodeURI("Test unique document");
+ let { PageMod } = require("sdk/page-mod");
+ tabs.open({
+ url: url,
+ onReady: function onReady(tab) {
+ let pageMod = new PageMod({
+ include: url,
+ attachTo: ["existing", "top", "frame"],
+ onAttach: function(worker) {
+ test.assertEqual(tab, worker.tab, "A worker has been created on this existing tab");
+ pageMod.destroy();
+ tab.close();
+ test.done();
+ }
+ });
+ }
+ });
+
+};
+
+exports['test tab worker on message'] = function(test) {
+ test.waitUntilDone();
+
+ let { browserWindows } = require("sdk/windows");
+ let tabs = require("sdk/tabs");
+ let { PageMod } = require("sdk/page-mod");
+
+ let url1 = "data:text/html;charset=utf-8,<title>tab1</title><h1>worker1.tab</h1>";
+ let url2 = "data:text/html;charset=utf-8,<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("sdk/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("sdk/tabs");
+ tabs.open({
+ url: "about:",
+ onReady: function onReady(tab) {
+ test.pass("check automatic destroy");
+ tab.close();
+ test.done();
+ }
+ });
+
+}
+
+exports['test attachment to tabs only'] = function(test) {
+ test.waitUntilDone();
+
+ let { PageMod } = require('sdk/page-mod');
+ let openedTab = null; // Tab opened in openTabWithIframe()
+ let workerCount = 0;
+
+ let mod = PageMod({
+ include: 'data:text/html*',
+ contentScriptWhen: 'start',
+ contentScript: '',
+ onAttach: function onAttach(worker) {
+ if (worker.tab === openedTab) {
+ if (++workerCount == 3) {
+ test.pass('Succesfully applied to tab documents and its iframe');
+ worker.destroy();
+ mod.destroy();
+ test.done();
+ }
+ }
+ else {
+ test.fail('page-mod attached to a non-tab document');
+ }
+ }
+ });
+
+ function openHiddenFrame() {
+ console.info('Open iframe in hidden window');
+ let hiddenFrames = require('sdk/frame/hidden-frame');
+ let hiddenFrame = hiddenFrames.add(hiddenFrames.HiddenFrame({
+ onReady: function () {
+ let element = this.element;
+ element.addEventListener('DOMContentLoaded', function onload() {
+ element.removeEventListener('DOMContentLoaded', onload, false);
+ hiddenFrames.remove(hiddenFrame);
+ openToplevelWindow();
+ }, false);
+ element.setAttribute('src', 'data:text/html;charset=utf-8,foo');
+ }
+ }));
+ }
+
+ function openToplevelWindow() {
+ console.info('Open toplevel window');
+ let win = open('data:text/html;charset=utf-8,bar');
+ win.addEventListener('DOMContentLoaded', function onload() {
+ win.removeEventListener('DOMContentLoaded', onload, false);
+ win.close();
+ openBrowserIframe();
+ }, false);
+ }
+
+ function openBrowserIframe() {
+ console.info('Open iframe in browser window');
+ let window = require('sdk/deprecated/window-utils').activeBrowserWindow;
+ let document = window.document;
+ let iframe = document.createElement('iframe');
+ iframe.setAttribute('type', 'content');
+ iframe.setAttribute('src', 'data:text/html;charset=utf-8,foobar');
+ iframe.addEventListener('DOMContentLoaded', function onload() {
+ iframe.removeEventListener('DOMContentLoaded', onload, false);
+ iframe.parentNode.removeChild(iframe);
+ openTabWithIframes();
+ }, false);
+ document.documentElement.appendChild(iframe);
+ }
+
+ // Only these three documents will be accepted by the page-mod
+ function openTabWithIframes() {
+ console.info('Open iframes in a tab');
+ let subContent = '<iframe src="data:text/html;charset=utf-8,sub frame" />'
+ let content = '<iframe src="data:text/html,' +
+ encodeURIComponent(subContent) + '" />';
+ require('sdk/tabs').open({
+ url: 'data:text/html;charset=utf-8,' + encodeURIComponent(content),
+ onOpen: function onOpen(tab) {
+ openedTab = tab;
+ }
+ });
+ }
+
+ openHiddenFrame();
+};
+
+exports['test111 attachTo [top]'] = function(test) {
+ test.waitUntilDone();
+
+ let { PageMod } = require('sdk/page-mod');
+
+ let subContent = '<iframe src="data:text/html;charset=utf-8,sub frame" />'
+ let content = '<iframe src="data:text/html;charset=utf-8,' +
+ encodeURIComponent(subContent) + '" />';
+ let topDocumentURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(content)
+
+ let workerCount = 0;
+
+ let mod = PageMod({
+ include: 'data:text/html*',
+ contentScriptWhen: 'start',
+ contentScript: 'self.postMessage(document.location.href);',
+ attachTo: ['top'],
+ onAttach: function onAttach(worker) {
+ if (++workerCount == 1) {
+ worker.on('message', function (href) {
+ test.assertEqual(href, topDocumentURL,
+ "worker on top level document only");
+ worker.destroy();
+ mod.destroy();
+ test.done();
+ });
+ }
+ else {
+ test.fail('page-mod attached to a non-top document');
+ }
+ }
+ });
+
+ require('sdk/tabs').open(topDocumentURL);
+};
+
+exports['test111 attachTo [frame]'] = function(test) {
+ test.waitUntilDone();
+
+ let { PageMod } = require('sdk/page-mod');
+
+ let subFrameURL = 'data:text/html;charset=utf-8,subframe';
+ let subContent = '<iframe src="' + subFrameURL + '" />';
+ let frameURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(subContent);
+ let content = '<iframe src="' + frameURL + '" />';
+ let topDocumentURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(content)
+
+ let workerCount = 0, messageCount = 0;
+
+ function onMessage(href) {
+ if (href == frameURL)
+ test.pass("worker on first frame");
+ else if (href == subFrameURL)
+ test.pass("worker on second frame");
+ else
+ test.fail("worker on unexpected document: " + href);
+ this.destroy();
+ if (++messageCount == 2) {
+ mod.destroy();
+ test.done();
+ }
+ }
+ let mod = PageMod({
+ include: 'data:text/html*',
+ contentScriptWhen: 'start',
+ contentScript: 'self.postMessage(document.location.href);',
+ attachTo: ['frame'],
+ onAttach: function onAttach(worker) {
+ if (++workerCount <= 2) {
+ worker.on('message', onMessage);
+ }
+ else {
+ test.fail('page-mod attached to a non-frame document');
+ }
+ }
+ });
+
+ require('sdk/tabs').open(topDocumentURL);
+};
+
+exports.testContentScriptOptionsOption = function(test) {
+ test.waitUntilDone();
+
+ let callbackDone = null;
+ testPageMod(test, "about:", [{
+ include: "about:*",
+ contentScript: "self.postMessage( [typeof self.options.d, self.options] );",
+ contentScriptWhen: "end",
+ contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}},
+ onAttach: function(worker) {
+ worker.on('message', function(msg) {
+ test.assertEqual( msg[0], 'undefined', 'functions are stripped from contentScriptOptions' );
+ test.assertEqual( typeof msg[1], 'object', 'object as contentScriptOptions' );
+ test.assertEqual( msg[1].a, true, 'boolean in contentScriptOptions' );
+ test.assertEqual( msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions' );
+ test.assertEqual( msg[1].c, 'string', 'string in contentScriptOptions' );
+ callbackDone();
+ });
+ }
+ }],
+ function(win, done) {
+ callbackDone = done;
+ }
+ );
+};
+
+exports.testPageModCss = function(test) {
+ let [pageMod] = testPageMod(test,
+ 'data:text/html;charset=utf-8,<div style="background: silver">css test</div>', [{
+ include: "data:*",
+ contentStyle: "div { height: 100px; }",
+ contentStyleFile:
+ require("sdk/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;charset=utf-8,<div style="width:320px; max-width: 480px!important">css test</div>', [{
+ include: "data:*",
+ contentStyleFile: [
+ // Highlight evaluation order in this list
+ "data:text/css;charset=utf-8,div { border: 1px solid black; }",
+ "data:text/css;charset=utf-8,div { border: 10px solid black; }",
+ // Highlight evaluation order between contentStylesheet & contentStylesheetFile
+ "data:text/cs;charset=utf-8s,div { height: 1000px; }",
+ // Highlight precedence between the author and user style sheet
+ "data:text/css;charset=utf-8,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;charset=utf-8,<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();
+
+ }
+ );
+};
+
+exports.testPageModCssAutomaticDestroy = function(test) {
+ test.waitUntilDone();
+ let loader = Loader(module);
+
+ let pageMod = loader.require("page-mod").PageMod({
+ include: "data:*",
+ contentStyle: "div { width: 100px!important; }"
+ });
+
+ tabs.open({
+ url: "data:text/html;charset=utf-8,<div style='width:200px'>css test</div>",
+
+ onReady: function onReady(tab) {
+ let browserWindow = windowUtils.activeBrowserWindow;
+ let win = getTabContentWindow(getActiveTab(browserWindow));
+
+ let div = win.document.querySelector("div"),
+ style = win.getComputedStyle(div);
+
+ test.assertEqual(
+ style.width,
+ "100px",
+ "PageMod contentStyle worked"
+ );
+
+ loader.unload();
+
+ test.assertEqual(
+ style.width,
+ "200px",
+ "PageMod contentStyle is removed after loader's unload"
+ );
+
+ tab.close();
+ test.done();
+ }
+ });
+};
+
+
+exports.testPageModTimeout = function(test) {
+ test.waitUntilDone();
+ let tab = null
+ let loader = Loader(module);
+ let { PageMod } = loader.require("page-mod");
+
+ let mod = PageMod({
+ include: "data:*",
+ contentScript: Isolate(function() {
+ var id = setTimeout(function() {
+ self.port.emit("fired", id)
+ }, 10)
+ self.port.emit("scheduled", id);
+ }),
+ onAttach: function(worker) {
+ worker.port.on("scheduled", function(id) {
+ test.pass("timer was scheduled")
+ worker.port.on("fired", function(data) {
+ test.assertEqual(id, data, "timer was fired")
+ tab.close()
+ worker.destroy()
+ loader.unload()
+ test.done()
+ })
+ })
+ }
+ });
+
+ tabs.open({
+ url: "data:text/html;charset=utf-8,timeout",
+ onReady: function($) { tab = $ }
+ })
+}
+
+
+exports.testPageModcancelTimeout = function(test) {
+ test.waitUntilDone();
+ let tab = null
+ let loader = Loader(module);
+ let { PageMod } = loader.require("page-mod");
+
+ let mod = PageMod({
+ include: "data:*",
+ contentScript: Isolate(function() {
+ var id1 = setTimeout(function() {
+ self.port.emit("failed")
+ }, 10)
+ var id2 = setTimeout(function() {
+ self.port.emit("timeout")
+ }, 100)
+ clearTimeout(id1)
+ }),
+ onAttach: function(worker) {
+ worker.port.on("failed", function() {
+ test.fail("cancelled timeout fired")
+ })
+ worker.port.on("timeout", function(id) {
+ test.pass("timer was scheduled")
+ tab.close()
+ worker.destroy()
+ loader.unload()
+ test.done()
+ })
+ }
+ });
+
+ tabs.open({
+ url: "data:text/html;charset=utf-8,cancell timeout",
+ onReady: function($) { tab = $ }
+ })
+}
+
+exports.testBug803529 = function(test) {
+ test.waitUntilDone();
+
+ let subIFrame = '<iframe src="data:text/html;charset=utf-8,sub frame" />'
+ let iFrame = '<iframe src="data:text/html;charset=utf-8,' + encodeURIComponent(subIFrame) + '" />';
+ let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(iFrame)
+
+ let counter = 0;
+ let tab = openTab(getMostRecentBrowserWindow(), url);
+ let window = getTabContentWindow(tab);
+
+ function wait4Iframes() {
+ if (window.document.readyState != "complete" ||
+ getFrames(window).length != 2) {
+ return;
+ }
+
+ let pagemod = pageMod.PageMod({
+ include: ["*", "data:*"],
+ attachTo: ["existing", "frame"],
+ contentScriptWhen: 'ready',
+ onAttach: function(mod) {
+ if (++counter != 2) return;
+ test.pass('page mod attached to iframe');
+ timer.setTimeout(function() {
+ pagemod.destroy();
+ closeTab(tab);
+ test.done();
+ }, 0);
+ }
+ });
+ }
+
+ window.addEventListener("load", wait4Iframes, false);
+};
+
+exports.testIFramePostMessage = function(test) {
+ test.waitUntilDone();
+
+ tabs.open({
+ url: data.url("test-iframe.html"),
+ onReady: function(tab) {
+ var worker = tab.attach({
+ contentScriptFile: data.url('test-iframe.js'),
+ contentScript: ' var iframePath = \'' + data.url('test-iframe-postmessage.html') + '\'',
+ onMessage: function(msg) {
+ test.assertEqual(msg.first, 'a string');
+ test.assert(msg.second[1], "array");
+ test.assertEqual(typeof msg.third, 'object');
+
+ worker.destroy();
+ tab.close(function() test.done());
+ }
+ });
+ }
+ });
+};
diff --git a/tools/addon-sdk-1.12/test/test-page-worker.js b/tools/addon-sdk-1.12/test/test-page-worker.js
new file mode 100644
index 0000000..469c19b
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-page-worker.js
@@ -0,0 +1,384 @@
+/* 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('sdk/test/loader');
+
+const ERR_DESTROYED =
+ "Couldn't find the worker to receive this message. " +
+ "The script may not be initialized yet, or may already have been unloaded.";
+
+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;charset=utf-8,<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;charset=utf-8,<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("sdk/page-worker");
+ let global = loader.sandbox("sdk/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("sdk/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;charset=utf-8,<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;charset=utf-8,<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;charset=utf-8,<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;charset=utf-8,<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;charset=utf-8,<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("sdk/self").data.url("test-page-worker.html"),
+ contentScriptFile: require("sdk/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;charset=utf-8,<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;charset=utf-8,<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;charset=utf-8,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");
+};
+
+exports.testContentScriptOptionsOption = function(test) {
+ test.waitUntilDone();
+
+ let page = new Page({
+ contentScript: "self.postMessage( [typeof self.options.d, self.options] );",
+ contentScriptWhen: "end",
+ contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}},
+ onMessage: function(msg) {
+ test.assertEqual( msg[0], 'undefined', 'functions are stripped from contentScriptOptions' );
+ test.assertEqual( typeof msg[1], 'object', 'object as contentScriptOptions' );
+ test.assertEqual( msg[1].a, true, 'boolean in contentScriptOptions' );
+ test.assertEqual( msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions' );
+ test.assertEqual( msg[1].c, 'string', 'string in contentScriptOptions' );
+ test.done();
+ }
+ });
+};
+
+function isDestroyed(page) {
+ try {
+ page.postMessage("foo");
+ }
+ catch (err if err.message == ERR_DESTROYED) {
+ return true;
+ }
+ return false;
+}
+
+
+let pageWorkerSupported = true;
+
+try {
+ Pages = require("sdk/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.12/test/test-panel.js b/tools/addon-sdk-1.12/test/test-panel.js
new file mode 100644
index 0000000..bdce83d
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-panel.js
@@ -0,0 +1,486 @@
+/* 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('sdk/panel');
+let tests = {}, panels, Panel;
+const { Loader } = require('sdk/test/loader');
+const timer = require("sdk/timers");
+
+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;charset=utf-8," + 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;charset=utf-8," + encodeURIComponent(content),
+ contentScript: "self.on('message', function(message){" +
+ " if (message=='resize') " +
+ " unsafeWindow.contentResize();" +
+ "});",
+ contentScriptWhen: "ready",
+ onMessage: function (message) {
+
+ },
+ onShow: function () {
+ panel.postMessage('resize');
+ 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;charset=utf-8,<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("sdk/tabs");
+ let url = 'data:text/html;charset=utf-8,' +
+ '<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;charset=utf-8," + 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)
+ 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("sdk/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;charset=utf-8," + 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.");
+};
+
+exports.testContentScriptOptionsOption = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let panel = loader.require("sdk/panel").Panel({
+ contentScript: "self.postMessage( [typeof self.options.d, self.options] );",
+ contentScriptWhen: "end",
+ contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}},
+ onMessage: function(msg) {
+ test.assertEqual( msg[0], 'undefined', 'functions are stripped from contentScriptOptions' );
+ test.assertEqual( typeof msg[1], 'object', 'object as contentScriptOptions' );
+ test.assertEqual( msg[1].a, true, 'boolean in contentScriptOptions' );
+ test.assertEqual( msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions' );
+ test.assertEqual( msg[1].c, 'string', 'string in contentScriptOptions' );
+ test.done();
+ }
+ });
+};
+
+let panelSupported = true;
+
+try {
+ panels = require("sdk/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.12/test/test-passwords-utils.js b/tools/addon-sdk-1.12/test/test-passwords-utils.js
new file mode 100644
index 0000000..3773090
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-passwords-utils.js
@@ -0,0 +1,142 @@
+/* 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("sdk/passwords/utils");
+
+exports["test store requires `password` field"] = function(assert) {
+ assert.throws(function() {
+ store({ username: "foo", realm: "bar" });
+ }, "`password` is required");
+};
+
+exports["test store requires `username` field"] = function(assert) {
+ assert.throws(function() {
+ store({ password: "foo", realm: "bar" });
+ }, "`username` is required");
+};
+
+exports["test store requires `realm` field"] = function(assert) {
+ assert.throws(function() {
+ store({ username: "foo", password: "bar" });
+ }, "`password` is required");
+};
+
+exports["test can't store same login twice"] = function(assert) {
+ let options = { username: "user", password: "pass", realm: "realm" };
+ store(options);
+ assert.throws(function() {
+ store(options);
+ }, "can't store same pass twice");
+ remove(options);
+};
+
+exports["test remove throws if no login found"] = function(assert) {
+ assert.throws(function() {
+ remove({ username: "foo", password: "bar", realm: "baz" });
+ }, "can't remove unstored credentials");
+};
+
+exports["test addon associated credentials"] = function(assert) {
+ let options = { username: "foo", password: "bar", realm: "baz" };
+ store(options);
+
+ assert.ok(search().length, "credential was stored");
+ assert.ok(search(options).length, "stored credential found");
+ assert.ok(search({ username: options.username }).length, "found by username");
+ assert.ok(search({ password: options.password }).length, "found by password");
+ assert.ok(search({ realm: options.realm }).length, "found by realm");
+
+ let credential = search(options)[0];
+ assert.equal(credential.url.indexOf("addon:"), 0,
+ "`addon:` uri is used for add-on associated credentials");
+ assert.equal(credential.username, options.username, "username matches");
+ assert.equal(credential.password, options.password, "password matches");
+ assert.equal(credential.realm, options.realm, "realm matches");
+ assert.equal(credential.formSubmitURL, null,
+ "`formSubmitURL` is `null` for add-on associated credentials");
+ assert.equal(credential.usernameField, "", "usernameField is empty");
+ assert.equal(credential.passwordField, "", "passwordField is empty");
+
+ remove(search(options)[0]);
+ assert.ok(!search(options).length, "remove worked");
+};
+
+exports["test web page associated credentials"] = function(assert) {
+ let options = {
+ url: "http://www.example.com",
+ formSubmitURL: "http://login.example.com",
+ username: "user",
+ password: "pass",
+ usernameField: "user-f",
+ passwordField: "pass-f"
+ };
+ store({
+ url: "http://www.example.com/login",
+ formSubmitURL: "http://login.example.com/foo/authenticate.cgi",
+ username: options.username,
+ password: options.password,
+ usernameField: options.usernameField,
+ passwordField: options.passwordField
+ });
+
+ assert.ok(search().length, "credential was stored");
+ assert.ok(search(options).length, "stored credential found");
+ assert.ok(search({ username: options.username }).length, "found by username");
+ assert.ok(search({ password: options.password }).length, "found by password");
+ assert.ok(search({ formSubmitURL: options.formSubmitURL }).length,
+ "found by formSubmitURL");
+ assert.ok(search({ usernameField: options.usernameField }).length,
+ "found by usernameField");
+ assert.ok(search({ passwordField: options.passwordField }).length,
+ "found by passwordField");
+
+ let credential = search(options)[0];
+ assert.equal(credential.url, options.url, "url matches");
+ assert.equal(credential.username, options.username, "username matches");
+ assert.equal(credential.password, options.password, "password matches");
+ assert.equal(credential.realm, null, "realm is ");
+ assert.equal(credential.formSubmitURL, options.formSubmitURL,
+ "`formSubmitURL` matches");
+ assert.equal(credential.usernameField, options.usernameField,
+ "usernameField matches");
+ assert.equal(credential.passwordField, options.passwordField,
+ "passwordField matches");
+
+ remove(search(options)[0]);
+ assert.ok(!search(options).length, "remove worked");
+};
+
+exports["test site authentication credentials"] = function(assert) {
+ let options = {
+ url: "http://test.authentication.com",
+ username: "u",
+ password: "p",
+ realm: "r"
+ };
+
+ store(options);
+ assert.ok(search().length, "credential was stored");
+ assert.ok(search(options).length, "stored credential found");
+ assert.ok(search({ username: options.username }).length, "found by username");
+ assert.ok(search({ password: options.password }).length, "found by password");
+ assert.ok(search({ realm: options.realm }).length, "found by realm");
+ assert.ok(search({ url: options.url }).length, "found by url");
+
+ let credential = search(options)[0];
+ assert.equal(credential.url, options.url, "url matches");
+ assert.equal(credential.username, options.username, "username matches");
+ assert.equal(credential.password, options.password, "password matches");
+ assert.equal(credential.realm, options.realm, "realm matches");
+ assert.equal(credential.formSubmitURL, null,
+ "`formSubmitURL` is `null` for site authentication credentials");
+ assert.equal(credential.usernameField, "", "usernameField is empty");
+ assert.equal(credential.passwordField, "", "passwordField is empty");
+
+ remove(search(options)[0]);
+ assert.ok(!search(options).length, "remove worked");
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-passwords.js b/tools/addon-sdk-1.12/test/test-passwords.js
new file mode 100644
index 0000000..04e2181
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-passwords.js
@@ -0,0 +1,280 @@
+/* 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("sdk/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.12/test/test-plain-text-console.js b/tools/addon-sdk-1.12/test/test-plain-text-console.js
new file mode 100644
index 0000000..ce41b07
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-plain-text-console.js
@@ -0,0 +1,68 @@
+/* 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/. */
+
+exports.testPlainTextConsole = function(test) {
+ var prints = [];
+ function print(message) {
+ prints.push(message);
+ }
+ function lastPrint() {
+ var last = prints.slice(-1)[0];
+ prints = [];
+ return last;
+ }
+
+ var Console = require("sdk/console/plain-text").PlainTextConsole;
+ var con = new Console(print);
+
+ test.pass("PlainTextConsole instantiates");
+
+ con.log('testing', 1, [2, 3, 4]);
+ test.assertEqual(lastPrint(), "info: " + require("sdk/self").name + ": testing 1 2,3,4\n",
+ "PlainTextConsole.log() must work.");
+
+ con.info('testing', 1, [2, 3, 4]);
+ test.assertEqual(lastPrint(), "info: " + require("sdk/self").name + ": testing 1 2,3,4\n",
+ "PlainTextConsole.info() must work.");
+
+ con.warn('testing', 1, [2, 3, 4]);
+ test.assertEqual(lastPrint(), "warning: " + require("sdk/self").name + ": testing 1 2,3,4\n",
+ "PlainTextConsole.warn() must work.");
+
+ con.error('testing', 1, [2, 3, 4]);
+ test.assertEqual(lastPrint(), "error: " + require("sdk/self").name + ": testing 1 2,3,4\n",
+ "PlainTextConsole.error() must work.");
+
+ con.debug('testing', 1, [2, 3, 4]);
+ test.assertEqual(lastPrint(), "debug: " + require("sdk/self").name + ": testing 1 2,3,4\n",
+ "PlainTextConsole.debug() must work.");
+
+ con.log('testing', undefined);
+ test.assertEqual(lastPrint(), "info: " + require("sdk/self").name + ": testing undefined\n",
+ "PlainTextConsole.log() must stringify undefined.");
+
+ con.log('testing', null);
+ test.assertEqual(lastPrint(), "info: " + require("sdk/self").name + ": testing null\n",
+ "PlainTextConsole.log() must stringify null.");
+
+ con.log("testing", { toString: function() "obj.toString()" });
+ test.assertEqual(lastPrint(), "info: " + require("sdk/self").name + ": testing obj.toString()\n",
+ "PlainTextConsole.log() must stringify custom toString.");
+
+ con.log("testing", { toString: function() { throw "fail!"; } });
+ test.assertEqual(lastPrint(), "info: " + require("sdk/self").name + ": testing <toString() error>\n",
+ "PlainTextConsole.log() must stringify custom bad toString.");
+
+ con.exception(new Error("blah"));
+ var tbLines = prints[0].split("\n");
+ test.assertEqual(tbLines[0], "error: " + require("sdk/self").name + ": An exception occurred.");
+ test.assertEqual(tbLines[1], "Traceback (most recent call last):");
+ test.assertEqual(tbLines.slice(-2)[0], "Error: blah");
+
+ prints = [];
+ con.trace();
+ tbLines = prints[0].split("\n");
+ test.assertEqual(tbLines[0], "info: " + require("sdk/self").name + ": Traceback (most recent call last):");
+ test.assertEqual(tbLines.slice(-2)[0].trim(), "con.trace();");
+};
diff --git a/tools/addon-sdk-1.12/test/test-preferences-service.js b/tools/addon-sdk-1.12/test/test-preferences-service.js
new file mode 100644
index 0000000..89cb2f5
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-preferences-service.js
@@ -0,0 +1,143 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const prefs = require("sdk/preferences/service");
+const Branch = prefs.Branch;
+const { Cc, Ci, Cu } = require("chrome");
+const BundleService = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
+
+const specialChars = "!@#$%^&*()_-=+[]{}~`\'\"<>,./?;:";
+
+exports.testReset = function(test) {
+ prefs.reset("test_reset_pref");
+ test.assertEqual(prefs.has("test_reset_pref"), false);
+ test.assertEqual(prefs.isSet("test_reset_pref"), false);
+ prefs.set("test_reset_pref", 5);
+ test.assertEqual(prefs.has("test_reset_pref"), true);
+ test.assertEqual(prefs.isSet("test_reset_pref"), true);
+ test.assertEqual(prefs.keys("test_reset_pref").toString(), "test_reset_pref");
+};
+
+exports.testGetAndSet = function(test) {
+ let svc = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService).
+ getBranch(null);
+ svc.setCharPref("test_set_get_pref", "a normal string");
+ test.assertEqual(prefs.get("test_set_get_pref"), "a normal string",
+ "preferences-service should read from " +
+ "application-wide preferences service");
+
+ prefs.set("test_set_get_pref.integer", 1);
+ test.assertEqual(prefs.get("test_set_get_pref.integer"), 1,
+ "set/get integer preference should work");
+
+ test.assertEqual(
+ prefs.keys("test_set_get_pref").sort().toString(),
+ ["test_set_get_pref.integer","test_set_get_pref"].sort().toString());
+
+ prefs.set("test_set_get_number_pref", 42);
+ test.assertRaises(
+ function() { prefs.set("test_set_get_number_pref", 3.14159); },
+ "cannot store non-integer number: 3.14159",
+ "setting a float preference should raise an error"
+ );
+ test.assertEqual(prefs.get("test_set_get_number_pref"), 42,
+ "bad-type write attempt should not overwrite");
+
+ // 0x80000000 (no), 0x7fffffff (yes), -0x80000000 (yes), -0x80000001 (no)
+ test.assertRaises(
+ function() { prefs.set("test_set_get_number_pref", Math.pow(2, 31)); },
+ ("you cannot set the test_set_get_number_pref pref to the number " +
+ "2147483648, as number pref values must be in the signed 32-bit " +
+ "integer range -(2^31) to 2^31-1. To store numbers outside that " +
+ "range, store them as strings."),
+ "setting an int pref outside the range -(2^31) to 2^31-1 shouldn't work"
+ );
+ test.assertEqual(prefs.get("test_set_get_number_pref"), 42,
+ "out-of-range write attempt should not overwrite 1");
+ prefs.set("test_set_get_number_pref", Math.pow(2, 31)-1);
+ test.assertEqual(prefs.get("test_set_get_number_pref"), 0x7fffffff,
+ "in-range write attempt should work 1");
+ prefs.set("test_set_get_number_pref", -Math.pow(2, 31));
+ test.assertEqual(prefs.get("test_set_get_number_pref"), -0x80000000,
+ "in-range write attempt should work 2");
+ test.assertRaises(
+ function() { prefs.set("test_set_get_number_pref", -0x80000001); },
+ ("you cannot set the test_set_get_number_pref pref to the number " +
+ "-2147483649, as number pref values must be in the signed 32-bit " +
+ "integer range -(2^31) to 2^31-1. To store numbers outside that " +
+ "range, store them as strings."),
+ "setting an int pref outside the range -(2^31) to 2^31-1 shouldn't work"
+ );
+ test.assertEqual(prefs.get("test_set_get_number_pref"), -0x80000000,
+ "out-of-range write attempt should not overwrite 2");
+
+
+ prefs.set("test_set_get_pref.string", "foo");
+ test.assertEqual(prefs.get("test_set_get_pref.string"), "foo",
+ "set/get string preference should work");
+
+ prefs.set("test_set_get_pref.boolean", true);
+ test.assertEqual(prefs.get("test_set_get_pref.boolean"), true,
+ "set/get boolean preference should work");
+
+ prefs.set("test_set_get_unicode_pref", String.fromCharCode(960));
+ test.assertEqual(prefs.get("test_set_get_unicode_pref"),
+ String.fromCharCode(960),
+ "set/get unicode preference should work");
+
+ var unsupportedValues = [null, [], undefined];
+ unsupportedValues.forEach(
+ function(value) {
+ test.assertRaises(
+ function() { prefs.set("test_set_pref", value); },
+ ("can't set pref test_set_pref to value '" + value + "'; " +
+ "it isn't a string, integer, or boolean"),
+ "Setting a pref to " + uneval(value) + " should raise error"
+ );
+ });
+};
+
+exports.testPrefClass = function(test) {
+ var branch = Branch("test_foo");
+
+ test.assertEqual(branch.test, undefined, "test_foo.test is undefined");
+ branch.test = true;
+ test.assertEqual(branch.test, true, "test_foo.test is true");
+ delete branch.test;
+ test.assertEqual(branch.test, undefined, "test_foo.test is undefined");
+};
+
+exports.testGetSetLocalized = function(test) {
+ let prefName = "general.useragent.locale";
+
+ // Ensure that "general.useragent.locale" is a 'localized' pref
+ let bundleURL = "chrome://global/locale/intl.properties";
+ prefs.setLocalized(prefName, bundleURL);
+
+ // Fetch the expected value directly from the property file
+ let expectedValue = BundleService.createBundle(bundleURL).
+ GetStringFromName(prefName).
+ toLowerCase();
+
+ test.assertEqual(prefs.getLocalized(prefName).toLowerCase(),
+ expectedValue,
+ "get localized preference");
+
+ // Undo our modification
+ prefs.reset(prefName);
+}
+
+// TEST: setting and getting preferences with special characters work
+exports.testSpecialChars = function(test) {
+ let chars = specialChars.split('');
+ const ROOT = "test.";
+
+ chars.forEach(function(char) {
+ let rand = Math.random() + "";
+ prefs.set(ROOT+char, rand);
+ test.assertEqual(prefs.get(ROOT+char), rand, "setting pref with a name that is a special char, " + char + ", worked!");
+ });
+};
diff --git a/tools/addon-sdk-1.12/test/test-preferences-target.js b/tools/addon-sdk-1.12/test/test-preferences-target.js
new file mode 100644
index 0000000..b2bb077
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-preferences-target.js
@@ -0,0 +1,42 @@
+/* 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 { PrefsTarget } = require('sdk/preferences/event-target');
+const { get, set, reset } = require('sdk/preferences/service');
+const { Loader } = require('sdk/test/loader');
+const { setTimeout } = require('sdk/timers');
+
+const root = PrefsTarget();
+
+exports.testPrefsTarget = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let pt = loader.require('sdk/preferences/event-target').PrefsTarget({});
+ let name = 'test';
+
+ test.assertEqual(get(name, ''), '', 'test pref is blank');
+
+ pt.once(name, function() {
+ test.assertEqual(pt.prefs[name], 2, 'test pref is 2');
+
+ pt.once(name, function() {
+ test.fail('should not have heard a pref change');
+ });
+ loader.unload();
+ root.once(name, function() {
+ test.pass('test pref was changed');
+ reset(name);
+
+ // NOTE: using setTimeout to make sure that the other listener had
+ // a chance to fail
+ // end test
+ setTimeout(function() test.done());
+ });
+ set(name, 3);
+ });
+
+ pt.prefs[name] = 2;
+};
diff --git a/tools/addon-sdk-1.12/test/test-private-browsing.js b/tools/addon-sdk-1.12/test/test-private-browsing.js
new file mode 100644
index 0000000..de73892
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/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/. */
+"use strict";
+
+let { Cc,Ci } = require("chrome");
+const timer = require("sdk/timers");
+const { LoaderWithHookedConsole, pb } = require("private-browsing-helper");
+
+let pbService;
+// Currently, only Firefox implements the private browsing service.
+if (require("sdk/system/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, errors } = LoaderWithHookedConsole();
+ let pb2 = loader.require("sdk/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() {
+ 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("sdk/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.12/test/test-promise.js b/tools/addon-sdk-1.12/test/test-promise.js
new file mode 100644
index 0000000..744baeb
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-promise.js
@@ -0,0 +1,327 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/*jshint asi: true undef: true es5: true node: true devel: true
+ forin: true */
+/*global define: true */
+
+'use strict';
+
+var core = require('sdk/core/promise'),
+ defer = core.defer, resolve = core.resolve, reject = core.reject,
+ promised = core.promised
+
+exports['test all observers are notified'] = function(assert, done) {
+ var expected = 'Taram pam param!'
+ var deferred = defer()
+ var pending = 10, i = 0
+
+ function resolved(value) {
+ assert.equal(value, expected, 'value resolved as expected: #' + pending)
+ if (!--pending) done()
+ }
+
+ while (i++ < pending) deferred.promise.then(resolved)
+
+ deferred.resolve(expected)
+}
+
+exports['test exceptions dont stop notifications'] = function(assert, done) {
+ var threw = false, boom = Error('Boom!')
+ var deferred = defer()
+
+ var promise2 = deferred.promise.then(function() {
+ threw = true
+ throw boom
+ })
+
+ deferred.promise.then(function() {
+ assert.ok(threw, 'observer is called even though previos one threw')
+ promise2.then(function() {
+ assert.fail('should not resolve')
+ }, function(reason) {
+ assert.equal(reason, boom, 'rejects to thrown error')
+ done()
+ })
+ })
+
+ deferred.resolve('go!')
+}
+
+exports['test subsequent resolves are ignored'] = function(assert, done) {
+ var deferred = defer()
+ deferred.resolve(1)
+ deferred.resolve(2)
+ deferred.reject(3)
+
+ deferred.promise.then(function(actual) {
+ assert.equal(actual, 1, 'resolves to first value')
+ }, function() {
+ assert.fail('must not reject')
+ })
+ deferred.promise.then(function(actual) {
+ assert.equal(actual, 1, 'subsequent resolutions are ignored')
+ done()
+ }, function() {
+ assert.fail('must not reject')
+ })
+}
+
+exports['test subsequent rejections are ignored'] = function(assert, done) {
+ var deferred = defer()
+ deferred.reject(1)
+ deferred.resolve(2)
+ deferred.reject(3)
+
+ deferred.promise.then(function(actual) {
+ assert.fail('must not resolve')
+ }, function(actual) {
+ assert.equal(actual, 1, 'must reject to first')
+ })
+ deferred.promise.then(function(actual) {
+ assert.fail('must not resolve')
+ }, function(actual) {
+ assert.equal(actual, 1, 'must reject to first')
+ done()
+ })
+}
+
+exports['test error recovery'] = function(assert, done) {
+ var boom = Error('Boom!')
+ var deferred = defer()
+
+ deferred.promise.then(function() {
+ assert.fail('rejected promise should not resolve')
+ }, function(reason) {
+ assert.equal(reason, boom, 'rejection reason delivered')
+ return 'recovery'
+ }).then(function(value) {
+ assert.equal(value, 'recovery', 'error handled by a handler')
+ done()
+ })
+
+ deferred.reject(boom)
+}
+
+
+exports['test error recovery with promise'] = function(assert, done) {
+ var deferred = defer()
+
+ deferred.promise.then(function() {
+ assert.fail('must reject')
+ }, function(actual) {
+ assert.equal(actual, 'reason', 'rejected')
+ var deferred = defer()
+ deferred.resolve('recovery')
+ return deferred.promise
+ }).then(function(actual) {
+ assert.equal(actual, 'recovery', 'recorvered via promise')
+ var deferred = defer()
+ deferred.reject('error')
+ return deferred.promise
+ }).then(null, function(actual) {
+ assert.equal(actual, 'error', 'rejected via promise')
+ var deferred = defer()
+ deferred.reject('end')
+ return deferred.promise
+ }).then(null, function(actual) {
+ assert.equal(actual, 'end', 'rejeced via promise')
+ done()
+ })
+
+ deferred.reject('reason')
+}
+
+exports['test propagation'] = function(assert, done) {
+ var d1 = defer(), d2 = defer(), d3 = defer()
+
+ d1.promise.then(function(actual) {
+ assert.equal(actual, 'expected', 'resolves to expected value')
+ done()
+ })
+
+ d1.resolve(d2.promise)
+ d2.resolve(d3.promise)
+ d3.resolve('expected')
+}
+
+exports['test chaining'] = function(assert, done) {
+ var boom = Error('boom'), brax = Error('braxXXx')
+ var deferred = defer()
+
+ deferred.promise.then().then().then(function(actual) {
+ assert.equal(actual, 2, 'value propagates unchanged')
+ return actual + 2
+ }).then(null, function(reason) {
+ assert.fail('should not reject')
+ }).then(function(actual) {
+ assert.equal(actual, 4, 'value propagates through if not handled')
+ throw boom
+ }).then(function(actual) {
+ assert.fail('exception must reject promise')
+ }).then().then(null, function(actual) {
+ assert.equal(actual, boom, 'reason propagates unchanged')
+ throw brax
+ }).then().then(null, function(actual) {
+ assert.equal(actual, brax, 'reason changed becase of exception')
+ return 'recovery'
+ }).then(function(actual) {
+ assert.equal(actual, 'recovery', 'recovered from error')
+ done()
+ })
+
+ deferred.resolve(2)
+}
+
+
+exports['test reject'] = function(assert, done) {
+ var expected = Error('boom')
+
+ reject(expected).then(function() {
+ assert.fail('should reject')
+ }, function(actual) {
+ assert.equal(actual, expected, 'rejected with expected reason')
+ }).then(function() {
+ done()
+ })
+}
+
+exports['test resolve to rejected'] = function(assert, done) {
+ var expected = Error('boom')
+ var deferred = defer()
+
+ deferred.promise.then(function() {
+ assert.fail('should reject')
+ }, function(actual) {
+ assert.equal(actual, expected, 'rejected with expected failure')
+ }).then(function() {
+ done()
+ })
+
+ deferred.resolve(reject(expected))
+}
+
+exports['test resolve'] = function(assert, done) {
+ var expected = 'value'
+ resolve(expected).then(function(actual) {
+ assert.equal(actual, expected, 'resolved as expected')
+ }).then(function() {
+ done()
+ })
+}
+
+exports['test resolve with prototype'] = function(assert, done) {
+ var seventy = resolve(70, {
+ subtract: function subtract(y) {
+ return this.then(function(x) { return x - y })
+ }
+ })
+
+ seventy.subtract(17).then(function(actual) {
+ assert.equal(actual, 70 - 17, 'resolves to expected')
+ done()
+ })
+}
+
+exports['test promised with normal args'] = function(assert, done) {
+ var sum = promised(function(x, y) { return x + y })
+
+ sum(7, 8).then(function(actual) {
+ assert.equal(actual, 7 + 8, 'resolves as expected')
+ done()
+ })
+}
+
+exports['test promised with promise args'] = function(assert, done) {
+ var sum = promised(function(x, y) { return x + y })
+ var deferred = defer()
+
+ sum(11, deferred.promise).then(function(actual) {
+ assert.equal(actual, 11 + 24, 'resolved as expected')
+ done()
+ })
+
+ deferred.resolve(24)
+}
+
+exports['test promised with prototype'] = function(assert, done) {
+ var deferred = defer()
+ var numeric = {}
+ numeric.subtract = promised(function(y) { return this - y }, numeric)
+
+ var sum = promised(function(x, y) { return x + y }, numeric)
+
+ sum(7, 70).
+ subtract(14).
+ subtract(deferred.promise).
+ subtract(5).
+ then(function(actual) {
+ assert.equal(actual, 7 + 70 - 14 - 23 - 5, 'resolved as expected')
+ done()
+ })
+
+ deferred.resolve(23)
+}
+
+exports['test promised error handleing'] = function(assert, done) {
+ var expected = Error('boom')
+ var f = promised(function() {
+ throw expected
+ })
+
+ f().then(function() {
+ assert.fail('should reject')
+ }, function(actual) {
+ assert.equal(actual, expected, 'rejected as expected')
+ done()
+ })
+}
+
+exports['test return promise form promised'] = function(assert, done) {
+ var f = promised(function() {
+ return resolve(17)
+ })
+
+ f().then(function(actual) {
+ assert.equal(actual, 17, 'resolves to a promise resolution')
+ done()
+ })
+}
+
+exports['test promised returning failure'] = function(assert, done) {
+ var expected = Error('boom')
+ var f = promised(function() {
+ return reject(expected)
+ })
+
+ f().then(function() {
+ assert.fail('must reject')
+ }, function(actual) {
+ assert.equal(actual, expected, 'rejects with expected reason')
+ done()
+ })
+}
+
+exports['test promised are greedy'] = function(assert, done) {
+ var runs = 0
+ var f = promised(function() { ++runs })
+ var promise = f()
+ assert.equal(runs, 1, 'promised runs task right away')
+ done()
+}
+
+exports['test arrays should not flatten'] = function(assert, done) {
+ var a = defer()
+ var b = defer()
+
+ var combine = promised(function(str, arr) {
+ assert.equal(str, 'Hello', 'Array was not flattened')
+ assert.deepEqual(arr, [ 'my', 'friend' ])
+ })
+
+ combine(a.promise, b.promise).then(done)
+
+
+ a.resolve('Hello')
+ b.resolve([ 'my', 'friend' ])
+}
+
+require("test").run(exports)
diff --git a/tools/addon-sdk-1.12/test/test-querystring.js b/tools/addon-sdk-1.12/test/test-querystring.js
new file mode 100644
index 0000000..9c016cf
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-querystring.js
@@ -0,0 +1,206 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"use strict";
+
+// test using assert
+var qs = require('sdk/querystring');
+
+// folding block, commented to pass gjslint
+// {{{
+// [ wonkyQS, canonicalQS, obj ]
+var qsTestCases = [
+ ['foo=918854443121279438895193',
+ 'foo=918854443121279438895193',
+ {'foo': '918854443121279438895193'}],
+ ['foo=bar', 'foo=bar', {'foo': 'bar'}],
+ //['foo=bar&foo=quux', 'foo=bar&foo=quux', {'foo': ['bar', 'quux']}],
+ ['foo=1&bar=2', 'foo=1&bar=2', {'foo': '1', 'bar': '2'}],
+ // ['my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F',
+ // 'my%20weird%20field=q1!2%22\'w%245%267%2Fz8)%3F',
+ // {'my weird field': 'q1!2"\'w$5&7/z8)?' }],
+ ['foo%3Dbaz=bar', 'foo%3Dbaz=bar', {'foo=baz': 'bar'}],
+ ['foo=baz=bar', 'foo=baz%3Dbar', {'foo': 'baz=bar'}],
+ /*
+ ['str=foo&arr=1&arr=2&arr=3&somenull=&undef=',
+ 'str=foo&arr=1&arr=2&arr=3&somenull=&undef=',
+ { 'str': 'foo',
+ 'arr': ['1', '2', '3'],
+ 'somenull': '',
+ 'undef': ''}],
+ */
+ //[' foo = bar ', '%20foo%20=%20bar%20', {' foo ': ' bar '}],
+ // disable test that fails ['foo=%zx', 'foo=%25zx', {'foo': '%zx'}],
+ ['foo=%EF%BF%BD', 'foo=%EF%BF%BD', {'foo': '\ufffd' }]
+];
+
+// [ wonkyQS, canonicalQS, obj ]
+var qsColonTestCases = [
+ ['foo:bar', 'foo:bar', {'foo': 'bar'}],
+ //['foo:bar;foo:quux', 'foo:bar;foo:quux', {'foo': ['bar', 'quux']}],
+ ['foo:1&bar:2;baz:quux',
+ 'foo:1%26bar%3A2;baz:quux',
+ {'foo': '1&bar:2', 'baz': 'quux'}],
+ ['foo%3Abaz:bar', 'foo%3Abaz:bar', {'foo:baz': 'bar'}],
+ ['foo:baz:bar', 'foo:baz%3Abar', {'foo': 'baz:bar'}]
+];
+
+// [wonkyObj, qs, canonicalObj]
+var extendedFunction = function() {};
+extendedFunction.prototype = {a: 'b'};
+var qsWeirdObjects = [
+ //[{regexp: /./g}, 'regexp=', {'regexp': ''}],
+ //[{regexp: new RegExp('.', 'g')}, 'regexp=', {'regexp': ''}],
+ //[{fn: function() {}}, 'fn=', {'fn': ''}],
+ //[{fn: new Function('')}, 'fn=', {'fn': ''}],
+ //[{math: Math}, 'math=', {'math': ''}],
+ //[{e: extendedFunction}, 'e=', {'e': ''}],
+ //[{d: new Date()}, 'd=', {'d': ''}],
+ //[{d: Date}, 'd=', {'d': ''}],
+ //[{f: new Boolean(false), t: new Boolean(true)}, 'f=&t=', {'f': '', 't': ''}],
+ [{f: false, t: true}, 'f=false&t=true', {'f': 'false', 't': 'true'}],
+ //[{n: null}, 'n=', {'n': ''}],
+ //[{nan: NaN}, 'nan=', {'nan': ''}],
+ //[{inf: Infinity}, 'inf=', {'inf': ''}]
+];
+// }}}
+
+var qsNoMungeTestCases = [
+ ['', {}],
+ //['foo=bar&foo=baz', {'foo': ['bar', 'baz']}],
+ ['blah=burp', {'blah': 'burp'}],
+ //['gragh=1&gragh=3&goo=2', {'gragh': ['1', '3'], 'goo': '2'}],
+ ['frappucino=muffin&goat%5B%5D=scone&pond=moose',
+ {'frappucino': 'muffin', 'goat[]': 'scone', 'pond': 'moose'}],
+ ['trololol=yes&lololo=no', {'trololol': 'yes', 'lololo': 'no'}]
+];
+
+exports['test basic'] = function(assert) {
+ assert.strictEqual('918854443121279438895193',
+ qs.parse('id=918854443121279438895193').id,
+ 'prase id=918854443121279438895193');
+};
+
+exports['test that the canonical qs is parsed properly'] = function(assert) {
+ qsTestCases.forEach(function(testCase) {
+ assert.deepEqual(testCase[2], qs.parse(testCase[0]),
+ 'parse ' + testCase[0]);
+ });
+};
+
+
+exports['test that the colon test cases can do the same'] = function(assert) {
+ qsColonTestCases.forEach(function(testCase) {
+ assert.deepEqual(testCase[2], qs.parse(testCase[0], ';', ':'),
+ 'parse ' + testCase[0] + ' -> ; :');
+ });
+};
+
+exports['test the weird objects, that they get parsed properly'] = function(assert) {
+ qsWeirdObjects.forEach(function(testCase) {
+ assert.deepEqual(testCase[2], qs.parse(testCase[1]),
+ 'parse ' + testCase[1]);
+ });
+};
+
+exports['test non munge test cases'] = function(assert) {
+ qsNoMungeTestCases.forEach(function(testCase) {
+ //console.log(testCase[0], JSON.stringify(testCase[1]), qs.stringify(testCase[1], '&', '=', false));
+ assert.deepEqual(testCase[0], qs.stringify(testCase[1], '&', '=', false),
+ 'stringify ' + JSON.stringify(testCase[1]) + ' -> & =');
+ });
+};
+
+exports['test the nested qs-in-qs case'] = function(assert) {
+ var f = qs.parse('a=b&q=x%3Dy%26y%3Dz');
+ f.q = qs.parse(f.q);
+ assert.deepEqual(f, { a: 'b', q: { x: 'y', y: 'z' } },
+ 'parse a=b&q=x%3Dy%26y%3Dz');
+};
+
+exports['test nested in colon'] = function(assert) {
+ var f = qs.parse('a:b;q:x%3Ay%3By%3Az', ';', ':');
+ f.q = qs.parse(f.q, ';', ':');
+ assert.deepEqual(f, { a: 'b', q: { x: 'y', y: 'z' } },
+ 'parse a:b;q:x%3Ay%3By%3Az -> ; :');
+};
+
+exports['test stringifying'] = function(assert) {
+ qsTestCases.forEach(function(testCase) {
+ assert.equal(testCase[1], qs.stringify(testCase[2]),
+ 'stringify ' + JSON.stringify(testCase[2]));
+ });
+
+ qsColonTestCases.forEach(function(testCase) {
+ assert.equal(testCase[1], qs.stringify(testCase[2], ';', ':'),
+ 'stringify ' + JSON.stringify(testCase[2]) + ' -> ; :');
+ });
+
+ qsWeirdObjects.forEach(function(testCase) {
+ assert.equal(testCase[1], qs.stringify(testCase[0]),
+ 'stringify ' + JSON.stringify(testCase[0]));
+ });
+};
+
+exports['test stringifying nested'] = function(assert) {
+ var f = qs.stringify({
+ a: 'b',
+ q: qs.stringify({
+ x: 'y',
+ y: 'z'
+ })
+ });
+ assert.equal(f, 'a=b&q=x%3Dy%26y%3Dz',
+ JSON.stringify({
+ a: 'b',
+ 'qs.stringify -> q': {
+ x: 'y',
+ y: 'z'
+ }
+ }));
+
+ var threw = false;
+ try { qs.parse(undefined); } catch(error) { threw = true; }
+ assert.ok(!threw, "does not throws on undefined");
+};
+
+exports['test nested in colon'] = function(assert) {
+ var f = qs.stringify({
+ a: 'b',
+ q: qs.stringify({
+ x: 'y',
+ y: 'z'
+ }, ';', ':')
+ }, ';', ':');
+ assert.equal(f, 'a:b;q:x%3Ay%3By%3Az',
+ 'stringify ' + JSON.stringify({
+ a: 'b',
+ 'qs.stringify -> q': {
+ x: 'y',
+ y: 'z'
+ }
+ }) + ' -> ; : ');
+
+
+ assert.deepEqual({}, qs.parse(), 'parse undefined');
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-registry.js b/tools/addon-sdk-1.12/test/test-registry.js
new file mode 100644
index 0000000..13c127b
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-registry.js
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+exports['test:add'] = function(test) {
+ function Class() {}
+ let fixture = require('sdk/util/registry').Registry(Class);
+ let isAddEmitted = false;
+ fixture.on('add', function(item) {
+ test.assert(
+ item instanceof Class,
+ 'if object added is not an instance should construct instance from it'
+ );
+ test.assert(
+ fixture.has(item),
+ 'callback is called after instance is added'
+ );
+ test.assert(
+ !isAddEmitted,
+ 'callback should be called for the same item only once'
+ );
+ isAddEmitted = true;
+ });
+
+ let object = fixture.add({});
+ fixture.add(object);
+};
+
+exports['test:remove'] = function(test) {
+ function Class() {}
+ let fixture = require('sdk/util/registry').Registry(Class);
+ fixture.on('remove', function(item) {
+ test.assert(
+ item instanceof Class,
+ 'if object removed can be only instance of Class'
+ );
+ test.assert(
+ fixture.has(item),
+ 'callback is called before instance is removed'
+ );
+ test.assert(
+ !isRemoveEmitted,
+ 'callback should be called for the same item only once'
+ );
+ isRemoveEmitted = true;
+ });
+
+ fixture.remove({});
+ let object = fixture.add({});
+ fixture.remove(object);
+ fixture.remove(object);
+};
+
+exports['test:items'] = function(test) {
+ function Class() {}
+ let fixture = require('sdk/util/registry').Registry(Class),
+ actual,
+ times = 0;
+
+ function testItem(item) {
+ times ++;
+ test.assertEqual(
+ actual,
+ item,
+ 'item should match actual item being added/removed'
+ );
+ }
+
+ actual = fixture.add({});
+
+ fixture.on('add', testItem);
+ fixture.on('remove', testItem);
+
+ fixture.remove(actual);
+ fixture.remove(fixture.add(actual = new Class()));
+ test.assertEqual(3, times, 'should notify listeners on each call');
+}
+
diff --git a/tools/addon-sdk-1.12/test/test-request.js b/tools/addon-sdk-1.12/test/test-request.js
new file mode 100644
index 0000000..8d81ae2
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-request.js
@@ -0,0 +1,342 @@
+/* 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("sdk/request");
+const { pathFor } = require("sdk/system");
+const { startServerAsync } = require("sdk/test/httpd");
+const file = require("sdk/io/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;charset=utf-8,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 in o1) {
+ let val = o1[key];
+ e = e && key in o2 && equal(o2[key], val);
+ if (!e)
+ break;
+ }
+ for (let key in o2) {
+ let val = o2[key]
+ 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.12/test/test-require.js b/tools/addon-sdk-1.12/test/test-require.js
new file mode 100644
index 0000000..d7e790e
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-require.js
@@ -0,0 +1,29 @@
+/* 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 traceback = require("sdk/console/traceback");
+
+exports.test_no_args = function(test) {
+ var passed = false;
+ try {
+ var oops = require(); // leave this on line 6!
+ } catch(e) {
+ let msg = e.toString();
+ test.assertEqual(msg.indexOf("Error: you must provide a module name when calling require() from "), 0);
+ test.assertNotEqual(msg.indexOf("test-require"), -1, msg);
+ // we'd also like to assert that the right filename and linenumber is in
+ // the stack trace, but this currently doesn't work (see bugs 679591 and
+ // 551604)
+ if (0) {
+ let tb = traceback.fromException(e);
+ let lastFrame = tb[tb.length-1];
+ test.assertNotEqual(lastFrame.filename.indexOf("test-require.js"), -1,
+ lastFrame.filename);
+ test.assertEqual(lastFrame.lineNo, 6);
+ test.assertEqual(lastFrame.funcName, "??");
+ }
+ passed = true;
+ }
+ test.assert(passed, 'require() with no args should raise helpful error');
+};
diff --git a/tools/addon-sdk-1.12/test/test-sandbox.js b/tools/addon-sdk-1.12/test/test-sandbox.js
new file mode 100644
index 0000000..4f2de96
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-sandbox.js
@@ -0,0 +1,121 @@
+/* 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 { sandbox, load, evaluate } = require('sdk/loader/sandbox');
+const xulApp = require("sdk/system/xul-app");
+const fixturesURI = module.uri.split('test-sandbox.js')[0] + 'fixtures/';
+
+
+exports['test basics'] = function(assert) {
+ let fixture = sandbox('http://example.com');
+ assert.equal(evaluate(fixture, 'var a = 1;'), undefined,
+ 'returns expression value');
+ assert.equal(evaluate(fixture, 'b = 2;'), 2,
+ 'returns expression value');
+ assert.equal(fixture.b, 2, 'global is defined as property');
+ assert.equal(fixture.a, 1, 'global is defined as property');
+ assert.equal(evaluate(fixture, 'a + b;'), 3, 'returns correct sum');
+};
+
+exports['test non-privileged'] = function(assert) {
+ let fixture = sandbox('http://example.com');
+ if (xulApp.versionInRange(xulApp.platformVersion, "15.0a1", "18.*")) {
+ let rv = evaluate(fixture, 'Compo' + 'nents.utils');
+ assert.equal(rv, undefined,
+ "Components's attributes are undefined in content sandboxes");
+ }
+ else {
+ assert.throws(function() {
+ evaluate(fixture, 'Compo' + 'nents.utils');
+ }, 'Access to components is restricted');
+ }
+ fixture.sandbox = sandbox;
+ assert.throws(function() {
+ evaluate(fixture, sandbox('http://foo.com'));
+ }, 'Can not call privileged code');
+};
+
+exports['test injection'] = function(assert) {
+ let fixture = sandbox();
+ fixture.hi = function(name) 'Hi ' + name
+ assert.equal(evaluate(fixture, 'hi("sandbox");'), 'Hi sandbox',
+ 'injected functions are callable');
+};
+
+exports['test exceptions'] = function(assert) {
+ let fixture = sandbox();
+ try {
+ evaluate(fixture, '!' + function() {
+ var message = 'boom';
+ throw Error(message);
+ } + '();');
+ }
+ catch (error) {
+ assert.equal(error.fileName, '', 'no fileName reported');
+ assert.equal(error.lineNumber, 3, 'reports correct line number');
+ }
+
+ try {
+ evaluate(fixture, '!' + function() {
+ var message = 'boom';
+ throw Error(message);
+ } + '();', 'foo.js');
+ }
+ catch (error) {
+ assert.equal(error.fileName, 'foo.js', 'correct fileName reported');
+ assert.equal(error.lineNumber, 3, 'reports correct line number');
+ }
+
+ try {
+ evaluate(fixture, '!' + function() {
+ var message = 'boom';
+ throw Error(message);
+ } + '();', 'foo.js', 2);
+ }
+ catch (error) {
+ assert.equal(error.fileName, 'foo.js', 'correct fileName reported');
+ assert.equal(error.lineNumber, 4, 'line number was opted');
+ }
+};
+
+exports['test opt version'] = function(assert) {
+ let fixture = sandbox();
+ assert.throws(function() {
+ evaluate(fixture, 'let a = 2;', 'test.js', 1, '1.5');
+ }, 'No let in js 1.5');
+};
+
+exports['test load'] = function(assert) {
+ let fixture = sandbox();
+ load(fixture, fixturesURI + 'sandbox-normal.js');
+ assert.equal(fixture.a, 1, 'global variable defined');
+ assert.equal(fixture.b, 2, 'global via `this` property was set');
+ assert.equal(fixture.f(), 4, 'function was defined');
+};
+
+exports['test load with data: URL'] = function(assert) {
+ let code = "var a = 1; this.b = 2; function f() 4";
+ let fixture = sandbox();
+ load(fixture, "data:," + encodeURIComponent(code));
+
+ assert.equal(fixture.a, 1, 'global variable defined');
+ assert.equal(fixture.b, 2, 'global via `this` property was set');
+ assert.equal(fixture.f(), 4, 'function was defined');
+};
+
+exports['test load script with complex char'] = function(assert) {
+ let fixture = sandbox();
+ load(fixture, fixturesURI + 'sandbox-complex-character.js');
+ assert.equal(fixture.chars, 'გამარჯობა', 'complex chars were loaded correctly');
+};
+
+exports['test load script with data: URL and complex char'] = function(assert) {
+ let code = "var chars = 'გამარჯობა';";
+ let fixture = sandbox();
+ load(fixture, "data:," + encodeURIComponent(code));
+
+ assert.equal(fixture.chars, 'გამარჯობა', 'complex chars were loaded correctly');
+};
+
+require('test').run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-selection.js b/tools/addon-sdk-1.12/test/test-selection.js
new file mode 100644
index 0000000..9985ca4
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-selection.js
@@ -0,0 +1,457 @@
+/* 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("sdk/timers");
+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("sdk/deprecated/tab-browser");
+ let dataURL = "data:text/html;charset=utf-8," + 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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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;
+
+ module.exports = {
+ testAppNotSupported: function (test) {
+ test.pass("the selection module does not support this application.");
+ }
+ }
+}
diff --git a/tools/addon-sdk-1.12/test/test-self.js b/tools/addon-sdk-1.12/test/test-self.js
new file mode 100644
index 0000000..6a4b530
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-self.js
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {Cc, Ci, Cu, Cm, components} = require('chrome');
+Cu.import("resource://gre/modules/AddonManager.jsm", this);
+
+exports.testSelf = function(test) {
+ var self = require("sdk/self");
+
+ var source = self.data.load("test-content-symbiont.js");
+ test.assert(source.match(/test-content-symbiont/), "self.data.load() works");
+
+ // Likewise, we can't assert anything about the full URL, because that
+ // depends on self.id . We can only assert that it ends in the right
+ // thing.
+ var url = self.data.url("test-content-symbiont.js");
+ test.assertEqual(typeof(url), "string", "self.data.url('x') returns string");
+ test.assertEqual(/\/test-content-symbiont\.js$/.test(url), true);
+
+ // Make sure 'undefined' is not in url when no string is provided.
+ url = self.data.url();
+ test.assertEqual(typeof(url), "string", "self.data.url() returns string");
+ test.assertEqual(/\/undefined$/.test(url), false);
+
+ // When tests are run on just the api-utils package, self.name is
+ // api-utils. When they're run as 'cfx testall', self.name is testpkgs.
+ test.assert(self.name == "addon-sdk", "self.name is addon-sdk");
+
+ // loadReason may change here, as we change the way tests addons are installed
+ test.assertEqual(self.loadReason, "startup",
+ "self.loadReason is always `startup` on test runs");
+};
+
+exports.testSelfID = function(test) {
+ test.waitUntilDone();
+
+ var self = require("sdk/self");
+ // We can't assert anything about the ID inside the unit test right now,
+ // because the ID we get depends upon how the test was invoked. The idea
+ // is that it is supposed to come from the main top-level package's
+ // package.json file, from the "id" key.
+ test.assertEqual(typeof(self.id), "string", "self.id is a string");
+ test.assert(self.id.length > 0);
+
+ AddonManager.getAddonByID(self.id, function(addon) {
+ if (!addon) {
+ test.fail("did not find addon with self.id");
+ }
+ else {
+ test.pass("found addon with self.id");
+ }
+ test.done();
+ });
+}
diff --git a/tools/addon-sdk-1.12/test/test-set-exports.js b/tools/addon-sdk-1.12/test/test-set-exports.js
new file mode 100644
index 0000000..5221237
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-set-exports.js
@@ -0,0 +1,35 @@
+/* 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 four = require("./modules/exportsEquals");
+exports.testExportsEquals = function(test) {
+ test.assertEqual(four, 4);
+}
+
+/* TODO: Discuss idea of dropping support for this feature that was alternative
+ to `module.exports = ..` that failed.
+let five = require("./modules/setExports");
+exports.testSetExports = function(test) {
+ test.assertEqual(five, 5);
+}
+
+exports.testDupeSetExports = function(test) {
+ var passed = false;
+ try {
+ var dupe = require('./modules/dupeSetExports');
+ } catch(e) {
+ passed = /define\(\) was used, so module\.exports= and module\.setExports\(\) may not be used/.test(e.toString());
+ }
+ test.assertEqual(passed, true, 'define() or setExports(), not both');
+}
+*/
+
+exports.testModule = function(test) {
+ // module.id is not cast in stone yet. In the future, it may include the
+ // package name, or may possibly be a/ URL of some sort. For now, it's a
+ // URL that starts with resource: and ends with this module name, but the
+ // part in between varies depending upon how the test is run.
+ var found = /test-set-exports$/.test(module.id);
+ test.assertEqual(found, true, module.id+" ends with test-set-exports.js");
+}
diff --git a/tools/addon-sdk-1.12/test/test-simple-prefs.js b/tools/addon-sdk-1.12/test/test-simple-prefs.js
new file mode 100644
index 0000000..920af33
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-simple-prefs.js
@@ -0,0 +1,231 @@
+/* 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("sdk/test/loader");
+const { setTimeout } = require("sdk/timers");
+const { notify } = require("sdk/deprecated/observer-service");
+const { id } = require("sdk/self");
+const simplePrefs = require("sdk/simple-prefs");
+const { prefs: sp } = simplePrefs;
+
+const specialChars = "!@#$%^&*()_-=+[]{}~`\'\"<>,./?;:";
+
+exports.testIterations = function(test) {
+ sp["test"] = true;
+ sp["test.test"] = true;
+ let prefAry = [];
+ for (var name in sp ) {
+ prefAry.push(name);
+ }
+ test.assert("test" in sp);
+ test.assert(!sp.getPropertyDescriptor);
+ test.assert(Object.prototype.hasOwnProperty.call(sp, "test"));
+ test.assertEqual(["test", "test.test"].toString(), prefAry.sort().toString(), "for (x in y) part 1/2 works");
+ test.assertEqual(["test", "test.test"].toString(), Object.keys(sp).sort().toString(), "Object.keys works");
+
+ delete sp["test"];
+ delete sp["test.test"];
+ let prefAry = [];
+ for (var name in sp ) {
+ prefAry.push(name);
+ }
+ test.assertEqual([].toString(), prefAry.toString(), "for (x in y) part 2/2 works");
+}
+
+exports.testSetGetBool = function(test) {
+ test.assertEqual(sp.test, undefined, "Value should not exist");
+ sp.test = true;
+ test.assert(sp.test, "Value read should be the value previously set");
+};
+
+// TEST: setting and getting preferences with special characters work
+exports.testSpecialChars = function(test) {
+ let chars = specialChars.split("");
+ let len = chars.length;
+
+ let count = 0;
+ chars.forEach(function(char) {
+ let rand = Math.random() + "";
+ simplePrefs.on(char, function onPrefChanged() {
+ simplePrefs.removeListener(char, onPrefChanged);
+ test.assertEqual(sp[char], rand, "setting pref with a name that is a special char, " + char + ", worked!");
+
+ // end test
+ if (++count == len)
+ test.done();
+ })
+ sp[char] = rand;
+ });
+};
+
+exports.testSetGetInt = function(test) {
+ 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");
+};
+
+exports.testSetComplex = function(test) {
+ try {
+ sp["test-complex"] = {test: true};
+ test.fail("Complex values are not allowed");
+ }
+ catch (e) {
+ test.pass("Complex values are not allowed");
+ }
+};
+
+exports.testSetGetString = function(test) {
+ 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");
+};
+
+exports.testHasAndRemove = function(test) {
+ sp.test = true;
+ test.assert(("test" in sp), "Value exists");
+ delete sp.test;
+ test.assertEqual(sp.test, undefined, "Value should be undefined");
+};
+
+exports.testPrefListener = function(test) {
+ test.waitUntilDone();
+
+ let listener = function(prefName) {
+ simplePrefs.removeListener('test-listener', listener);
+ test.assertEqual(prefName, "test-listen", "The prefs listener heard the right event");
+ test.done();
+ };
+
+ simplePrefs.on("test-listen", listener);
+
+ sp["test-listen"] = true;
+
+ // Wildcard listen
+ let toSet = ['wildcard1','wildcard.pref2','wildcard.even.longer.test'];
+ let observed = [];
+
+ let wildlistener = function(prefName) {
+ if (toSet.indexOf(prefName) > -1) observed.push(prefName);
+ };
+
+ simplePrefs.on('',wildlistener);
+
+ toSet.forEach(function(pref) {
+ sp[pref] = true;
+ });
+
+ test.assert((observed.length == 3 && toSet.length == 3),
+ "Wildcard lengths inconsistent" + JSON.stringify([observed.length, toSet.length]));
+
+ toSet.forEach(function(pref,ii) {
+ test.assertEqual(observed[ii], pref, "Wildcard observed " + pref);
+ });
+
+ simplePrefs.removeListener('',wildlistener);
+
+};
+
+exports.testBtnListener = function(test) {
+ test.waitUntilDone();
+
+ let name = "test-btn-listen";
+ simplePrefs.on(name, function listener() {
+ simplePrefs.removeListener(name, listener);
+ test.pass("Button press event was heard");
+ test.done();
+ });
+ notify((id + "-cmdPressed"), "", name);
+};
+
+exports.testPrefRemoveListener = function(test) {
+ test.waitUntilDone();
+
+ 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");
+
+ simplePrefs.removeListener("test-listen2", listener);
+
+ sp["test-listen2"] = false;
+
+ setTimeout(function() {
+ test.pass("The prefs listener was removed");
+ test.done();
+ }, 250);
+ };
+
+ simplePrefs.on("test-listen2", listener);
+
+ // emit change
+ sp["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("sdk/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("sdk/simple-prefs").prefs["test-listen3"] = false;
+
+ test.done();
+ };
+
+ sp.on("test-listen3", listener);
+
+ // emit change
+ sp.prefs["test-listen3"] = true;
+};
+
+
+// Bug 710117: Test that simple-pref listeners are removed on unload
+exports.testPrefUnloadWildcardListener = function(test) {
+ test.waitUntilDone();
+ let testpref = "test-wildcard-unload-listener";
+ 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[testpref] = false;
+ // this should execute, but also definitely shouldn't fire listener
+ require("simple-prefs").prefs[testpref] = false;
+
+ test.done();
+ };
+
+ sp.on("", listener);
+ // emit change
+ sp.prefs[testpref] = true;
+};
+
+
+// Bug 732919 - JSON.stringify() fails on simple-prefs.prefs
+exports.testPrefJSONStringification = function(test) {
+ var sp = require("sdk/simple-prefs").prefs;
+ test.assertEqual(
+ Object.keys(sp).join(),
+ Object.keys(JSON.parse(JSON.stringify(sp))).join(),
+ "JSON stringification should work.");
+};
diff --git a/tools/addon-sdk-1.12/test/test-simple-storage.js b/tools/addon-sdk-1.12/test/test-simple-storage.js
new file mode 100644
index 0000000..2202bed
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/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("sdk/io/file");
+const prefs = require("sdk/preferences/service");
+
+const QUOTA_PREF = "extensions.addon-sdk.simple-storage.quota";
+
+let {Cc,Ci} = require("chrome");
+
+const { Loader } = require("sdk/test/loader");
+const { id } = require("sdk/self");
+
+let storeFile = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+storeFile.append("jetpack");
+storeFile.append(id);
+storeFile.append("simple-storage");
+storeFile.append("store.json");
+let storeFilename = storeFile.path;
+
+function manager(loader) loader.sandbox("sdk/simple-storage").manager;
+
+exports.testSetGet = function (test) {
+ test.waitUntilDone();
+
+ // Load the module once, set a value.
+ let loader = Loader(module);
+ let ss = loader.require("sdk/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("sdk/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 in obj1) {
+ if (!(prop in obj2) || obj2[prop] !== obj1[prop])
+ return false;
+ }
+ for (let prop in obj2) {
+ if (!(prop in obj1) || obj1[prop] !== obj2[prop])
+ 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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/simple-storage");
+
+ manager(loader).jsonStore.onWrite = function (storage) {
+ loader = Loader(module);
+ ss = loader.require("sdk/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("sdk/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("sdk/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("sdk/simple-storage");
+ manager(loader).jsonStore.onWrite = function () {
+ test.assert(file.exists(storeFilename), "Store file should exist");
+
+ loader = Loader(module);
+ ss = loader.require("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/simple-storage");
+ test.assertRaises(function () ss.storage = val, pred, msg);
+ loader.unload();
+}
diff --git a/tools/addon-sdk-1.12/test/test-tab-browser.js b/tools/addon-sdk-1.12/test/test-tab-browser.js
new file mode 100644
index 0000000..7475f13
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-tab-browser.js
@@ -0,0 +1,513 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var timer = require("sdk/timers");
+var {Cc,Ci} = require("chrome");
+
+function onBrowserLoad(callback, event) {
+ if (event.target && event.target.defaultView == this) {
+ this.removeEventListener("load", onBrowserLoad, true);
+ let browsers = this.document.getElementsByTagName("tabbrowser");
+ try {
+ timer.setTimeout(function (window) {
+ callback(window, browsers[0]);
+ }, 10, this);
+ } catch (e) { console.exception(e); }
+ }
+}
+// Utility function to open a new browser window.
+function openBrowserWindow(callback, url) {
+ let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher);
+ let urlString = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ urlString.data = url;
+ let window = ww.openWindow(null, "chrome://browser/content/browser.xul",
+ "_blank", "chrome,all,dialog=no", urlString);
+ if (callback)
+ window.addEventListener("load", onBrowserLoad.bind(window, callback), true);
+
+ return window;
+}
+
+// Helper for calling code at window close
+function closeBrowserWindow(window, callback) {
+ timer.setTimeout(function() {
+ window.addEventListener("unload", function onUnload() {
+ window.removeEventListener("unload", onUnload, false);
+ callback();
+ }, false);
+ window.close();
+ }, 0);
+}
+
+// Helper for opening two windows at once
+function openTwoWindows(callback) {
+ openBrowserWindow(function (window1) {
+ openBrowserWindow(function (window2) {
+ callback(window1, window2);
+ });
+ });
+}
+
+// Helper for closing two windows at once
+function closeTwoWindows(window1, window2, callback) {
+ closeBrowserWindow(window1, function() {
+ closeBrowserWindow(window2, callback);
+ });
+}
+
+exports.testAddTab = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ const tabBrowser = require("sdk/deprecated/tab-browser");
+
+ let cache = [];
+ let windowUtils = require("sdk/deprecated/window-utils");
+ new windowUtils.WindowTracker({
+ onTrack: function(win) {
+ cache.push(win);
+ },
+ onUntrack: function(win) {
+ cache.splice(cache.indexOf(win), 1)
+ }
+ });
+ let startWindowCount = cache.length;
+
+ // Test 1: add a tab
+ let firstUrl = "data:text/html;charset=utf-8,one";
+ tabBrowser.addTab(firstUrl, {
+ onLoad: function(e) {
+ let win1 = cache[startWindowCount - 1];
+ test.assertEqual(win1.content.location, firstUrl, "URL of new tab in first window matches");
+
+ // Test 2: add a tab in a new window
+ let secondUrl = "data:text/html;charset=utf-8,two";
+ tabBrowser.addTab(secondUrl, {
+ inNewWindow: true,
+ onLoad: function(e) {
+ test.assertEqual(cache.length, startWindowCount + 1, "a new window was opened");
+ let win2 = cache[startWindowCount];
+ let gBrowser = win2.gBrowser;
+ gBrowser.addEventListener("DOMContentLoaded", function onLoad(e) {
+ gBrowser.removeEventListener("DOMContentLoaded", onLoad, false);
+ test.assertEqual(win2.content.location, secondUrl, "URL of new tab in the new window matches");
+
+ closeBrowserWindow(win2, function() {
+ closeBrowserWindow(win1, function() {
+ test.done();
+ });
+ });
+ }, false);
+ }
+ });
+ }
+ });
+ });
+};
+
+exports.testTrackerWithDelegate = function(test) {
+ test.waitUntilDone();
+ const tabBrowser = require("sdk/deprecated/tab-browser");
+
+ var delegate = {
+ state: "initializing",
+ onTrack: function onTrack(browser) {
+ if (this.state == "initializing") {
+ this.state = "waiting for browser window to open";
+ }
+ else if (this.state == "waiting for browser window to open") {
+ this.state = "waiting for browser window to close";
+ timer.setTimeout(function() {
+ closeBrowserWindow(browser.ownerDocument.defaultView, function() {
+ test.assertEqual(delegate.state, "deinitializing");
+ tb.unload();
+ test.done();
+ });
+ }, 0);
+ }
+ else
+ test.fail("invalid state");
+ },
+ onUntrack: function onUntrack(browser) {
+ if (this.state == "waiting for browser window to close") {
+ test.pass("proper state in onUntrack");
+ this.state = "deinitializing";
+ }
+ else if (this.state != "deinitializing")
+ test.fail("invalid state");
+ }
+ };
+ var tb = new tabBrowser.Tracker(delegate);
+
+ delegate.state = "waiting for browser window to open";
+
+ openBrowserWindow();
+};
+
+exports.testWhenContentLoaded = function(test) {
+ test.waitUntilDone();
+ const tabBrowser = require("sdk/deprecated/tab-browser");
+
+ var tracker = tabBrowser.whenContentLoaded(
+ function(window) {
+ var item = window.document.getElementById("foo");
+ test.assertEqual(item.textContent, "bar",
+ "whenContentLoaded() works.");
+ tracker.unload();
+ closeBrowserWindow(activeWindow(), function() {
+ test.done();
+ });
+ });
+
+ openBrowserWindow(function(browserWindow, browser) {
+ var html = '<div id="foo">bar</div>';
+ browser.addTab("data:text/html;charset=utf-8," + html);
+ });
+};
+
+exports.testTrackerWithoutDelegate = function(test) {
+ test.waitUntilDone();
+ const tabBrowser = require("sdk/deprecated/tab-browser");
+
+ openBrowserWindow(function(browserWindow, browser) {
+ var tb = new tabBrowser.Tracker();
+
+ if (tb.length == 0)
+ test.fail("expect at least one tab browser to exist.");
+
+ for (var i = 0; i < tb.length; i++)
+ test.assertEqual(tb.get(i).nodeName, "tabbrowser",
+ "get() method and length prop should work");
+ for (var b in tb)
+ test.assertEqual(b.nodeName, "tabbrowser",
+ "iterator should work");
+
+ var matches = [b for (b in tb)
+ if (b == browser)];
+ test.assertEqual(matches.length, 1,
+ "New browser should be in tracker.");
+ tb.unload();
+
+ closeBrowserWindow(browserWindow, function() {
+ test.done();
+ });
+ });
+};
+
+exports.testTabTracker = function(test) {
+ test.waitUntilDone();
+ const tabBrowser = require("sdk/deprecated/tab-browser");
+
+ openBrowserWindow(function(browserWindow, browser) {
+ var delegate = {
+ tracked: 0,
+ onTrack: function(tab) {
+ this.tracked++;
+ },
+ onUntrack: function(tab) {
+ this.tracked--;
+ }
+ };
+
+ let tabTracker = tabBrowser.TabTracker(delegate);
+
+ let tracked = delegate.tracked;
+ let url1 = "data:text/html;charset=utf-8,1";
+ let url2 = "data:text/html;charset=utf-8,2";
+ let url3 = "data:text/html;charset=utf-8,3";
+ let tabCount = 0;
+
+ function tabLoadListener(e) {
+ let loadedURL = e.target.defaultView.location;
+ if (loadedURL == url1)
+ tabCount++;
+ else if (loadedURL == url2)
+ tabCount++;
+ else if (loadedURL == url3)
+ tabCount++;
+
+ if (tabCount == 3) {
+ test.assertEqual(delegate.tracked, tracked + 3, "delegate tracked tabs matched count");
+ tabTracker.unload();
+ closeBrowserWindow(browserWindow, function() {
+ timer.setTimeout(function() test.done(), 0);
+ });
+ }
+ }
+
+ tabBrowser.addTab(url1, {
+ onLoad: tabLoadListener
+ });
+ tabBrowser.addTab(url2, {
+ onLoad: tabLoadListener
+ });
+ tabBrowser.addTab(url3, {
+ onLoad: tabLoadListener
+ });
+ });
+};
+
+exports.testActiveTab = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(browserWindow, browser) {
+ const tabBrowser = require("sdk/deprecated/tab-browser");
+ const TabModule = require("sdk/deprecated/tab-browser").TabModule;
+ let tm = new TabModule(browserWindow);
+ test.assertEqual(tm.length, 1);
+ let url1 = "data:text/html;charset=utf-8,foo";
+ let url2 = "data:text/html;charset=utf-8,bar";
+
+ function tabURL(tab) tab.ownerDocument.defaultView.content.location.toString()
+
+ tabBrowser.addTab(url1, {
+ onLoad: function(e) {
+ // make sure we're running in the right window.
+ test.assertEqual(tabBrowser.activeTab.ownerDocument.defaultView, browserWindow, "active window matches");
+ browserWindow.focus();
+
+ test.assertEqual(tabURL(tabBrowser.activeTab), url1, "url1 matches");
+ let tabIndex = browser.getBrowserIndexForDocument(e.target);
+ let tabAtIndex = browser.tabContainer.getItemAtIndex(tabIndex);
+ test.assertEqual(tabAtIndex, tabBrowser.activeTab, "activeTab element matches");
+
+ tabBrowser.addTab(url2, {
+ inBackground: true,
+ onLoad: function() {
+ test.assertEqual(tabURL(tabBrowser.activeTab), url1, "url1 still matches");
+ let tabAtIndex = browser.tabContainer.getItemAtIndex(tabIndex);
+ test.assertEqual(tabAtIndex, tabBrowser.activeTab, "activeTab element matches");
+ closeBrowserWindow(browserWindow, function() {
+ test.done()
+ });
+ }
+ });
+ }
+ });
+ });
+};
+
+// TabModule tests
+exports.testEventsAndLengthStayInModule = function(test) {
+ test.waitUntilDone();
+ let TabModule = require("sdk/deprecated/tab-browser").TabModule;
+
+ openTwoWindows(function(window1, window2) {
+ let tm1 = new TabModule(window1);
+ let tm2 = new TabModule(window2);
+
+ let counter1 = 0, counter2 = 0;
+ let counterTabs = 0;
+
+ function onOpenListener() {
+ ++counterTabs;
+ if (counterTabs < 5)
+ return;
+ test.assertEqual(counter1, 2, "Correct number of events fired from window 1");
+ test.assertEqual(counter2, 3, "Correct number of events fired from window 2");
+ test.assertEqual(counterTabs, 5, "Correct number of events fired from all windows");
+ test.assertEqual(tm1.length, 3, "Correct number of tabs in window 1");
+ test.assertEqual(tm2.length, 4, "Correct number of tabs in window 2");
+ closeTwoWindows(window1, window2, function() test.done());
+ }
+
+ tm1.onOpen = function() ++counter1 && onOpenListener();
+ tm2.onOpen = function() ++counter2 && onOpenListener();
+
+ let url = "data:text/html;charset=utf-8,default";
+ tm1.open(url);
+ tm1.open(url);
+
+ tm2.open(url);
+ tm2.open(url);
+ tm2.open(url);
+ });
+}
+
+exports.testTabModuleActiveTab_getterAndSetter = function(test) {
+ test.waitUntilDone();
+ let TabModule = require("sdk/deprecated/tab-browser").TabModule;
+
+ openTwoWindows(function(window1, window2) {
+ let tm1 = new TabModule(window1);
+ let tm2 = new TabModule(window2);
+
+ // First open two tabs per window
+ tm1.open({
+ url: "data:text/html;charset=utf-8,<title>window1,tab1</title>",
+ onOpen: function(tab1) {
+ tm1.open({
+ url: "data:text/html;charset=utf-8,<title>window1,tab2</title>",
+ onOpen: function (tab2) {
+ tm2.open({
+ url: "data:text/html;charset=utf-8,<title>window2,tab1</title>",
+ onOpen: function (tab3) {
+ tm2.open({
+ url: "data:text/html;charset=utf-8,<title>window2,tab2</title>",
+ onOpen: function(tab4) {
+ onTabsOpened(tab1, tab2, tab3, tab4);
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+ });
+
+ // Then try to activate tabs, but wait for all of them to be activated after
+ // being opened
+ function onTabsOpened(tab1, tab2, tab3, tab4) {
+ test.assertEqual(tm1.activeTab.title, "window1,tab2",
+ "Correct active tab on window 1");
+ test.assertEqual(tm2.activeTab.title, "window2,tab2",
+ "Correct active tab on window 2");
+
+ tm1.onActivate = function onActivate() {
+ tm1.onActivate.remove(onActivate);
+ timer.setTimeout(function() {
+ test.assertEqual(tm1.activeTab.title, "window1,tab1",
+ "activeTab setter works (window 1)");
+ test.assertEqual(tm2.activeTab.title, "window2,tab2",
+ "activeTab is ignored with tabs from another window");
+ closeTwoWindows(window1, window2, function() test.done());
+ }, 1000);
+ }
+
+ tm1.activeTab = tab1;
+ // Setting activeTab from another window should have no effect:
+ tm1.activeTab = tab4;
+ }
+
+ });
+}
+
+// test tabs iterator
+exports.testTabModuleTabsIterator = function(test) {
+ test.waitUntilDone();
+ let TabModule = require("sdk/deprecated/tab-browser").TabModule;
+
+ openBrowserWindow(function(window) {
+ let tm1 = new TabModule(window);
+ let url = "data:text/html;charset=utf-8,default";
+ tm1.open(url);
+ tm1.open(url);
+ tm1.open({
+ url: url,
+ onOpen: function(tab) {
+ let count = 0;
+ for each (let t in tm1) count++;
+ test.assertEqual(count, 4, "iterated tab count matches");
+ test.assertEqual(count, tm1.length, "length tab count matches");
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ });
+};
+
+// inNewWindow parameter is ignored on single-window modules
+exports.testTabModuleCantOpenInNewWindow = function(test) {
+ test.waitUntilDone();
+ let TabModule = require("sdk/deprecated/tab-browser").TabModule;
+
+ openBrowserWindow(function(window) {
+ let tm = new TabModule(window);
+ let url = "data:text/html;charset=utf-8,default";
+ tm.open({
+ url: url,
+ inNewWindow: true,
+ onOpen: function() {
+ test.assertEqual(tm.length, 2, "Tab was open on same window");
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ });
+};
+
+// Test that having two modules attached to the same
+// window won't duplicate events fired on each module
+exports.testModuleListenersDontInteract = function(test) {
+ test.waitUntilDone();
+ let TabModule = require("sdk/deprecated/tab-browser").TabModule;
+
+ openBrowserWindow(function(window) {
+ let tm1 = new TabModule(window);
+ let tm2 = new TabModule(window);
+
+ let url = "data:text/html;charset=utf-8,foo";
+ let eventCount = 0, eventModule1 = 0, eventModule2 = 0;
+
+
+ let listener1 = function() {
+ // this should be called twice: when tab is open and when
+ // the url location is changed
+ eventCount++;
+ eventModule1++;
+ }
+ tm1.onReady = listener1;
+
+ tm2.open({
+ url: "about:blank",
+ onOpen: function(tab) {
+ // add listener via property assignment
+ let listener2 = function() {
+ eventCount++;
+ eventModule2++;
+ };
+ tab.onReady = listener2;
+
+ // add listener via collection add
+ let listener3 = function() {
+ eventCount++;
+ eventModule2++;
+ };
+ tab.onReady.add(listener3);
+
+ tab.location = url;
+
+ test.waitUntilEqual(function () eventCount, 4,
+ "Correct global number of events")
+ .then(function () {
+ test.assertEqual(eventModule1, 2,
+ "Correct number of events on module 1");
+ test.assertEqual(eventModule2, 2,
+ "Correct number of events on module 2");
+
+ tm1.onReady.remove(listener1);
+ tab.onReady.remove(listener2);
+ tab.onReady.remove(listener3);
+ closeBrowserWindow(window, function() test.done());
+ });
+ }
+ });
+ });
+};
+
+/******************* helpers *********************/
+
+// Helper for getting the active window
+function activeWindow() {
+ return Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator).
+ getMostRecentWindow("navigator:browser");
+};
+
+// 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("sdk/deprecated/tab-browser");
+}
+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;
+
+ module.exports = {
+ testAppNotSupported: function (test) {
+ test.pass("the tab-browser module does not support this application.");
+ }
+ };
+}
diff --git a/tools/addon-sdk-1.12/test/test-tab-observer.js b/tools/addon-sdk-1.12/test/test-tab-observer.js
new file mode 100644
index 0000000..8b45c19
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-tab-observer.js
@@ -0,0 +1,39 @@
+/* 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 { openTab, closeTab } = require("sdk/tabs/utils");
+const { Loader } = require("sdk/test/loader");
+const { setTimeout } = require("sdk/timers");
+
+exports["test unload tab observer"] = function(assert, done) {
+ let loader = Loader(module);
+
+ let window = loader.require("sdk/deprecated/window-utils").activeBrowserWindow;
+ let observer = loader.require("sdk/tabs/observer").observer;
+ let opened = 0;
+ let closed = 0;
+
+ observer.on("open", function onOpen(window) { opened++; });
+ observer.on("close", function onClose(window) { closed++; });
+
+ // Open and close tab to trigger observers.
+ closeTab(openTab(window, "data:text/html;charset=utf-8,tab-1"));
+
+ // Unload the module so that all listeners set by observer are removed.
+ loader.unload();
+
+ // Open and close tab once again.
+ closeTab(openTab(window, "data:text/html;charset=utf-8,tab-2"));
+
+ // Enqueuing asserts to make sure that assertion is not performed early.
+ setTimeout(function () {
+ assert.equal(1, opened, "observer open was called before unload only");
+ assert.equal(1, closed, "observer close was called before unload only");
+ done();
+ }, 0);
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-tab.js b/tools/addon-sdk-1.12/test/test-tab.js
new file mode 100644
index 0000000..280266c
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-tab.js
@@ -0,0 +1,111 @@
+/* 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 tabAPI = require('sdk/tabs/tab');
+const tabs = require("sdk/tabs"); // From addon-kit
+const windowUtils = require("sdk/deprecated/window-utils");
+const { getTabForWindow } = require('sdk/tabs/helpers');
+
+// The primary test tab
+var primaryTab;
+
+// We have an auxiliary tab to test background tabs.
+var auxTab;
+
+// The window for the outer iframe in the primary test page
+var iframeWin;
+
+exports.testGetTabForWindow = function(test) {
+ test.waitUntilDone();
+
+ test.assertEqual(getTabForWindow(windowUtils.activeWindow), null,
+ "getTabForWindow return null on topwindow");
+ test.assertEqual(getTabForWindow(windowUtils.activeBrowserWindow), null,
+ "getTabForWindow return null on topwindow");
+
+ let subSubDocument = encodeURIComponent(
+ 'Sub iframe<br/>'+
+ '<iframe id="sub-sub-iframe" src="data:text/html;charset=utf-8,SubSubIframe" />');
+ let subDocument = encodeURIComponent(
+ 'Iframe<br/>'+
+ '<iframe id="sub-iframe" src="data:text/html;charset=utf-8,'+subSubDocument+'" />');
+ let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(
+ 'Content<br/><iframe id="iframe" src="data:text/html;charset=utf-8,'+subDocument+'" />');
+
+ // Open up a new tab in the background.
+ //
+ // This lets us test whether GetTabForWindow works even when the tab in
+ // question is not active.
+ tabs.open({
+ inBackground: true,
+ url: "about:mozilla",
+ onReady: function(tab) { auxTab = tab; step2(url, test);},
+ onActivate: function(tab) { step3(test); }
+ });
+};
+
+function step2(url, test) {
+
+ tabs.open({
+ url: url,
+ onReady: function(tab) {
+ primaryTab = tab;
+ let window = windowUtils.activeBrowserWindow.content;
+
+ let matchedTab = getTabForWindow(window);
+ test.assertEqual(matchedTab, tab,
+ "We are able to find the tab with his content window object");
+
+ let timer = require("sdk/timers");
+ function waitForFrames() {
+ let iframe = window.document.getElementById("iframe");
+ if (!iframe) {
+ timer.setTimeout(waitForFrames, 100);
+ return;
+ }
+ iframeWin = iframe.contentWindow;
+ let subIframe = iframeWin.document.getElementById("sub-iframe");
+ if (!subIframe) {
+ timer.setTimeout(waitForFrames, 100);
+ return;
+ }
+ let subIframeWin = subIframe.contentWindow;
+ let subSubIframe = subIframeWin.document.getElementById("sub-sub-iframe");
+ if (!subSubIframe) {
+ timer.setTimeout(waitForFrames, 100);
+ return;
+ }
+ let subSubIframeWin = subSubIframe.contentWindow;
+
+ matchedTab = getTabForWindow(iframeWin);
+ test.assertEqual(matchedTab, tab,
+ "We are able to find the tab with an iframe window object");
+
+ matchedTab = getTabForWindow(subIframeWin);
+ test.assertEqual(matchedTab, tab,
+ "We are able to find the tab with a sub-iframe window object");
+
+ matchedTab = getTabForWindow(subSubIframeWin);
+ test.assertEqual(matchedTab, tab,
+ "We are able to find the tab with a sub-sub-iframe window object");
+
+ // Put our primary tab in the background and test again.
+ // The onActivate listener will take us to step3.
+ auxTab.activate();
+ }
+ waitForFrames();
+ }
+ });
+}
+
+function step3(test) {
+
+ let matchedTab = getTabForWindow(iframeWin);
+ test.assertEqual(matchedTab, primaryTab,
+ "We get the correct tab even when it's in the background");
+
+ primaryTab.close(function () {
+ auxTab.close(function () { test.done();});
+ });
+}
diff --git a/tools/addon-sdk-1.12/test/test-tabs-common.js b/tools/addon-sdk-1.12/test/test-tabs-common.js
new file mode 100644
index 0000000..3d65143
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-tabs-common.js
@@ -0,0 +1,296 @@
+/* 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('sdk/test/loader');
+const { browserWindows } = require('sdk/windows');
+const tabs = require('sdk/tabs');
+
+const URL = 'data:text/html;charset=utf-8,<html><head><title>#title#</title></head></html>';
+
+// TEST: tab count
+exports.testTabCounts = function(test) {
+ test.waitUntilDone();
+
+ tabs.open({
+ url: 'about:blank',
+ onReady: function(tab) {
+ let count1 = 0,
+ count2 = 0;
+ for each(let window in browserWindows) {
+ count1 += window.tabs.length;
+ for each(let tab in window.tabs) {
+ count2 += 1;
+ }
+ }
+
+ test.assert(tabs.length > 1, 'tab count is > 1');
+ test.assertEqual(count1, tabs.length, 'tab count by length is correct');
+ test.assertEqual(count2, tabs.length, 'tab count by iteration is correct');
+
+ // end test
+ tab.close(function() test.done());
+ }
+ });
+};
+
+// TEST: tab.activate()
+exports.testActiveTab_setter_alt = function(test) {
+ test.waitUntilDone();
+
+ let url = URL.replace("#title#", "testActiveTab_setter_alt");
+ let tab1URL = URL.replace("#title#", "tab1");
+
+ tabs.open({
+ url: tab1URL,
+ onReady: function(activeTab) {
+ let activeTabURL = tabs.activeTab.url;
+
+ tabs.open({
+ url: url,
+ inBackground: true,
+ onReady: function onReady(tab) {
+ test.assertEqual(tabs.activeTab.url, activeTabURL, "activeTab url has not changed");
+ test.assertEqual(tab.url, url, "url of new background tab matches");
+
+ tab.once('activate', function onActivate(eventTab) {
+ 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");
+
+ activeTab.close(function() {
+ tab.close(function() {
+ // end test
+ test.done();
+ });
+ });
+ });
+
+ tab.activate();
+ }
+ });
+ }
+ });
+};
+
+// TEST: tab.close()
+exports.testTabClose_alt = function(test) {
+ test.waitUntilDone();
+
+ let url = URL.replace('#title#', 'TabClose_alt');
+ let tab1URL = URL.replace('#title#', 'tab1');
+
+ tabs.open({
+ url: tab1URL,
+ onReady: function(tab1) {
+ // make sure that our tab is not active first
+ test.assertNotEqual(tabs.activeTab.url, url, "tab is not the active tab");
+
+ tabs.open({
+ url: url,
+ onReady: function(tab) {
+ test.assertEqual(tab.url, url, "tab is now the active tab");
+ test.assertEqual(tabs.activeTab.url, url, "tab is now the active tab");
+
+ // another tab should be activated on close
+ tabs.once('activate', function() {
+ test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
+
+ // end test
+ tab1.close(function() test.done());
+ });
+
+ tab.close();
+ }
+ });
+ }
+ });
+};
+
+exports.testAttachOnOpen_alt = function (test) {
+ // Take care that attach has to be called on tab ready and not on tab open.
+ test.waitUntilDone();
+
+ tabs.open({
+ url: "data:text/html;charset=utf-8,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();
+ tab.close(function() test.done());
+ }
+ });
+ }
+ });
+};
+
+exports.testAttachOnMultipleDocuments_alt = function (test) {
+ // Example of attach that process multiple tab documents
+ test.waitUntilDone();
+
+ let firstLocation = "data:text/html;charset=utf-8,foobar";
+ let secondLocation = "data:text/html;charset=utf-8,bar";
+ let thirdLocation = "data:text/html;charset=utf-8,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");
+ },
+ /Couldn't find the worker/,
+ "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");
+ },
+ /Couldn't find the worker/,
+ "postMessage throw because worker2 is destroyed");
+ checkEnd(tab);
+ }
+ });
+ worker2.postMessage("new-doc-2");
+ }
+ else if (onReadyCount == 3) {
+ tab.close();
+ }
+ }
+ });
+
+ function checkEnd(tab) {
+ if (detachEventCount != 2)
+ return;
+
+ test.pass("Got all detach events");
+
+ // end test
+ test.done();
+ }
+};
+
+exports.testAttachWrappers_alt = function (test) {
+ // Check that content script has access to wrapped values by default
+ test.waitUntilDone();
+
+ let document = "data:text/html;charset=utf-8,<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)
+ tab.close(function() test.done());
+ }
+ });
+ }
+ });
+};
+
+// TEST: activeWindow getter and activeTab getter on tab 'activate' event
+exports.testActiveWindowActiveTabOnActivate_alt = function(test) {
+ test.waitUntilDone();
+
+ let activateCount = 0;
+ let newTabs = [];
+ let tabs = browserWindows.activeWindow.tabs;
+
+ tabs.on('activate', function onActivate(tab) {
+ test.assertEqual(tabs.activeTab, tab,
+ "the active window's active tab is the tab provided");
+
+ if (++activateCount == 2) {
+ tabs.removeListener('activate', onActivate);
+
+ newTabs.forEach(function(tab) {
+ tab.close(function() {
+ if (--activateCount == 0) {
+ // end test
+ test.done();
+ }
+ });
+ });
+ }
+ else if (activateCount > 2) {
+ test.fail("activateCount is greater than 2 for some reason..");
+ }
+ });
+
+ tabs.open({
+ url: URL.replace("#title#", "tabs.open1"),
+ onOpen: function(tab) newTabs.push(tab)
+ });
+ tabs.open({
+ url: URL.replace("#title#", "tabs.open2"),
+ onOpen: function(tab) newTabs.push(tab)
+ });
+};
+
+// TEST: tab properties
+exports.testTabContentTypeAndReload = function(test) {
+ test.waitUntilDone();
+
+ let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>";
+ let urlXML = "data:text/xml;charset=utf-8,<foo>bar</foo>";
+ tabs.open({
+ url: url,
+ onReady: function(tab) {
+ if (tab.url === url) {
+ test.assertEqual(tab.contentType, "text/html");
+ tab.url = urlXML;
+ }
+ else {
+ test.assertEqual(tab.contentType, "text/xml");
+ tab.close(function() test.done());
+ }
+ }
+ });
+};
diff --git a/tools/addon-sdk-1.12/test/test-tabs.js b/tools/addon-sdk-1.12/test/test-tabs.js
new file mode 100644
index 0000000..cad8931
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-tabs.js
@@ -0,0 +1,11 @@
+/* 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';
+
+if (require("sdk/system/xul-app").is("Firefox")) {
+ module.exports = require("./tabs/test-firefox-tabs");
+}
+else if (require("sdk/system/xul-app").is("Fennec")) {
+ module.exports = require("./tabs/test-fennec-tabs");
+}
diff --git a/tools/addon-sdk-1.12/test/test-text-streams.js b/tools/addon-sdk-1.12/test/test-text-streams.js
new file mode 100644
index 0000000..36a01c8
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-text-streams.js
@@ -0,0 +1,156 @@
+/* -*- 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("sdk/io/file");
+const { pathFor } = require("sdk/system");
+const { Loader } = require("sdk/test/loader");
+
+const STREAM_CLOSED_ERROR = "The stream is closed and cannot be used.";
+
+// This should match the constant of the same name in text-streams.js.
+const BUFFER_BYTE_LEN = 0x8000;
+
+exports.testWriteRead = function (test) {
+ let fname = dataFileFilename();
+
+ // Write a small string less than the stream's buffer size...
+ let str = "exports.testWriteRead data!";
+ let stream = file.open(fname, "w");
+ test.assert(!stream.closed, "stream.closed after open should be false");
+ stream.write(str);
+ stream.close();
+ test.assert(stream.closed, "stream.closed after close should be true");
+ test.assertRaises(function () stream.close(),
+ STREAM_CLOSED_ERROR,
+ "stream.close after already closed should raise error");
+ test.assertRaises(function () stream.write("This shouldn't be written!"),
+ STREAM_CLOSED_ERROR,
+ "stream.write after close should raise error");
+
+ // ... and read it.
+ stream = file.open(fname);
+ test.assert(!stream.closed, "stream.closed after open should be false");
+ test.assertEqual(stream.read(), str,
+ "stream.read should return string written");
+ test.assertEqual(stream.read(), "",
+ "stream.read at EOS should return empty string");
+ stream.close();
+ test.assert(stream.closed, "stream.closed after close should be true");
+ test.assertRaises(function () stream.close(),
+ STREAM_CLOSED_ERROR,
+ "stream.close after already closed should raise error");
+ test.assertRaises(function () stream.read(),
+ STREAM_CLOSED_ERROR,
+ "stream.read after close should raise error");
+
+ // Write a big string many times the size of the stream's buffer and read it.
+ // Since it comes after the previous test, this also ensures that the file is
+ // truncated when it's opened for writing.
+ str = "";
+ let bufLen = BUFFER_BYTE_LEN;
+ let fileSize = bufLen * 10;
+ for (let i = 0; i < fileSize; i++)
+ str += i % 10;
+ stream = file.open(fname, "w");
+ stream.write(str);
+ stream.close();
+ stream = file.open(fname);
+ test.assertEqual(stream.read(), str,
+ "stream.read should return string written");
+ stream.close();
+
+ // The same, but write and read in chunks.
+ stream = file.open(fname, "w");
+ let i = 0;
+ while (i < str.length) {
+ // Use a chunk length that spans buffers.
+ let chunk = str.substr(i, bufLen + 1);
+ stream.write(chunk);
+ i += bufLen + 1;
+ }
+ stream.close();
+ stream = file.open(fname);
+ let readStr = "";
+ bufLen = BUFFER_BYTE_LEN;
+ let readLen = bufLen + 1;
+ do {
+ var frag = stream.read(readLen);
+ readStr += frag;
+ } while (frag);
+ stream.close();
+ test.assertEqual(readStr, str,
+ "stream.write and read in chunks should work as expected");
+
+ // Read the same file, passing in strange numbers of bytes to read.
+ stream = file.open(fname);
+ test.assertEqual(stream.read(fileSize * 100), str,
+ "stream.read with big byte length should return string " +
+ "written");
+ stream.close();
+
+ stream = file.open(fname);
+ test.assertEqual(stream.read(0), "",
+ "string.read with zero byte length should return empty " +
+ "string");
+ stream.close();
+
+ stream = file.open(fname);
+ test.assertEqual(stream.read(-1), "",
+ "string.read with negative byte length should return " +
+ "empty string");
+ stream.close();
+
+ file.remove(fname);
+};
+
+exports.testWriteAsync = function (test) {
+ test.waitUntilDone();
+
+ let fname = dataFileFilename();
+ let str = "exports.testWriteAsync data!";
+ let stream = file.open(fname, "w");
+ test.assert(!stream.closed, "stream.closed after open should be false");
+
+ // Write.
+ stream.writeAsync(str, function (err) {
+ test.assertEqual(this, stream, "|this| should be the stream object");
+ test.assertEqual(err, undefined,
+ "stream.writeAsync should not cause error");
+ test.assert(stream.closed, "stream.closed after write should be true");
+ test.assertRaises(function () stream.close(),
+ STREAM_CLOSED_ERROR,
+ "stream.close after already closed should raise error");
+ test.assertRaises(function () stream.writeAsync("This shouldn't work!"),
+ STREAM_CLOSED_ERROR,
+ "stream.writeAsync after close should raise error");
+
+ // Read.
+ stream = file.open(fname, "r");
+ test.assert(!stream.closed, "stream.closed after open should be false");
+ let readStr = stream.read();
+ test.assertEqual(readStr, str,
+ "string.read should yield string written");
+ stream.close();
+ file.remove(fname);
+ test.done();
+ });
+};
+
+exports.testUnload = function (test) {
+ let loader = Loader(module);
+ let file = loader.require("sdk/io/file");
+
+ let filename = dataFileFilename("temp");
+ let stream = file.open(filename, "w");
+
+ loader.unload();
+ test.assert(stream.closed, "stream should be closed after module unload");
+};
+
+// Returns the name of a file that should be used to test writing and reading.
+function dataFileFilename() {
+ return file.join(pathFor("ProfD"), "test-text-streams-data");
+}
diff --git a/tools/addon-sdk-1.12/test/test-timer.js b/tools/addon-sdk-1.12/test/test-timer.js
new file mode 100644
index 0000000..2c7d846
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-timer.js
@@ -0,0 +1,131 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var timer = require("sdk/timers");
+const { Loader } = require("sdk/test/loader");
+
+exports.testSetTimeout = function(test) {
+ timer.setTimeout(function() {
+ test.pass("testSetTimeout passed");
+ test.done();
+ }, 1);
+ test.waitUntilDone();
+};
+
+exports.testParamedSetTimeout = function(test) {
+ let params = [1, 'foo', { bar: 'test' }, null, undefined];
+ timer.setTimeout.apply(null, [function() {
+ test.assertEqual(arguments.length, params.length);
+ for (let i = 0, ii = params.length; i < ii; i++)
+ test.assertEqual(params[i], arguments[i]);
+ test.done();
+ }, 1].concat(params));
+ test.waitUntilDone();
+};
+
+exports.testClearTimeout = function(test) {
+ var myFunc = function myFunc() {
+ test.fail("myFunc() should not be called in testClearTimeout");
+ };
+ var id = timer.setTimeout(myFunc, 1);
+ timer.setTimeout(function() {
+ test.pass("testClearTimeout passed");
+ test.done();
+ }, 2);
+ timer.clearTimeout(id);
+ test.waitUntilDone();
+};
+
+exports.testParamedClearTimeout = function(test) {
+ let params = [1, 'foo', { bar: 'test' }, null, undefined];
+ var myFunc = function myFunc() {
+ test.fail("myFunc() should not be called in testClearTimeout");
+ };
+ var id = timer.setTimeout(myFunc, 1);
+ timer.setTimeout.apply(null, [function() {
+ test.assertEqual(arguments.length, params.length);
+ for (let i = 0, ii = params.length; i < ii; i++)
+ test.assertEqual(params[i], arguments[i]);
+ test.done();
+ }, 1].concat(params));
+ timer.clearTimeout(id);
+ test.waitUntilDone();
+};
+
+exports.testSetInterval = function (test) {
+ var count = 0;
+ var id = timer.setInterval(function () {
+ count++;
+ if (count >= 5) {
+ timer.clearInterval(id);
+ test.pass("testSetInterval passed");
+ test.done();
+ }
+ }, 1);
+ test.waitUntilDone();
+};
+
+exports.testParamedSetInerval = function(test) {
+ let params = [1, 'foo', { bar: 'test' }, null, undefined];
+ let count = 0;
+ let id = timer.setInterval.apply(null, [function() {
+ count ++;
+ if (count < 5) {
+ test.assertEqual(arguments.length, params.length);
+ for (let i = 0, ii = params.length; i < ii; i++)
+ test.assertEqual(params[i], arguments[i]);
+ } else {
+ timer.clearInterval(id);
+ test.done();
+ }
+ }, 1].concat(params));
+ test.waitUntilDone();
+};
+
+exports.testClearInterval = function (test) {
+ timer.clearInterval(timer.setInterval(function () {
+ test.fail("setInterval callback should not be called");
+ }, 1));
+ var id = timer.setInterval(function () {
+ timer.clearInterval(id);
+ test.pass("testClearInterval passed");
+ test.done();
+ }, 2);
+ test.waitUntilDone();
+};
+
+exports.testParamedClearInterval = function(test) {
+ timer.clearInterval(timer.setInterval(function () {
+ test.fail("setInterval callback should not be called");
+ }, 1, timer, {}, null));
+
+ let id = timer.setInterval(function() {
+ timer.clearInterval(id);
+ test.assertEqual(3, arguments.length);
+ test.done();
+ }, 2, undefined, 'test', {});
+ test.waitUntilDone();
+};
+
+
+exports.testUnload = function(test) {
+ var loader = Loader(module);
+ var sbtimer = loader.require("sdk/timers");
+
+ var myFunc = function myFunc() {
+ test.fail("myFunc() should not be called in testUnload");
+ };
+
+ sbtimer.setTimeout(myFunc, 1);
+ sbtimer.setTimeout(myFunc, 1, 'foo', 4, {}, undefined);
+ sbtimer.setInterval(myFunc, 1);
+ sbtimer.setInterval(myFunc, 1, {}, null, 'bar', undefined, 87);
+ loader.unload();
+ timer.setTimeout(function() {
+ test.pass("timer testUnload passed");
+ test.done();
+ }, 2);
+ test.waitUntilDone();
+};
+
diff --git a/tools/addon-sdk-1.12/test/test-tmp-file.js b/tools/addon-sdk-1.12/test/test-tmp-file.js
new file mode 100644
index 0000000..187f9e7
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-tmp-file.js
@@ -0,0 +1,24 @@
+/* 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 tmp = require("sdk/test/tmp-file");
+const file = require("sdk/io/file");
+
+const testFolderURL = module.uri.split('test-tmp-file.js')[0];
+
+exports.testCreateFromString = function (test) {
+ let expectedContent = "foo";
+ let path = tmp.createFromString(expectedContent);
+ let content = file.read(path);
+ test.assertEqual(content, expectedContent,
+ "Temporary file contains the expected content");
+}
+
+exports.testCreateFromURL = function (test) {
+ let url = testFolderURL + "test-tmp-file.txt";
+ let path = tmp.createFromURL(url);
+ let content = file.read(path);
+ test.assertEqual(content, "foo",
+ "Temporary file contains the expected content");
+}
diff --git a/tools/addon-sdk-1.12/test/test-tmp-file.txt b/tools/addon-sdk-1.12/test/test-tmp-file.txt
new file mode 100644
index 0000000..1910281
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-tmp-file.txt
@@ -0,0 +1 @@
+foo \ No newline at end of file
diff --git a/tools/addon-sdk-1.12/test/test-traceback.js b/tools/addon-sdk-1.12/test/test-traceback.js
new file mode 100644
index 0000000..df310d1
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-traceback.js
@@ -0,0 +1,118 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var traceback = require("sdk/console/traceback");
+var {Cc,Ci,Cr,Cu} = require("chrome");
+
+function throwNsIException() {
+ var ios = Cc['@mozilla.org/network/io-service;1']
+ .getService(Ci.nsIIOService);
+ ios.newURI("i'm a malformed URI", null, null);
+}
+
+function throwError() {
+ throw new Error("foob");
+}
+
+exports.testFormatDoesNotFetchRemoteFiles = function(test) {
+ var observers = require("sdk/deprecated/observer-service");
+ ["http", "https"].forEach(
+ function(scheme) {
+ var httpRequests = 0;
+ function onHttp() {
+ httpRequests++;
+ }
+
+ observers.add("http-on-modify-request", onHttp);
+
+ try {
+ var tb = [{filename: scheme + "://www.mozilla.org/",
+ lineNo: 1,
+ funcName: "blah"}];
+ traceback.format(tb);
+ } catch (e) {
+ test.exception(e);
+ }
+
+ observers.remove("http-on-modify-request", onHttp);
+
+ test.assertEqual(httpRequests, 0,
+ "traceback.format() does not make " +
+ scheme + " request");
+ });
+};
+
+exports.testFromExceptionWithString = function(test) {
+ try {
+ throw "foob";
+ test.fail("an exception should've been thrown");
+ } catch (e if e == "foob") {
+ var tb = traceback.fromException(e);
+ test.assertEqual(tb.length, 0);
+ }
+};
+
+exports.testFormatWithString = function(test) {
+ // This can happen if e.g. a thrown exception was
+ // a string instead of an Error instance.
+ test.assertEqual(traceback.format("blah"),
+ "Traceback (most recent call last):");
+};
+
+exports.testFromExceptionWithError = function(test) {
+ try {
+ throwError();
+ test.fail("an exception should've been thrown");
+ } catch (e if e instanceof Error) {
+ var tb = traceback.fromException(e);
+ var xulApp = require("sdk/system/xul-app");
+ test.assertEqual(tb.slice(-1)[0].funcName, "throwError");
+ }
+};
+
+exports.testFromExceptionWithNsIException = function(test) {
+ try {
+ throwNsIException();
+ test.fail("an exception should've been thrown");
+ } catch (e if e.result == Cr.NS_ERROR_MALFORMED_URI) {
+ var tb = traceback.fromException(e);
+ test.assertEqual(tb.slice(-1)[0].funcName,
+ "throwNsIException");
+ }
+};
+
+exports.testFormat = function(test) {
+ function getTraceback() {
+ return traceback.format();
+ }
+
+ var formatted = getTraceback();
+ test.assertEqual(typeof(formatted), "string");
+ var lines = formatted.split("\n");
+ test.assertEqual(lines.slice(-2)[0].indexOf("getTraceback") > 0,
+ true,
+ "formatted traceback should include function name");
+ test.assertEqual(lines.slice(-1)[0].trim(),
+ "return traceback.format();",
+ "formatted traceback should include source code");
+};
+
+exports.testExceptionsWithEmptyStacksAreLogged = function(test) {
+ // Ensures that our fix to bug 550368 works.
+ var sandbox = Cu.Sandbox("http://www.foo.com");
+ var excRaised = false;
+ try {
+ Cu.evalInSandbox("returns 1 + 2;", sandbox, "1.8",
+ "blah.js", 25);
+ } catch (e) {
+ excRaised = true;
+ var stack = traceback.fromException(e);
+ test.assertEqual(stack.length, 1, "stack should have one frame");
+ test.assert(stack[0].filename, "blah.js", "frame should have filename");
+ test.assert(stack[0].lineNo, 25, "frame should have line no");
+ test.assertEqual(stack[0].funcName, null, "frame should have null function name");
+ }
+ if (!excRaised)
+ test.fail("Exception should have been raised.");
+};
diff --git a/tools/addon-sdk-1.12/test/test-traits-core.js b/tools/addon-sdk-1.12/test/test-traits-core.js
new file mode 100644
index 0000000..8ab2edd
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-traits-core.js
@@ -0,0 +1,838 @@
+/* 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 ERR_CONFLICT = 'Remaining conflicting property: ',
+ ERR_REQUIRED = 'Missing required property: ';
+
+function assertSametrait(test, trait1, trait2) {
+ let names1 = Object.getOwnPropertyNames(trait1),
+ names2 = Object.getOwnPropertyNames(trait2);
+
+ test.assertEqual(
+ names1.length,
+ names2.length,
+ 'equal traits must have same amount of properties'
+ );
+
+ for (let i = 0; i < names1.length; i++) {
+ let name = names1[i];
+ test.assertNotEqual(
+ -1,
+ names2.indexOf(name),
+ 'equal traits must contain same named properties: ' + name
+ );
+ assertSameDescriptor(test, name, trait1[name], trait2[name]);
+ }
+}
+
+function assertSameDescriptor(test, name, desc1, desc2) {
+ if (desc1.conflict || desc2.conflict) {
+ test.assertEqual(
+ desc1.conflict,
+ desc2.conflict,
+ 'if one of same descriptors has `conflict` another must have it: '
+ + name
+ );
+ } else if (desc1.required || desc2.required) {
+ test.assertEqual(
+ desc1.required,
+ desc2.required,
+ 'if one of same descriptors is has `required` another must have it: '
+ + name
+ );
+ } else {
+ test.assertEqual(
+ desc1.get,
+ desc2.get,
+ 'get must be the same on both descriptors: ' + name
+ );
+ test.assertEqual(
+ desc1.set,
+ desc2.set,
+ 'set must be the same on both descriptors: ' + name
+ );
+ test.assertEqual(
+ desc1.value,
+ desc2.value,
+ 'value must be the same on both descriptors: ' + name
+ );
+ test.assertEqual(
+ desc1.enumerable,
+ desc2.enumerable,
+ 'enumerable must be the same on both descriptors: ' + name
+ );
+ test.assertEqual(
+ desc1.required,
+ desc2.required,
+ 'value must be the same on both descriptors: ' + name
+ );
+ }
+}
+
+function Data(value, enumerable, confligurable, writable) {
+ return {
+ value: value,
+ enumerable: false !== enumerable,
+ confligurable: false !== confligurable,
+ writable: false !== writable
+ };
+}
+
+function Method(method, enumerable, confligurable, writable) {
+ return {
+ value: method,
+ enumerable: false !== enumerable,
+ confligurable: false !== confligurable,
+ writable: false !== writable
+ };
+}
+
+function Accessor(get, set, enumerable, confligurable) {
+ return {
+ get: get,
+ set: set,
+ enumerable: false !== enumerable,
+ confligurable: false !== confligurable,
+ };
+}
+
+function Required(name) {
+ function required() { throw new Error(ERR_REQUIRED + name) }
+ return {
+ get: required,
+ set: required,
+ required: true
+ };
+}
+
+function Conflict(name) {
+ function conflict() { throw new Error(ERR_CONFLICT + name) }
+ return {
+ get: conflict,
+ set: conflict,
+ conflict: true
+ };
+}
+
+function testMethod() {};
+
+const { trait, compose, resolve, required, override, create } =
+ require('sdk/deprecated/traits/core');
+
+
+exports['test:empty trait'] = function(test) {
+ assertSametrait(
+ test,
+ trait({}),
+ {}
+ );
+};
+
+exports['test:simple trait'] = function(test) {
+ assertSametrait(
+ test,
+ trait({
+ a: 0,
+ b: testMethod
+ }),
+ {
+ a: Data(0, true, true, true),
+ b: Method(testMethod, true, true, true)
+ }
+ );
+};
+
+exports['test:simple trait with required prop'] = function(test) {
+ assertSametrait(
+ test,
+ trait({
+ a: required,
+ b: 1
+ }),
+ {
+ a: Required('a'),
+ b: Data(1)
+ }
+ );
+};
+
+exports['test:ordering of trait properties is irrelevant'] = function(test) {
+ assertSametrait(test,
+ trait({ a: 0, b: 1, c: required }),
+ trait({ b: 1, c: required, a: 0 })
+ );
+};
+
+exports['test:trait with accessor property'] = function(test) {
+ let record = { get a() {}, set a(v) {} };
+ let get = Object.getOwnPropertyDescriptor(record,'a').get;
+ let set = Object.getOwnPropertyDescriptor(record,'a').set;
+ assertSametrait(test,
+ trait(record),
+ { a: Accessor(get, set ) }
+ );
+};
+
+exports['test:simple composition'] = function(test) {
+ assertSametrait(test,
+ compose(
+ trait({ a: 0, b: 1 }),
+ trait({ c: 2, d: testMethod })
+ ),
+ {
+ a: Data(0),
+ b: Data(1),
+ c: Data(2),
+ d: Method(testMethod)
+ }
+ );
+};
+
+exports['test:composition with conflict'] = function(test) {
+ assertSametrait(test,
+ compose(
+ trait({ a: 0, b: 1 }),
+ trait({ a: 2, c: testMethod })
+ ),
+ {
+ a: Conflict('a'),
+ b: Data(1),
+ c: Method(testMethod)
+ }
+ );
+};
+
+exports['test:composition of identical props does not cause conflict'] =
+function(test) {
+ assertSametrait(test,
+ compose(
+ trait({ a: 0, b: 1 }),
+ trait({ a: 0, c: testMethod })
+ ),
+ {
+ a: Data(0),
+ b: Data(1),
+ c: Method(testMethod) }
+ )
+};
+
+exports['test:composition with identical required props'] =
+function(test) {
+ assertSametrait(test,
+ compose(
+ trait({ a: required, b: 1 }),
+ trait({ a: required, c: testMethod })
+ ),
+ {
+ a: Required(),
+ b: Data(1),
+ c: Method(testMethod)
+ }
+ );
+};
+
+exports['test:composition satisfying a required prop'] = function (test) {
+ assertSametrait(test,
+ compose(
+ trait({ a: required, b: 1 }),
+ trait({ a: testMethod })
+ ),
+ {
+ a: Method(testMethod),
+ b: Data(1)
+ }
+ );
+};
+
+exports['test:compose is neutral wrt conflicts'] = function (test) {
+ assertSametrait(test,
+ compose(
+ compose(
+ trait({ a: 1 }),
+ trait({ a: 2 })
+ ),
+ trait({ b: 0 })
+ ),
+ {
+ a: Conflict('a'),
+ b: Data(0)
+ }
+ );
+};
+
+exports['test:conflicting prop overrides required prop'] = function (test) {
+ assertSametrait(test,
+ compose(
+ compose(
+ trait({ a: 1 }),
+ trait({ a: 2 })
+ ),
+ trait({ a: required })
+ ),
+ {
+ a: Conflict('a')
+ }
+ );
+};
+
+exports['test:compose is commutative'] = function (test) {
+ assertSametrait(test,
+ compose(
+ trait({ a: 0, b: 1 }),
+ trait({ c: 2, d: testMethod })
+ ),
+ compose(
+ trait({ c: 2, d: testMethod }),
+ trait({ a: 0, b: 1 })
+ )
+ );
+};
+
+exports['test:compose is commutative, also for required/conflicting props'] =
+function (test) {
+ assertSametrait(test,
+ compose(
+ trait({ a: 0, b: 1, c: 3, e: required }),
+ trait({ c: 2, d: testMethod })
+ ),
+ compose(
+ trait({ c: 2, d: testMethod }),
+ trait({ a: 0, b: 1, c: 3, e: required })
+ )
+ );
+};
+exports['test:compose is associative'] = function (test) {
+ assertSametrait(test,
+ compose(
+ trait({ a: 0, b: 1, c: 3, d: required }),
+ compose(
+ trait({ c: 3, d: required }),
+ trait({ c: 2, d: testMethod, e: 'foo' })
+ )
+ ),
+ compose(
+ compose(
+ trait({ a: 0, b: 1, c: 3, d: required }),
+ trait({ c: 3, d: required })
+ ),
+ trait({ c: 2, d: testMethod, e: 'foo' })
+ )
+ );
+};
+
+exports['test:diamond import of same prop does not generate conflict'] =
+function (test) {
+ assertSametrait(test,
+ compose(
+ compose(
+ trait({ b: 2 }),
+ trait({ a: 1 })
+ ),
+ compose(
+ trait({ c: 3 }),
+ trait({ a: 1 })
+ ),
+ trait({ d: 4 })
+ ),
+ {
+ a: Data(1),
+ b: Data(2),
+ c: Data(3),
+ d: Data(4)
+ }
+ );
+};
+
+exports['test:resolve with empty resolutions has no effect'] =
+function (test) {
+ assertSametrait(test, resolve({}, trait({
+ a: 1,
+ b: required,
+ c: testMethod
+ })), {
+ a: Data(1),
+ b: Required(),
+ c: Method(testMethod)
+ });
+};
+
+exports['test:resolve: renaming'] = function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: 'A', c: 'C' },
+ trait({ a: 1, b: required, c: testMethod })
+ ),
+ {
+ A: Data(1),
+ b: Required(),
+ C: Method(testMethod),
+ a: Required(),
+ c: Required()
+ }
+ );
+};
+
+exports['test:resolve: renaming to conflicting name causes conflict, order 1']
+= function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: 'b'},
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ b: Conflict('b'),
+ a: Required()
+ }
+ );
+};
+
+exports['test:resolve: renaming to conflicting name causes conflict, order 2']
+= function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: 'b' },
+ trait({ b: 2, a: 1 })
+ ),
+ {
+ b: Conflict('b'),
+ a: Required()
+ }
+ );
+};
+
+exports['test:resolve: simple exclusion'] = function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: undefined },
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ a: Required(),
+ b: Data(2)
+ }
+ );
+};
+
+exports['test:resolve: exclusion to "empty" trait'] = function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: undefined, b: undefined },
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ a: Required(),
+ b: Required()
+ }
+ );
+};
+
+exports['test:resolve: exclusion and renaming of disjoint props'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: undefined, b: 'c' },
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ a: Required(),
+ c: Data(2),
+ b: Required()
+ }
+ );
+};
+
+exports['test:resolve: exclusion and renaming of overlapping props'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: undefined, b: 'a' },
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ a: Data(2),
+ b: Required()
+ }
+ );
+};
+
+exports['test:resolve: renaming to a common alias causes conflict'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: 'c', b: 'c' },
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ c: Conflict('c'),
+ a: Required(),
+ b: Required()
+ }
+ );
+};
+
+exports['test:resolve: renaming overrides required target'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { b: 'a' },
+ trait({ a: required, b: 2 })
+ ),
+ {
+ a: Data(2),
+ b: Required()
+ }
+ );
+};
+
+exports['test:resolve: renaming required properties has no effect'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { b: 'a' },
+ trait({ a: 2, b: required })
+ ),
+ {
+ a: Data(2),
+ b: Required()
+ }
+ );
+};
+
+exports['test:resolve: renaming of non-existent props has no effect'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: 'c', d: 'c' },
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ c: Data(1),
+ b: Data(2),
+ a: Required()
+ }
+ );
+};
+
+exports['test:resolve: exclusion of non-existent props has no effect'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { b: undefined },
+ trait({ a: 1 })
+ ),
+ {
+ a: Data(1)
+ }
+ );
+};
+
+exports['test:resolve is neutral w.r.t. required properties'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: 'c', b: undefined },
+ trait({ a: required, b: required, c: 'foo', d: 1 })
+ ),
+ {
+ a: Required(),
+ b: Required(),
+ c: Data('foo'),
+ d: Data(1)
+ }
+ );
+};
+
+exports['test:resolve supports swapping of property names, ordering 1'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: 'b', b: 'a' },
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ a: Data(2),
+ b: Data(1)
+ }
+ );
+};
+
+exports['test:resolve supports swapping of property names, ordering 2'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { b: 'a', a: 'b' },
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ a: Data(2),
+ b: Data(1)
+ }
+ );
+};
+
+exports['test:resolve supports swapping of property names, ordering 3'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { b: 'a', a: 'b' },
+ trait({ b: 2, a: 1 })
+ ),
+ {
+ a: Data(2),
+ b: Data(1)
+ }
+ );
+};
+
+exports['test:resolve supports swapping of property names, ordering 4'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: 'b', b: 'a' },
+ trait({ b: 2, a: 1 })
+ ),
+ {
+ a: Data(2),
+ b: Data(1)
+ }
+ );
+};
+
+exports['test:override of mutually exclusive traits'] = function (test) {
+ assertSametrait(test,
+ override(
+ trait({ a: 1, b: 2 }),
+ trait({ c: 3, d: testMethod })
+ ),
+ {
+ a: Data(1),
+ b: Data(2),
+ c: Data(3),
+ d: Method(testMethod)
+ }
+ );
+};
+
+exports['test:override of mutually exclusive traits is compose'] =
+function (test) {
+ assertSametrait(test,
+ override(
+ trait({ a: 1, b: 2 }),
+ trait({ c: 3, d: testMethod })
+ ),
+ compose(
+ trait({ d: testMethod, c: 3 }),
+ trait({ b: 2, a: 1 })
+ )
+ );
+};
+
+exports['test:override of overlapping traits'] = function (test) {
+ assertSametrait(test,
+ override(
+ trait({ a: 1, b: 2 }),
+ trait({ a: 3, c: testMethod })
+ ),
+ {
+ a: Data(1),
+ b: Data(2),
+ c: Method(testMethod)
+ }
+ );
+};
+
+exports['test:three-way override of overlapping traits'] = function (test) {
+ assertSametrait(test,
+ override(
+ trait({ a: 1, b: 2 }),
+ trait({ b: 4, c: 3 }),
+ trait({ a: 3, c: testMethod, d: 5 })
+ ),
+ {
+ a: Data(1),
+ b: Data(2),
+ c: Data(3),
+ d: Data(5)
+ }
+ );
+};
+
+exports['test:override replaces required properties'] = function (test) {
+ assertSametrait(test,
+ override(
+ trait({ a: required, b: 2 }),
+ trait({ a: 1, c: testMethod })
+ ),
+ {
+ a: Data(1),
+ b: Data(2),
+ c: Method(testMethod)
+ }
+ );
+};
+
+exports['test:override is not commutative'] = function (test) {
+ assertSametrait(test,
+ override(
+ trait({ a: 1, b: 2 }),
+ trait({ a: 3, c: 4 })
+ ),
+ {
+ a: Data(1),
+ b: Data(2),
+ c: Data(4)
+ }
+ );
+
+ assertSametrait(test,
+ override(
+ trait({ a: 3, c: 4 }),
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ a: Data(3),
+ b: Data(2),
+ c: Data(4)
+ }
+ );
+};
+
+exports['test:override is associative'] = function (test) {
+ assertSametrait(test,
+ override(
+ override(
+ trait({ a: 1, b: 2 }),
+ trait({ a: 3, c: 4, d: 5 })
+ ),
+ trait({ a: 6, c: 7, e: 8 })
+ ),
+ override(
+ trait({ a: 1, b: 2 }),
+ override(
+ trait({ a: 3, c: 4, d: 5 }),
+ trait({ a: 6, c: 7, e: 8 })
+ )
+ )
+ );
+};
+
+exports['test:create simple'] = function(test) {
+ let o1 = create(
+ Object.prototype,
+ trait({ a: 1, b: function() { return this.a; } })
+ );
+
+ test.assertEqual(
+ Object.prototype,
+ Object.getPrototypeOf(o1),
+ 'o1 prototype'
+ );
+ test.assertEqual(1, o1.a, 'o1.a');
+ test.assertEqual(1, o1.b(), 'o1.b()');
+ test.assertEqual(
+ 2,
+ Object.getOwnPropertyNames(o1).length,
+ 'Object.keys(o1).length === 2'
+ );
+};
+
+exports['test:create with Array.prototype'] = function(test) {
+ let o2 = create(Array.prototype, trait({}));
+ test.assertEqual(
+ Array.prototype,
+ Object.getPrototypeOf(o2),
+ "o2 prototype"
+ );
+};
+
+exports['test:exception for incomplete required properties'] =
+function(test) {
+ try {
+ create(Object.prototype, trait({ foo: required }));
+ test.fail('expected create to complain about missing required props');
+ } catch(e) {
+ test.assertEqual(
+ 'Error: Missing required property: foo',
+ e.toString(),
+ 'required prop error'
+ );
+ }
+};
+
+exports['test:exception for unresolved conflicts'] = function(test) {
+ try {
+ create({}, compose(trait({ a: 0 }), trait({ a: 1 })));
+ test.fail('expected create to complain about unresolved conflicts');
+ } catch(e) {
+ test.assertEqual(
+ 'Error: Remaining conflicting property: a',
+ e.toString(),
+ 'conflicting prop error'
+ );
+ }
+};
+
+exports['test:verify that required properties are present but undefined'] =
+function(test) {
+ try {
+ let o4 = Object.create(Object.prototype, trait({ foo: required }));
+ test.assertEqual(true, 'foo' in o4, 'required property present');
+ try {
+ let foo = o4.foo;
+ test.fail('access to required property must throw');
+ } catch(e) {
+ test.assertEqual(
+ 'Error: Missing required property: foo',
+ e.toString(),
+ 'required prop error'
+ )
+ }
+ } catch(e) {
+ test.fail('did not expect create to complain about required props');
+ }
+};
+
+exports['test:verify that conflicting properties are present'] =
+function(test) {
+ try {
+ let o5 = Object.create(
+ Object.prototype,
+ compose(trait({ a: 0 }), trait({ a: 1 }))
+ );
+ test.assertEqual(true, 'a' in o5, 'conflicting property present');
+ try {
+ let a = o5.a; // accessors or data prop
+ test.fail('expected conflicting prop to cause exception');
+ } catch (e) {
+ test.assertEqual(
+ 'Error: Remaining conflicting property: a',
+ e.toString(),
+ 'conflicting prop access error'
+ );
+ }
+ } catch(e) {
+ test.fail('did not expect create to complain about conflicting props');
+ }
+};
+
+exports['test diamond with conflicts'] = function(test) {
+ function makeT1(x) trait({ m: function() { return x; } })
+ function makeT2(x) compose(trait({ t2: 'foo' }), makeT1(x))
+ function makeT3(x) compose(trait({ t3: 'bar' }), makeT1(x))
+
+ let T4 = compose(makeT2(5), makeT3(5));
+ try {
+ let o = create(Object.prototype, T4);
+ test.fail('expected diamond prop to cause exception');
+ } catch(e) {
+ test.assertEqual(
+ 'Error: Remaining conflicting property: m',
+ e.toString(),
+ 'diamond prop conflict'
+ );
+ }
+};
+
diff --git a/tools/addon-sdk-1.12/test/test-traits.js b/tools/addon-sdk-1.12/test/test-traits.js
new file mode 100644
index 0000000..8e01a70
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-traits.js
@@ -0,0 +1,398 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Trait } = require('sdk/deprecated/traits');
+
+exports['test:simple compose'] = function(test) {
+ let List = Trait.compose({
+ _list: null,
+ constructor: function List() {
+ this._list = [];
+ },
+ list: function list() this._list.slice(0),
+ add: function add(item) this._list.push(item),
+ remove: function remove(item) {
+ let list = this._list;
+ let index = list.indexOf(item);
+ if (0 <= index) list.slice(index, 1);
+ }
+ });
+
+ test.assertNotEqual(undefined, List, 'should not be undefined');
+ test.assertEqual('function', typeof List, 'type should be function');
+ test.assertEqual(
+ Trait.compose,
+ List.compose,
+ 'should inherit static compose'
+ );
+ test.assertEqual(
+ Trait.override,
+ List.override,
+ 'should inherit static override'
+ );
+ test.assertEqual(
+ Trait.required,
+ List.required,
+ 'should inherit static required'
+ );
+ test.assertEqual(
+ Trait.resolve,
+ List.resolve,
+ 'should inherit static resolve'
+ );
+
+ test.assert(
+ !('_list' in List.prototype),
+ 'should not expose private API'
+ );
+}
+exports['test: compose trait instance and create instance'] = function(test) {
+ let List = Trait.compose({
+ constructor: function List(options) {
+ this._list = [];
+ this._public.publicMember = options.publicMember;
+ },
+ _privateMember: true,
+ get privateMember() this._privateMember,
+ get list() this._list.slice(0),
+ add: function add(item) this._list.push(item),
+ remove: function remove(item) {
+ let list = this._list
+ let index = list.indexOf(item)
+ if (0 <= index) list.slice(index, 1)
+ }
+ });
+ let list = List({ publicMember: true });
+
+ test.assertEqual('object', typeof list, 'should return an object')
+ test.assertEqual(
+ true,
+ list instanceof List,
+ 'should be instance of a List'
+ );
+
+ test.assertEqual(
+ undefined,
+ list._privateMember,
+ 'instance should not expose private API'
+ );
+
+ test.assertEqual(
+ true,
+ list.privateMember,
+ 'privates are accessible by public API'
+ );
+
+ list._privateMember = false;
+
+ test.assertEqual(
+ true,
+ list.privateMember,
+ 'property changes on instance must not affect privates'
+ );
+
+ test.assert(
+ !('_list' in list),
+ 'instance should not expose private members'
+ );
+
+ test.assertEqual(
+ true,
+ list.publicMember,
+ 'public members are exposed'
+ )
+ test.assertEqual(
+ 'function',
+ typeof list.add,
+ 'should be function'
+ )
+ test.assertEqual(
+ 'function',
+ typeof list.remove,
+ 'should be function'
+ );
+
+ list.add(1);
+ test.assertEqual(
+ 1,
+ list.list[0],
+ 'exposed public API should be able of modifying privates'
+ )
+};
+
+
+exports['test:instances must not be hackable'] = function(test) {
+ let SECRET = 'There is no secret!',
+ secret = null;
+
+ let Class = Trait.compose({
+ _secret: null,
+ protect: function(data) this._secret = data
+ });
+
+ let i1 = Class();
+ i1.protect(SECRET);
+
+ test.assertEqual(
+ undefined,
+ (function() this._secret).call(i1),
+ 'call / apply can\'t access private state'
+ );
+
+ let proto = Object.getPrototypeOf(i1);
+ try {
+ proto.reveal = function() this._secret;
+ secret = i1.reveal();
+ } catch(e) {}
+ test.assertNotEqual(
+ SECRET,
+ secret,
+ 'public __proto__ changes should not affect privates'
+ );
+ secret = null;
+
+ let Class2 = Trait.compose({
+ _secret: null,
+ protect: function(data) this._secret = data
+ });
+ let i2 = Class2();
+ i2.protect(SECRET);
+ try {
+ Object.prototype.reveal = function() this._secret;
+ secret = i2.reveal();
+ } catch(e) {}
+ test.assertNotEqual(
+ SECRET,
+ secret,
+ 'Object.prototype changes must not affect instances'
+ );
+}
+
+exports['test:instanceof'] = function(test) {
+ const List = Trait.compose({
+ // private API:
+ _list: null,
+ // public API
+ constructor: function List() {
+ this._list = []
+ },
+ get length() this._list.length,
+ add: function add(item) this._list.push(item),
+ remove: function remove(item) {
+ let list = this._list;
+ let index = list.indexOf(item);
+ if (0 <= index) list.slice(index, 1);
+ }
+ });
+
+ test.assert(List() instanceof List, 'Must be instance of List');
+ test.assert(new List() instanceof List, 'Must be instance of List');
+};
+
+exports['test:privates are unaccessible'] = function(test) {
+ const List = Trait.compose({
+ // private API:
+ _list: null,
+ // public API
+ constructor: function List() {
+ this._list = [];
+ },
+ get length() this._list.length,
+ add: function add(item) this._list.push(item),
+ remove: function remove(item) {
+ let list = this._list;
+ let index = list.indexOf(item);
+ if (0 <= index) list.slice(index, 1);
+ }
+ });
+
+ let list = List();
+ test.assert(!('_list' in list), 'no privates on instance');
+ test.assert(
+ !('_list' in List.prototype),
+ 'no privates on prototype'
+ );
+};
+
+exports['test:public API can access private API'] = function(test) {
+ const List = Trait.compose({
+ // private API:
+ _list: null,
+ // public API
+ constructor: function List() {
+ this._list = [];
+ },
+ get length() this._list.length,
+ add: function add(item) this._list.push(item),
+ remove: function remove(item) {
+ let list = this._list;
+ let index = list.indexOf(item);
+ if (0 <= index) list.slice(index, 1);
+ }
+ });
+ let list = List();
+
+ list.add('test');
+
+ test.assertEqual(
+ 1,
+ list.length,
+ 'should be able to add element and access it from public getter'
+ );
+};
+
+exports['test:required'] = function(test) {
+ const Enumerable = Trait.compose({
+ list: Trait.required,
+ forEach: function forEach(consumer) {
+ return this.list.forEach(consumer);
+ }
+ });
+
+ try {
+ let i = Enumerable();
+ test.fail('should throw when creating instance with required properties');
+ } catch(e) {
+ test.assertEqual(
+ 'Error: Missing required property: list',
+ e.toString(),
+ 'required prop error'
+ );
+ }
+};
+
+exports['test:compose with required'] = function(test) {
+ const List = Trait.compose({
+ // private API:
+ _list: null,
+ // public API
+ constructor: function List() {
+ this._list = [];
+ },
+ get length() this._list.length,
+ add: function add(item) this._list.push(item),
+ remove: function remove(item) {
+ let list = this._list;
+ let index = list.indexOf(item);
+ if (0 <= index) list.slice(index, 1);
+ }
+ });
+
+ const Enumerable = Trait.compose({
+ list: Trait.required,
+ forEach: function forEach(consumer) {
+ return this.list.forEach(consumer);
+ }
+ });
+
+ const EnumerableList = Enumerable.compose({
+ get list() this._list.slice(0)
+ }, List);
+
+ let array = [1,2, 'ab']
+ let l = EnumerableList(array);
+ array.forEach(function(element) l.add(element));
+ let number = 0;
+ l.forEach(function(element, index) {
+ number ++;
+ test.assertEqual(array[index], element, 'should mach array element')
+ });
+ test.assertEqual(
+ array.length,
+ number,
+ 'should perform as many asserts as elements in array'
+ );
+};
+
+exports['test:resolve'] = function(test) {
+ const List = Trait.compose({
+ // private API:
+ _list: null,
+ // public API
+ constructor: function List() {
+ this._list = [];
+ },
+ get length() this._list.length,
+ add: function add(item) this._list.push(item),
+ remove: function remove(item) {
+ let list = this._list;
+ let index = list.indexOf(item);
+ if (0 <= index) list.slice(index, 1);
+ }
+ });
+
+ const Range = List.resolve({
+ constructor: null,
+ add: '_add',
+ }).compose({
+ min: null,
+ max: null,
+ get list() this._list.slice(0),
+ constructor: function Range(min, max) {
+ this.min = min;
+ this.max = max;
+ this._list = [];
+ },
+ add: function(item) {
+ if (item <= this.max && item >= this.min)
+ this._add(item)
+ }
+ });
+
+ let r = Range(0, 10);
+
+ test.assertEqual(
+ 0,
+ r.min,
+ 'constructor must have set min'
+ );
+ test.assertEqual(
+ 10,
+ r.max,
+ 'constructor must have set max'
+ );
+
+ test.assertEqual(
+ 0,
+ r.length,
+ 'should not contain any elements'
+ );
+
+ r.add(5);
+
+ test.assertEqual(
+ 1,
+ r.length,
+ 'should add `5` to list'
+ );
+
+ r.add(12);
+
+ test.assertEqual(
+ 1,
+ r.length,
+ 'should not add `12` to list'
+ );
+};
+
+exports['test:custom iterator'] = function(test) {
+ let Sub = Trait.compose({
+ foo: "foo",
+ bar: "bar",
+ baz: "baz",
+ __iterator__: function() {
+ yield 1;
+ yield 2;
+ yield 3;
+ }
+ });
+
+ let (i = 0, sub = Sub()) {
+ for (let item in sub)
+ test.assertEqual(++i, item, "iterated item has the right value");
+ };
+};
+
diff --git a/tools/addon-sdk-1.12/test/test-type.js b/tools/addon-sdk-1.12/test/test-type.js
new file mode 100644
index 0000000..99e11de
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-type.js
@@ -0,0 +1,92 @@
+/* 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 utils = require("sdk/lang/type");
+
+exports["test function"] = function (assert) {
+ assert.ok(utils.isFunction(function(){}), "value is function");
+ assert.ok(utils.isFunction(Object), "Object is function");
+ assert.ok(utils.isFunction(new Function("")), "Genertaed value is function");
+ assert.ok(!utils.isFunction({}), "object is not a function");
+ assert.ok(!utils.isFunction(4), "number is not a function");
+};
+
+exports["test atoms"] = function (assert) {
+ assert.ok(utils.isPrimitive(2), "number is primitive");
+ assert.ok(utils.isPrimitive(NaN), "`NaN` is primitve");
+ assert.ok(utils.isPrimitive(undefined), "`undefined` is primitive");
+ assert.ok(utils.isPrimitive(null), "`null` is primitive");
+ assert.ok(utils.isPrimitive(Infinity), "`Infinity` is primitive");
+ assert.ok(utils.isPrimitive("foo"), "strings are primitive");
+ assert.ok(utils.isPrimitive(true) && utils.isPrimitive(false),
+ "booleans are primitive");
+};
+
+exports["test object"] = function (assert) {
+ assert.ok(utils.isObject({}), "`{}` is object");
+ assert.ok(!utils.isObject(null), "`null` is not an object");
+ assert.ok(!utils.isObject(Object), "functions is not an object");
+};
+
+exports["test flat objects"] = function (assert) {
+ assert.ok(utils.isFlat({}), "`{}` is a flat object");
+ assert.ok(!utils.isFlat([]), "`[]` is not a flat object");
+ assert.ok(!utils.isFlat(new function() {}), "derived objects are not flat");
+ assert.ok(utils.isFlat(Object.prototype), "Object.prototype is flat");
+};
+
+exports["test json atoms"] = function (assert) {
+ assert.ok(utils.isJSON(null), "`null` is JSON");
+ assert.ok(utils.isJSON(undefined), "`undefined` is JSON");
+ assert.ok(utils.isJSON(NaN), "`NaN` is JSON");
+ assert.ok(utils.isJSON(Infinity), "`Infinity` is JSON");
+ assert.ok(utils.isJSON(true) && utils.isJSON(false), "booleans are JSON");
+ assert.ok(utils.isJSON(4), utils.isJSON(0), "numbers are JSON");
+ assert.ok(utils.isJSON("foo bar"), "strings are JSON");
+};
+
+exports["test instanceOf"] = function (assert) {
+ assert.ok(utils.instanceOf(assert, Object),
+ "assert is object from other sandbox");
+ assert.ok(utils.instanceOf(new Date(), Date), "instance of date");
+ assert.ok(!utils.instanceOf(null, Object), "null is not an instance");
+};
+
+exports["test json"] = function (assert) {
+ assert.ok(!utils.isJSON(function(){}), "functions are not json");
+ assert.ok(utils.isJSON({}), "`{}` is JSON");
+ assert.ok(utils.isJSON({
+ a: "foo",
+ b: 3,
+ c: undefined,
+ d: null,
+ e: {
+ f: {
+ g: "bar",
+ p: [{}, "oueou", 56]
+ },
+ q: { nan: NaN, infinity: Infinity },
+ "non standard name": "still works"
+ }
+ }), "JSON can contain nested objects");
+
+ var foo = {};
+ var bar = { foo: foo };
+ foo.bar = bar;
+ assert.ok(!utils.isJSON(foo), "recursive objects are not json");
+
+
+ assert.ok(!utils.isJSON({ get foo() { return 5 } }),
+ "json can not have getter");
+
+ assert.ok(!utils.isJSON({ foo: "bar", baz: function () {} }),
+ "json can not contain functions");
+
+ assert.ok(!utils.isJSON(Object.create({})),
+ "json must be direct descendant of `Object.prototype`");
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-unit-test.js b/tools/addon-sdk-1.12/test/test-unit-test.js
new file mode 100644
index 0000000..d6a1451
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-unit-test.js
@@ -0,0 +1,254 @@
+/* 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 timer = require("sdk/timers");
+const { Loader } = require("sdk/test/loader");
+
+var setupCalled = false, teardownCalled = false;
+
+exports.setup = function() {
+ setupCalled = true;
+};
+
+exports.teardown = function() {
+ teardownCalled = true;
+ setupCalled = false;
+};
+
+// Important note - unit tests are run in alphabetical order. The following
+// unit tests for setup/teardown are order dependent, sometimes the result of
+// one test is checked in the next test (testing for teardown does this). When
+// tests are cohesively a single unit, they are named <test_name> - partN where
+// N is their order in the sequence. Secondly, because these tests should be
+// run before all others, they start with an A.
+exports.testASetupTeardownSyncTestPart1 = function(test) {
+ test.assertEqual(true, setupCalled, 'setup function was called before this');
+ test.assertEqual(false, teardownCalled, 'teardown function was not called before this');
+};
+
+exports.testASetupTeardownSyncTestPart2 = function(test) {
+ test.assertEqual(true, setupCalled, 'setup was re-called before this');
+ test.assertEqual(true, teardownCalled, 'teardown was called after first function');
+};
+
+exports.testATeardownAsyncTestPart1 = function(test) {
+ teardownCalled = false;
+
+ timer.setTimeout(function() {
+ test.assertEqual(false, teardownCalled, "teardown not called until done");
+ test.done();
+ }, 200);
+ test.waitUntilDone();
+};
+
+exports.testATeardownAsyncTestPart2 = function(test) {
+ test.assertEqual(true, teardownCalled, "teardown called after done");
+};
+
+exports.testWaitUntilInstant = function(test) {
+ test.waitUntilDone();
+
+ test.waitUntil(function () true, "waitUntil with instant true pass")
+ .then(function () test.done());
+}
+
+exports.testWaitUntil = function(test) {
+ test.waitUntilDone();
+ let succeed = false;
+
+ test.waitUntil(function () succeed, "waitUntil pass")
+ .then(function () test.done());
+
+ timer.setTimeout(function () {
+ succeed = true;
+ }, 200);
+}
+
+exports.testWaitUntilEqual = function(test) {
+ test.waitUntilDone();
+ let succeed = false;
+
+ test.waitUntilEqual("foo", function () succeed ? "foo" : "bar",
+ "waitUntilEqual pass")
+ .then(function () test.done());
+
+ timer.setTimeout(function () {
+ succeed = true;
+ }, 200);
+}
+
+exports.testWaitUntilNotEqual = function(test) {
+ test.waitUntilDone();
+ let succeed = false;
+
+ test.waitUntilNotEqual("foo", function () succeed ? "bar" : "foo",
+ "waitUntilNotEqual pass")
+ .then(function () test.done());
+
+ timer.setTimeout(function () {
+ succeed = true;
+ }, 200);
+}
+
+exports.testWaitUntilMatches = function(test) {
+ test.waitUntilDone();
+ let succeed = false;
+
+ test.waitUntilMatches(function () succeed ? "foo" : "bar",
+ /foo/, "waitUntilEqual pass")
+ .then(function () test.done());
+
+ timer.setTimeout(function () {
+ succeed = true;
+ }, 200);
+}
+
+exports.testWaitUntilErrorInCallback = function(test) {
+ test.waitUntilDone();
+
+ test.expectFail(function() {
+ test.waitUntil(function () {throw "oops"}, "waitUntil pass")
+ .then(function () test.done());
+ });
+}
+
+exports.testWaitUntilTimeoutInCallback = function(test) {
+ test.waitUntilDone(1000);
+
+ let runner = new (require("sdk/deprecated/unit-test").TestRunner)({
+ console: {
+ calls: 0,
+ error: function(msg) {
+ this.calls++;
+ if (this.calls == 1)
+ test.assertEqual(arguments[0], "TEST FAILED: wait4ever (timed out)");
+ else if (this.calls == 2) {
+ test.assertEqual(arguments[0], "test assertion never became true:\n");
+ test.assertEqual(arguments[1], "assertion failed, value is false\n");
+ // We could additionally check that arguments[1] contains the correct
+ // stack, but it would be difficult to do so given that it contains
+ // resource: URLs with a randomly generated string embedded in them
+ // (the ID of the test addon created to run the tests). And in any
+ // case, checking the arguments seems sufficient.
+
+ test.done();
+ }
+ else {
+ test.fail("We got unexpected console.error() calls from waitUntil" +
+ " assertion callback: '" + arguments[1] + "'");
+ }
+ },
+ info: function (msg) {
+ test.assertEqual(msg, "executing 'wait4ever'");
+ },
+ trace: function () {}
+ }
+ });
+
+ runner.start({
+ test: {
+ name: "wait4ever",
+ testFunction: function(test) {
+ test.waitUntilDone(100);
+ test.waitUntil(function() false);
+ }
+ },
+ onDone: function() {}
+ });
+};
+
+exports.testExpectFail = function(test) {
+ test.expectFail(function() {
+ test.fail('expectFail masking .fail');
+ });
+
+ test.expectFail(function() {
+ test.assert(false, 'expectFail masking .assert');
+ });
+
+ test.assert(true, 'assert should pass with no expectFail');
+/*
+ test.expectFail(function() {
+ test.expectFail(function() {
+ test.fail('this should blow up');
+ });
+ });
+*/
+};
+
+exports.testAssertFunction = function(test) {
+ test.assertFunction(function() {}, 'assertFunction with function');
+ test.expectFail(function() {
+ test.assertFunction(null, 'assertFunction with non-function');
+ });
+};
+
+exports.testAssertUndefined = function(test) {
+ test.assertUndefined(undefined, 'assertUndefined with undefined');
+ test.expectFail(function() {
+ test.assertUndefined(null, 'assertUndefined with null');
+ });
+ test.expectFail(function() {
+ test.assertUndefined(false, 'assertUndefined with false');
+ });
+ test.expectFail(function() {
+ test.assertUndefined(0, 'assertUndefined with 0');
+ });
+};
+
+exports.testAssertNotUndefined = function(test) {
+ test.expectFail(function() {
+ test.assertNotUndefined(undefined, 'assertNotUndefined with undefined');
+ });
+ test.assertNotUndefined(null, 'assertNotUndefined with null');
+ test.assertNotUndefined(false, 'assertNotUndefined with false');
+ test.assertNotUndefined(0, 'assertNotUndefined with 0');
+};
+
+exports.testAssertNull = function(test) {
+ test.assertNull(null, 'assertNull with null');
+ test.expectFail(function() {
+ test.assertNull(undefined, 'assertNull with undefined');
+ });
+ test.expectFail(function() {
+ test.assertNull(false, 'assertNull with false');
+ });
+ test.expectFail(function() {
+ test.assertNull(0, 'assertNull with 0');
+ });
+};
+
+exports.testAssertNotNull = function(test) {
+ test.assertNotNull(undefined, 'assertNotNull with undefined');
+ test.assertNotNull(false, 'assertNotNull with false');
+ test.assertNotNull(0, 'assertNotNull with 0');
+
+ test.expectFail(function() {
+ test.assertNotNull(null, 'testAssertNotNull with null');
+ });
+};
+
+exports.testAssertObject = function(test) {
+ test.assertObject({}, 'assertObject with {}' );
+ test.assertObject(new Object(), 'assertObject with new Object');
+ test.expectFail(function() {
+ test.assertObject('fail', 'assertObject with string');
+ });
+};
+
+exports.testAssertString = function(test) {
+ test.assertString('', 'assertString with ""');
+ test.assertString(new String(), 'assertString with new String');
+};
+
+exports.testAssertArray = function(test) {
+ test.assertArray([], 'assertArray with []');
+ test.assertArray(new Array(), 'assertArray with new Array');
+};
+
+exports.testNumber = function(test) {
+ test.assertNumber(1, 'assertNumber with 1');
+ test.assertNumber(new Number('2'), 'assertNumber with new Number("2")' );
+};
+
diff --git a/tools/addon-sdk-1.12/test/test-unload.js b/tools/addon-sdk-1.12/test/test-unload.js
new file mode 100644
index 0000000..1bbb64e
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-unload.js
@@ -0,0 +1,167 @@
+/* -*- 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/. */
+
+var unload = require("sdk/system/unload");
+var { Loader } = require("sdk/test/loader");
+
+exports.testUnloading = function(test) {
+ var loader = Loader(module, {
+ console: Object.create(console, {
+ exception: { value: function(error) {
+ exceptions.push(error);
+ }}
+ })
+ });
+ var exceptions = [];
+ var ul = loader.require("sdk/system/unload");
+ var unloadCalled = 0;
+ function unload() {
+ unloadCalled++;
+ throw new Error("error");
+ }
+ ul.when(unload);
+
+ // This should be ignored, as we already registered it
+ ul.when(unload);
+
+ function unload2() { unloadCalled++; }
+ ul.when(unload2);
+ loader.unload();
+ test.assertEqual(unloadCalled, 2,
+ "Unloader functions are called on unload.");
+ test.assertEqual(exceptions.length, 1,
+ "One unload handler threw exception");
+};
+
+exports.testEnsure = function(test) {
+ test.assertRaises(function() { unload.ensure({}); },
+ "object has no 'unload' property",
+ "passing obj with no unload prop should fail");
+ test.assertRaises(function() { unload.ensure({}, "destroy"); },
+ "object has no 'destroy' property",
+ "passing obj with no custom unload prop should fail");
+
+ var called = 0;
+ var obj = {unload: function() { called++; }};
+
+ unload.ensure(obj);
+ obj.unload();
+ test.assertEqual(called, 1,
+ "unload() should be called");
+ obj.unload();
+ test.assertEqual(called, 1,
+ "unload() should be called only once");
+};
+
+/**
+ * Check that destructors are called only once with Traits.
+ * - check that public API is calling the destructor and unregister it,
+ * - check that composed traits with multiple ensure calls, leads to only
+ * one destructor call.
+ */
+exports.testEnsureWithTraits = function(test) {
+
+ let { Trait } = require("sdk/deprecated/traits");
+ let loader = Loader(module);
+ let ul = loader.require("sdk/system/unload");
+
+ let called = 0;
+ let composedCalled = 0;
+ let composedTrait = Trait.compose({
+ constructor: function () {
+ // We have to give "public interface" of this trait, as we want to
+ // call public `unload` method and ensure that we call it only once,
+ // either when we call this public function manually or on add-on unload
+ ul.ensure(this._public);
+ },
+ unload: function unload() {
+ composedCalled++;
+ }
+ });
+ let obj = Trait.compose(
+ composedTrait.resolve({
+ constructor: "_constructor",
+ unload : "_unload"
+ }), {
+ constructor: function constructor() {
+ // Same thing applies here, we need to pass public interface
+ ul.ensure(this._public);
+ this._constructor();
+ },
+ unload: function unload() {
+ called++;
+ this._unload();
+ }
+ })();
+
+ obj.unload();
+ test.assertEqual(called, 1,
+ "unload() should be called");
+
+ test.assertEqual(composedCalled, 1,
+ "composed object unload() should be called");
+
+ obj.unload();
+ test.assertEqual(called, 1,
+ "unload() should be called only once");
+ test.assertEqual(composedCalled, 1,
+ "composed object unload() should be called only once");
+
+ loader.unload();
+ test.assertEqual(called, 1,
+ "unload() should be called only once, after addon unload");
+ test.assertEqual(composedCalled, 1,
+ "composed object unload() should be called only once, " +
+ "after addon unload");
+};
+
+exports.testEnsureWithTraitsPrivate = function(test) {
+
+ let { Trait } = require("sdk/deprecated/traits");
+ let loader = Loader(module);
+ let ul = loader.require("sdk/system/unload");
+
+ let called = 0;
+ let privateObj = null;
+ let obj = Trait.compose({
+ constructor: function constructor() {
+ // This time wa don't have to give public interface,
+ // as we want to call a private method:
+ ul.ensure(this, "_unload");
+ privateObj = this;
+ },
+ _unload: function unload() {
+ called++;
+ this._unload();
+ }
+ })();
+
+ loader.unload();
+ test.assertEqual(called, 1,
+ "unload() should be called");
+
+ privateObj._unload();
+ test.assertEqual(called, 1,
+ "_unload() should be called only once, after addon unload");
+};
+
+exports.testReason = function (test) {
+ var reason = "Reason doesn't actually have to be anything in particular.";
+ var loader = Loader(module);
+ var ul = loader.require("sdk/system/unload");
+ ul.when(function (rsn) {
+ test.assertEqual(rsn, reason,
+ "when() reason should be reason given to loader");
+ });
+ var obj = {
+ unload: function (rsn) {
+ test.assertEqual(rsn, reason,
+ "ensure() reason should be reason given to loader");
+ }
+ };
+ ul.ensure(obj);
+ loader.unload(reason);
+};
diff --git a/tools/addon-sdk-1.12/test/test-url.js b/tools/addon-sdk-1.12/test/test-url.js
new file mode 100644
index 0000000..3b5cec2
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-url.js
@@ -0,0 +1,255 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var url = require("sdk/url");
+var { packed } = require("sdk/self");
+
+exports.testResolve = function(test) {
+ test.assertEqual(url.URL("bar", "http://www.foo.com/").toString(),
+ "http://www.foo.com/bar");
+
+ test.assertEqual(url.URL("bar", "http://www.foo.com"),
+ "http://www.foo.com/bar");
+
+ test.assertEqual(url.URL("http://bar.com/", "http://foo.com/"),
+ "http://bar.com/",
+ "relative should override base");
+
+ test.assertRaises(function() { url.URL("blah"); },
+ "malformed URI: blah",
+ "url.resolve() should throw malformed URI on base");
+
+ test.assertRaises(function() { url.URL("chrome://global"); },
+ "invalid URI: chrome://global",
+ "url.resolve() should throw invalid URI on base");
+
+ test.assertRaises(function() { url.URL("chrome://foo/bar"); },
+ "invalid URI: chrome://foo/bar",
+ "url.resolve() should throw on bad chrome URI");
+
+ test.assertEqual(url.URL("", "http://www.foo.com"),
+ "http://www.foo.com/",
+ "url.resolve() should add slash to end of domain");
+};
+
+exports.testParseHttp = function(test) {
+ var info = url.URL("http://foo.com/bar");
+ test.assertEqual(info.scheme, "http");
+ test.assertEqual(info.host, "foo.com");
+ test.assertEqual(info.port, null);
+ test.assertEqual(info.userPass, null);
+ test.assertEqual(info.path, "/bar");
+};
+
+exports.testParseHttpWithPort = function(test) {
+ var info = url.URL("http://foo.com:5/bar");
+ test.assertEqual(info.port, 5);
+};
+
+exports.testParseChrome = function(test) {
+ var info = url.URL("chrome://global/content/blah");
+ test.assertEqual(info.scheme, "chrome");
+ test.assertEqual(info.host, "global");
+ test.assertEqual(info.port, null);
+ test.assertEqual(info.userPass, null);
+ test.assertEqual(info.path, "/content/blah");
+};
+
+exports.testParseAbout = function(test) {
+ var info = url.URL("about:boop");
+ test.assertEqual(info.scheme, "about");
+ test.assertEqual(info.host, null);
+ test.assertEqual(info.port, null);
+ test.assertEqual(info.userPass, null);
+ test.assertEqual(info.path, "boop");
+};
+
+exports.testParseFTP = function(test) {
+ var info = url.URL("ftp://1.2.3.4/foo");
+ test.assertEqual(info.scheme, "ftp");
+ test.assertEqual(info.host, "1.2.3.4");
+ test.assertEqual(info.port, null);
+ test.assertEqual(info.userPass, null);
+ test.assertEqual(info.path, "/foo");
+};
+
+exports.testParseFTPWithUserPass = function(test) {
+ var info = url.URL("ftp://user:pass@1.2.3.4/foo");
+ test.assertEqual(info.userPass, "user:pass");
+};
+
+exports.testToFilename = function(test) {
+ test.assertRaises(
+ function() { url.toFilename("resource://nonexistent"); },
+ "resource does not exist: resource://nonexistent/",
+ "url.toFilename() on nonexistent resources should throw"
+ );
+
+ if (!packed)
+ test.assertMatches(url.toFilename(module.uri),
+ /.*test-url\.js$/,
+ "url.toFilename() on resource: URIs should work");
+ else
+ test.assertRaises(
+ function() { url.toFilename(module.uri); },
+ "cannot map to filename: "+module.uri,
+ "url.toFilename() can fail for packed XPIs");
+
+ test.assertRaises(
+ function() { url.toFilename("http://foo.com/"); },
+ "cannot map to filename: http://foo.com/",
+ "url.toFilename() on http: URIs should raise error"
+ );
+
+ try {
+ test.assertMatches(
+ url.toFilename("chrome://global/content/console.xul"),
+ /.*console\.xul$/,
+ "url.toFilename() w/ console.xul works when it maps to filesystem"
+ );
+ } catch (e) {
+ if (/chrome url isn\'t on filesystem/.test(e.message))
+ test.pass("accessing console.xul in jar raises exception");
+ else
+ test.fail("accessing console.xul raises " + e);
+ }
+
+ // TODO: Are there any chrome URLs that we're certain exist on the
+ // filesystem?
+ // test.assertMatches(url.toFilename("chrome://myapp/content/main.js"),
+ // /.*main\.js$/);
+};
+
+exports.testFromFilename = function(test) {
+ var profileDirName = require("sdk/system").pathFor("ProfD");
+ var fileUrl = url.fromFilename(profileDirName);
+ test.assertEqual(url.URL(fileUrl).scheme, 'file',
+ 'url.toFilename() should return a file: url');
+ test.assertEqual(url.fromFilename(url.toFilename(fileUrl)),
+ fileUrl);
+};
+
+exports.testURL = function(test) {
+ let URL = url.URL;
+ test.assert(URL("h:foo") instanceof URL, "instance is of correct type");
+ test.assertRaises(function() URL(),
+ "malformed URI: undefined",
+ "url.URL should throw on undefined");
+ test.assertRaises(function() URL(""),
+ "malformed URI: ",
+ "url.URL should throw on empty string");
+ test.assertRaises(function() URL("foo"),
+ "malformed URI: foo",
+ "url.URL should throw on invalid URI");
+ test.assert(URL("h:foo").scheme, "has scheme");
+ test.assertEqual(URL("h:foo").toString(),
+ "h:foo",
+ "toString should roundtrip");
+ // test relative + base
+ test.assertEqual(URL("mypath", "http://foo").toString(),
+ "http://foo/mypath",
+ "relative URL resolved to base");
+ // test relative + no base
+ test.assertRaises(function() URL("path").toString(),
+ "malformed URI: path",
+ "no base for relative URI should throw");
+
+ let a = URL("h:foo");
+ let b = URL(a);
+ test.assertEqual(b.toString(),
+ "h:foo",
+ "a URL can be initialized from another URL");
+ test.assertNotStrictEqual(a, b,
+ "a URL initialized from another URL is not the same object");
+ test.assert(a == "h:foo",
+ "toString is implicit when a URL is compared to a string via ==");
+ test.assertStrictEqual(a + "", "h:foo",
+ "toString is implicit when a URL is concatenated to a string");
+};
+
+exports.testStringInterface = function(test) {
+ let URL = url.URL;
+ var EM = "about:addons";
+ var a = URL(EM);
+
+ // make sure the standard URL properties are enumerable and not the String interface bits
+ test.assertEqual(Object.keys(a), "scheme,userPass,host,port,path", "enumerable key list check for URL.");
+ test.assertEqual(
+ JSON.stringify(a),
+ "{\"scheme\":\"about\",\"userPass\":null,\"host\":null,\"port\":null,\"path\":\"addons\"}",
+ "JSON.stringify should return a object with correct props and vals.");
+
+ // make sure that the String interface exists and works as expected
+ test.assertEqual(a.indexOf(":"), EM.indexOf(":"), "indexOf on URL works");
+ test.assertEqual(a.valueOf(), EM.valueOf(), "valueOf on URL works.");
+ test.assertEqual(a.toSource(), EM.toSource(), "toSource on URL works.");
+ test.assertEqual(a.lastIndexOf("a"), EM.lastIndexOf("a"), "lastIndexOf on URL works.");
+ test.assertEqual(a.match("t:").toString(), EM.match("t:").toString(), "match on URL works.");
+ test.assertEqual(a.toUpperCase(), EM.toUpperCase(), "toUpperCase on URL works.");
+ test.assertEqual(a.toLowerCase(), EM.toLowerCase(), "toLowerCase on URL works.");
+ test.assertEqual(a.split(":").toString(), EM.split(":").toString(), "split on URL works.");
+ test.assertEqual(a.charAt(2), EM.charAt(2), "charAt on URL works.");
+ test.assertEqual(a.charCodeAt(2), EM.charCodeAt(2), "charCodeAt on URL works.");
+ test.assertEqual(a.concat(EM), EM.concat(a), "concat on URL works.");
+ test.assertEqual(a.substr(2,3), EM.substr(2,3), "substr on URL works.");
+ test.assertEqual(a.substring(2,3), EM.substring(2,3), "substring on URL works.");
+ test.assertEqual(a.trim(), EM.trim(), "trim on URL works.");
+ test.assertEqual(a.trimRight(), EM.trimRight(), "trimRight on URL works.");
+ test.assertEqual(a.trimLeft(), EM.trimLeft(), "trimLeft on URL works.");
+}
+
+exports.testDataURLwithouthURI = function (test) {
+ const { DataURL } = url;
+
+ let dataURL = new DataURL();
+
+ test.assertEqual(dataURL.base64, false, "base64 is false for empty uri")
+ test.assertEqual(dataURL.data, "", "data is an empty string for empty uri")
+ test.assertEqual(dataURL.mimeType, "", "mimeType is an empty string for empty uri")
+ test.assertEqual(Object.keys(dataURL.parameters).length, 0, "parameters is an empty object for empty uri");
+
+ test.assertEqual(dataURL.toString(), "data:,");
+}
+
+exports.testDataURLwithMalformedURI = function (test) {
+ const { DataURL } = url;
+
+ test.assertRaises(function() {
+ let dataURL = new DataURL("http://www.mozilla.com/");
+ },
+ "Malformed Data URL: http://www.mozilla.com/",
+ "DataURL raises an exception for malformed data uri"
+ );
+}
+
+exports.testDataURLparse = function (test) {
+ const { DataURL } = url;
+
+ let dataURL = new DataURL("data:text/html;charset=US-ASCII,%3Ch1%3EHello!%3C%2Fh1%3E");
+
+ test.assertEqual(dataURL.base64, false, "base64 is false for non base64 data uri")
+ test.assertEqual(dataURL.data, "<h1>Hello!</h1>", "data is properly decoded")
+ test.assertEqual(dataURL.mimeType, "text/html", "mimeType is set properly")
+ test.assertEqual(Object.keys(dataURL.parameters).length, 1, "one parameters specified");
+ test.assertEqual(dataURL.parameters["charset"], "US-ASCII", "charset parsed");
+
+ test.assertEqual(dataURL.toString(), "data:text/html;charset=US-ASCII,%3Ch1%3EHello!%3C%2Fh1%3E");
+}
+
+exports.testDataURLparseBase64 = function (test) {
+ const { DataURL } = url;
+ const { decode } = require("sdk/base64");
+
+ let text = "Awesome!";
+ let b64text = "QXdlc29tZSE=";
+ let dataURL = new DataURL("data:text/plain;base64," + b64text);
+
+ test.assertEqual(dataURL.base64, true, "base64 is true for base64 encoded data uri")
+ test.assertEqual(dataURL.data, text, "data is properly decoded")
+ test.assertEqual(dataURL.mimeType, "text/plain", "mimeType is set properly")
+ test.assertEqual(Object.keys(dataURL.parameters).length, 1, "one parameters specified");
+ test.assertEqual(dataURL.parameters["base64"], "", "parameter set without value");
+
+ test.assertEqual(dataURL.toString(), "data:text/plain;base64," + encodeURIComponent(b64text));
+}
diff --git a/tools/addon-sdk-1.12/test/test-uuid.js b/tools/addon-sdk-1.12/test/test-uuid.js
new file mode 100644
index 0000000..3c9a4be
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-uuid.js
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { uuid } = require('sdk/util/uuid');
+
+exports['test generate uuid'] = function(assert) {
+ let signature = /{[0-9a-f\-]+}/
+ let first = String(uuid());
+ let second = String(uuid());
+
+ assert.ok(signature.test(first), 'first guid has a correct signature');
+ assert.ok(signature.test(second), 'second guid has a correct signature');
+ assert.notEqual(first, second, 'guid generates new guid on each call');
+};
+
+exports['test parse uuid'] = function(assert) {
+ let firefoxUUID = '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}';
+ let actual = uuid(firefoxUUID);
+
+ assert.equal(actual.number, firefoxUUID, 'uuid parsed given string');
+ assert.equal(String(actual), firefoxUUID, 'serializes to the same value');
+};
+
+require('test').run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-widget.js b/tools/addon-sdk-1.12/test/test-widget.js
new file mode 100644
index 0000000..78bc9f9
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-widget.js
@@ -0,0 +1,1152 @@
+/* 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('sdk/test/loader');
+const widgets = require("sdk/widget");
+const url = require("sdk/url");
+const windowUtils = require("sdk/deprecated/window-utils");
+const tabBrowser = require("sdk/deprecated/tab-browser");
+const timer = require("sdk/timers");
+
+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("sdk/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("sdk/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
+ 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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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;charset=utf-8,<body>foodle</body>";
+ let url2 = "data:text/html;charset=utf-8,<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("sdk/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);
+ test.assert(container().collapsed,
+ "1st window starts with an hidden addon-bar");
+
+ // Then open a browser window and verify that the addon-bar remains hidden
+ tabBrowser.addTab("about:blank", { inNewWindow: true, onLoad: function(e) {
+ let browserWindow2 = e.target.defaultView;
+ let doc2 = browserWindow2.document;
+ function container2() doc2.getElementById("addon-bar");
+ function widgetCount2() container2() ? container2().childNodes.length : 0;
+ let widgetStartCount2 = widgetCount2();
+ test.assert(container2().collapsed,
+ "2nd window starts with an hidden addon-bar");
+
+ 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");
+
+ // Reset addon-bar visibility before exiting this test
+ browserWindow.setToolbarVisibility(container(), true);
+
+ closeBrowserWindow(browserWindow2, 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("sdk/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("sdk/panel").Panel({
+ contentURL: "data:text/html;charset=utf-8,<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("sdk/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("sdk/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("sdk/panel").Panel({
+ contentURL: "data:text/html;charset=utf-8,<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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/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("sdk/widget");
+
+ let widget = new widgets.Widget({
+ id: "foo",
+ label: "foo",
+ content: "foo"
+ });
+ let view = widget.getView(require("sdk/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("sdk/deprecated/window-utils");
+ let widgets = require("sdk/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("sdk/deprecated/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("sdk/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.testContentScriptOptionsOption = function(test) {
+ test.waitUntilDone();
+
+ let widget = require("sdk/widget").Widget({
+ id: "fooz",
+ label: "fooz",
+ content: "fooz",
+ contentScript: "self.postMessage( [typeof self.options.d, self.options] );",
+ contentScriptWhen: "end",
+ contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}},
+ onMessage: function(msg) {
+ test.assertEqual( msg[0], 'undefined', 'functions are stripped from contentScriptOptions' );
+ test.assertEqual( typeof msg[1], 'object', 'object as contentScriptOptions' );
+ test.assertEqual( msg[1].a, true, 'boolean in contentScriptOptions' );
+ test.assertEqual( msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions' );
+ test.assertEqual( msg[1].c, 'string', 'string in contentScriptOptions' );
+ widget.destroy();
+ test.done();
+ }
+ });
+};
+
+exports.testOnAttachWithoutContentScript = function(test) {
+ test.waitUntilDone();
+
+ let widget = require("sdk/widget").Widget({
+ id: "onAttachNoCS",
+ label: "onAttachNoCS",
+ content: "onAttachNoCS",
+ onAttach: function (view) {
+ test.pass("received attach event");
+ widget.destroy();
+ test.done();
+ }
+ });
+};
+
+exports.testPostMessageOnAttach = function(test) {
+ test.waitUntilDone();
+
+ let widget = require("sdk/widget").Widget({
+ id: "onAttach",
+ label: "onAttach",
+ content: "onAttach",
+ // 1) Send a message immediatly after `attach` event
+ onAttach: function (view) {
+ view.postMessage("ok");
+ },
+ // 2) Listen to it and forward it back to the widget
+ contentScript: "self.on('message', self.postMessage);",
+ // 3) Listen to this forwarded message
+ onMessage: function (msg) {
+ test.assertEqual( msg, "ok", "postMessage works on `attach` event");
+ widget.destroy();
+ test.done();
+ }
+ });
+};
+
+exports.testPostMessageOnLocationChange = function(test) {
+ test.waitUntilDone();
+
+ let attachEventCount = 0;
+ let messagesCount = 0;
+ let widget = require("sdk/widget").Widget({
+ id: "onLocationChange",
+ label: "onLocationChange",
+ content: "onLocationChange",
+ contentScript: "new " + function ContentScriptScope() {
+ // Emit an event when content script is applied in order to know when
+ // the first document is loaded so that we can load the 2nd one
+ self.postMessage("ready");
+ // And forward any incoming message back to the widget to see if
+ // messaging is working on 2nd document
+ self.on("message", self.postMessage);
+ },
+ onMessage: function (msg) {
+ messagesCount++;
+ if (messagesCount == 1) {
+ test.assertEqual(msg, "ready", "First document is loaded");
+ widget.content = "location changed";
+ }
+ else if (messagesCount == 2) {
+ test.assertEqual(msg, "ready", "Second document is loaded");
+ widget.postMessage("ok");
+ }
+ else if (messagesCount == 3) {
+ test.assertEqual(msg, "ok",
+ "We receive the message sent to the 2nd document");
+ 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"});
+
+ // First wait for all 3 widgets to be added to the current browser window
+ let firstAttachCount = 0;
+ function onAttachFirstWindow(widget) {
+ if (++firstAttachCount<3)
+ return;
+ onWidgetsReady();
+ }
+ w1.once("attach", onAttachFirstWindow);
+ w2.once("attach", onAttachFirstWindow);
+ w3.once("attach", onAttachFirstWindow);
+
+ function getWidgetNode(toolbar, position) {
+ return toolbar.getElementsByTagName("toolbaritem")[position];
+ }
+ function openBrowserWindow() {
+ 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 = "about:blank";
+ return ww.openWindow(null, "chrome://browser/content/browser.xul",
+ "_blank", "chrome,all,dialog=no", urlString);
+ }
+
+ // Then move them before openeing a new browser window
+ function onWidgetsReady() {
+ // Hack to move 2nd and 3rd widgets manually to the navigation bar right after
+ // the search box.
+ 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");
+ let searchBox = doc.getElementById("search-container");
+ // Insert 3rd at the right of search box by adding it before its right sibling
+ navBar.insertItem(w3ToolbarItem.id, searchBox.nextSibling, null, false);
+ // Then insert 2nd before 3rd
+ navBar.insertItem(w2ToolbarItem.id, w3ToolbarItem, 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");
+
+ // Wait for all widget to be attached to this new window before checking
+ // their position
+ let attachCount = 0;
+ let browserWindow2;
+ function onAttach(widget) {
+ if (++attachCount < 3)
+ return;
+ let doc = browserWindow2.document;
+ let addonBar = doc.getElementById("addon-bar");
+ let searchBox = doc.getElementById("search-container");
+
+ // 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,
+ // i.e. right after search box
+ test.assertEqual(searchBox.nextSibling.getAttribute("label"), w2.label);
+ test.assertEqual(searchBox.nextSibling.nextSibling.getAttribute("label"), w3.label);
+
+ w1.destroy();
+ w2.destroy();
+ w3.destroy();
+
+ closeBrowserWindow(browserWindow2, function() {
+ test.done();
+ });
+ }
+ w1.on("attach", onAttach);
+ w2.on("attach", onAttach);
+ w3.on("attach", onAttach);
+
+ browserWindow2 = openBrowserWindow(browserWindow);
+ }
+};
+
+/******************* helpers *********************/
+
+// Helper for calling code at window close
+function closeBrowserWindow(window, callback) {
+ 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("sdk/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;
+
+ module.exports = {
+ testAppNotSupported: function (test) {
+ test.pass("the widget module does not support this application.");
+ }
+ }
+}
+
diff --git a/tools/addon-sdk-1.12/test/test-window-loader.js b/tools/addon-sdk-1.12/test/test-window-loader.js
new file mode 100644
index 0000000..b8fecce
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-window-loader.js
@@ -0,0 +1,120 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { WindowLoader } = require('sdk/windows/loader'),
+ { Trait } = require('sdk/deprecated/traits');
+
+const Loader = Trait.compose(
+ WindowLoader,
+ {
+ constructor: function Loader(options) {
+ this._onLoad = options.onLoad;
+ this._onUnload = options.onUnload;
+ if ('window' in options)
+ this._window = options.window;
+ this._load();
+ this.window = this._window;
+ },
+ window: null,
+ _onLoad: null,
+ _onUnload: null,
+ _tabOptions: []
+ }
+);
+
+exports['test compositions with missing required properties'] = function(test) {
+ test.assertRaises(
+ function() WindowLoader.compose({})(),
+ 'Missing required property: _onLoad',
+ 'should throw missing required property exception'
+ );
+ test.assertRaises(
+ function() WindowLoader.compose({ _onLoad: null, _tabOptions: null })(),
+ 'Missing required property: _onUnload',
+ 'should throw missing required property `_onUnload`'
+ );
+ test.assertRaises(
+ function() WindowLoader.compose({ _onUnload: null, _tabOptions: null })(),
+ 'Missing required property: _onLoad',
+ 'should throw missing required property `_onLoad`'
+ );
+ test.assertRaises(
+ function() WindowLoader.compose({ _onUnload: null, _onLoad: null })(),
+ 'Missing required property: _tabOptions',
+ 'should throw missing required property `_tabOptions`'
+ );
+};
+
+exports['test `load` events'] = function(test) {
+ test.waitUntilDone();
+ let onLoadCalled = false;
+ Loader({
+ onLoad: function(window) {
+ onLoadCalled = true;
+ test.assertEqual(
+ window, this._window, 'windows should match'
+ );
+ test.assertEqual(
+ window.document.readyState, 'complete', 'window must be fully loaded'
+ );
+ window.close();
+ },
+ onUnload: function(window) {
+ test.assertEqual(
+ window, this._window, 'windows should match'
+ );
+ test.assertEqual(
+ window.document.readyState, 'complete', 'window must be fully loaded'
+ );
+ test.assert(onLoadCalled, 'load callback is supposed to be called');
+ test.done();
+ }
+ });
+};
+
+exports['test removeing listeners'] = function(test) {
+ test.waitUntilDone();
+ Loader({
+ onLoad: function(window) {
+ test.assertEqual(
+ window, this._window, 'windows should match'
+ );
+ window.close();
+ },
+ onUnload: function(window) {
+ test.done();
+ }
+ });
+};
+
+exports['test create loader from opened window'] = function(test) {
+ test.waitUntilDone();
+ let onUnloadCalled = false;
+ Loader({
+ onLoad: function(window) {
+ test.assertEqual(
+ window, this._window, 'windows should match'
+ );
+ test.assertEqual(
+ window.document.readyState, 'complete', 'window must be fully loaded'
+ );
+ Loader({
+ window: window,
+ onLoad: function(win) {
+ test.assertEqual(win, window, 'windows should match');
+ window.close();
+ },
+ onUnload: function(window) {
+ test.assert(onUnloadCalled, 'first handler should be called already');
+ test.done();
+ }
+ });
+ },
+ onUnload: function(window) {
+ onUnloadCalled = true;
+ }
+ });
+};
+
diff --git a/tools/addon-sdk-1.12/test/test-window-observer.js b/tools/addon-sdk-1.12/test/test-window-observer.js
new file mode 100644
index 0000000..4a6264a
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-window-observer.js
@@ -0,0 +1,50 @@
+/* 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("sdk/test/loader");
+const timer = require("sdk/timers");
+
+exports["test unload window observer"] = function(assert, done) {
+ // Hacky way to be able to create unloadable modules via makeSandboxedLoader.
+ let loader = Loader(module);
+
+ let utils = loader.require("sdk/deprecated/window-utils");
+ let { activeBrowserWindow: activeWindow } = utils;
+ let { isBrowser } = require('sdk/window/utils');
+ let observer = loader.require("sdk/windows/observer").observer;
+ let opened = 0;
+ let closed = 0;
+
+ observer.on("open", function onOpen(window) {
+ // Ignoring non-browser windows
+ if (isBrowser(window))
+ opened++;
+ });
+ observer.on("close", function onClose(window) {
+ // Ignore non-browser windows & already opened `activeWindow` (unload will
+ // emit close on it even though it is not actually closed).
+ if (isBrowser(window) && window !== activeWindow)
+ closed++;
+ });
+
+ // Open window and close it to trigger observers.
+ activeWindow.open().close();
+
+ // Unload the module so that all listeners set by observer are removed.
+ loader.unload();
+
+ // Open and close window once again.
+ activeWindow.open().close();
+
+ // Enqueuing asserts to make sure that assertion is not performed early.
+ timer.setTimeout(function () {
+ assert.equal(1, opened, "observer open was called before unload only");
+ assert.equal(1, closed, "observer close was called before unload only");
+ done();
+ }, 0);
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-window-utils.js b/tools/addon-sdk-1.12/test/test-window-utils.js
new file mode 100644
index 0000000..c93b0d4
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-window-utils.js
@@ -0,0 +1,350 @@
+/* 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 windowUtils = require("sdk/deprecated/window-utils");
+var timer = require("sdk/timers");
+var { Cc, Ci } = require("chrome");
+var { Loader, unload } = require("sdk/test/loader");
+
+function toArray(iterator) {
+ let array = [];
+ for each (let item in iterator)
+ array.push(item);
+ return array;
+}
+
+function makeEmptyWindow() {
+ var xulNs = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var blankXul = ('<?xml version="1.0"?>' +
+ '<?xml-stylesheet href="chrome://global/skin/" ' +
+ ' type="text/css"?>' +
+ '<window xmlns="' + xulNs + '" windowtype="test:window">' +
+ '</window>');
+ var url = "data:application/vnd.mozilla.xul+xml;charset=utf-8," + escape(blankXul);
+ var features = ["chrome", "width=10", "height=10"];
+
+ var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(Ci.nsIWindowWatcher);
+ return ww.openWindow(null, url, null, features.join(","), null);
+}
+
+exports['test close on unload'] = function(assert) {
+ var timesClosed = 0;
+ var fakeWindow = {
+ _listeners: [],
+ addEventListener: function(name, func, bool) {
+ this._listeners.push(func);
+ },
+ removeEventListener: function(name, func, bool) {
+ var index = this._listeners.indexOf(func);
+ if (index == -1)
+ throw new Error("event listener not found");
+ this._listeners.splice(index, 1);
+ },
+ close: function() {
+ timesClosed++;
+ this._listeners.forEach(
+ function(func) {
+ func({target: fakeWindow.document});
+ });
+ },
+ document: {
+ get defaultView() { return fakeWindow; }
+ }
+ };
+
+ let loader = Loader(module);
+ loader.require("sdk/deprecated/window-utils").closeOnUnload(fakeWindow);
+ assert.equal(fakeWindow._listeners.length, 1,
+ "unload listener added on closeOnUnload()");
+ assert.equal(timesClosed, 0,
+ "window not closed when registered.");
+ loader.unload();
+ assert.equal(timesClosed, 1,
+ "window closed on module unload.");
+ assert.equal(fakeWindow._listeners.length, 0,
+ "unload event listener removed on module unload");
+
+ timesClosed = 0;
+ loader = Loader(module);
+ loader.require("sdk/deprecated/window-utils").closeOnUnload(fakeWindow);
+ assert.equal(timesClosed, 0,
+ "window not closed when registered.");
+ fakeWindow.close();
+ assert.equal(timesClosed, 1,
+ "window closed when close() called.");
+ assert.equal(fakeWindow._listeners.length, 0,
+ "unload event listener removed on window close");
+ loader.unload();
+ assert.equal(timesClosed, 1,
+ "window not closed again on module unload.");
+};
+
+exports['test window watcher'] = function(assert, done) {
+ var myWindow;
+ var finished = false;
+
+ var delegate = {
+ onTrack: function(window) {
+ if (window == myWindow) {
+ assert.pass("onTrack() called with our test window");
+ timer.setTimeout(function() { myWindow.close(); }, 1);
+ }
+ },
+ onUntrack: function(window) {
+ if (window == myWindow) {
+ assert.pass("onUntrack() called with our test window");
+ timer.setTimeout(function() {
+ if (!finished) {
+ finished = true;
+ myWindow = null;
+ wt.unload();
+ done();
+ } else
+ assert.fail("finishTest() called multiple times.");
+ }, 1);
+ }
+ }
+ };
+
+ // test bug 638007 (new is optional), using new
+ var wt = new windowUtils.WindowTracker(delegate);
+ myWindow = makeEmptyWindow();
+};
+
+exports['test window watcher untracker'] = function(assert, done) {
+ var myWindow;
+ var tracks = 0;
+ var unloadCalled = false;
+
+ var delegate = {
+ onTrack: function(window) {
+ tracks = tracks + 1;
+ if (window == myWindow) {
+ assert.pass("onTrack() called with our test window");
+ timer.setTimeout(function() {
+ myWindow.close();
+ }, 1);
+ }
+ },
+ onUntrack: function(window) {
+ tracks = tracks - 1;
+ if (window == myWindow && !unloadCalled) {
+ unloadCalled = true;
+ timer.setTimeout(function() {
+ wt.unload();
+ }, 1);
+ }
+ if (0 > tracks) {
+ assert.fail("WindowTracker onUntrack was called more times than onTrack..");
+ }
+ else if (0 == tracks) {
+ timer.setTimeout(function() {
+ myWindow = null;
+ done();
+ }, 1);
+ }
+ }
+ };
+
+ // test bug 638007 (new is optional), not using new
+ var wt = windowUtils.WindowTracker(delegate);
+ myWindow = makeEmptyWindow();
+};
+
+// test that _unregWindow calls _unregLoadingWindow
+exports['test window watcher unregs 4 loading wins'] = function(assert, done) {
+ var myWindow;
+ var finished = false;
+ let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator)
+ .getMostRecentWindow("navigator:browser");
+ var counter = 0;
+
+ var delegate = {
+ onTrack: function(window) {
+ var type = window.document.documentElement.getAttribute("windowtype");
+ if (type == "test:window")
+ assert.fail("onTrack shouldn't have been executed.");
+ }
+ };
+ var wt = new windowUtils.WindowTracker(delegate);
+
+ // make a new window
+ myWindow = makeEmptyWindow();
+
+ // make sure that the window hasn't loaded yet
+ assert.notEqual(
+ myWindow.document.readyState,
+ "complete",
+ "window hasn't loaded yet.");
+
+ // unload WindowTracker
+ wt.unload();
+
+ // make sure that the window still hasn't loaded, which means that the onTrack
+ // would have been removed successfully assuming that it doesn't execute.
+ assert.notEqual(
+ myWindow.document.readyState,
+ "complete",
+ "window still hasn't loaded yet.");
+
+ // wait for the window to load and then close it. onTrack wouldn't be called
+ // until the window loads, so we must let it load before closing it to be
+ // certain that onTrack was removed.
+ myWindow.addEventListener("load", function() {
+ // allow all of the load handles to execute before closing
+ myWindow.setTimeout(function() {
+ myWindow.addEventListener("unload", function() {
+ // once the window unloads test is done
+ done();
+ }, false);
+ myWindow.close();
+ }, 0);
+ }, false);
+}
+
+exports['test window watcher without untracker'] = function(assert, done) {
+ var myWindow;
+ var finished = false;
+
+ var delegate = {
+ onTrack: function(window) {
+ if (window == myWindow) {
+ assert.pass("onTrack() called with our test window");
+ timer.setTimeout(function() {
+ myWindow.close();
+
+ if (!finished) {
+ finished = true;
+ myWindow = null;
+ wt.unload();
+ done();
+ } else {
+ assert.fail("onTrack() called multiple times.");
+ }
+ }, 1);
+ }
+ }
+ };
+
+ var wt = new windowUtils.WindowTracker(delegate);
+ myWindow = makeEmptyWindow();
+};
+
+exports['test active window'] = function(assert, done) {
+ let testRunnerWindow = Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator)
+ .getMostRecentWindow("test:runner");
+ let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator)
+ .getMostRecentWindow("navigator:browser");
+
+ assert.equal(windowUtils.activeBrowserWindow, browserWindow,
+ "Browser window is the active browser window.");
+
+
+ let testSteps = [
+ function() {
+ windowUtils.activeWindow = browserWindow;
+ continueAfterFocus(browserWindow);
+ },
+ function() {
+ assert.equal(windowUtils.activeWindow, browserWindow,
+ "Correct active window [1]");
+ continueAfterFocus(windowUtils.activeWindow = testRunnerWindow);
+ },
+ function() {
+ assert.equal(windowUtils.activeWindow, testRunnerWindow,
+ "Correct active window [2]");
+ assert.equal(windowUtils.activeBrowserWindow, browserWindow,
+ "Correct active browser window [3]");
+ continueAfterFocus(windowUtils.activeWindow = browserWindow);
+ },
+ function() {
+ assert.equal(windowUtils.activeWindow, browserWindow,
+ "Correct active window [4]");
+ continueAfterFocus(windowUtils.activeWindow = testRunnerWindow);
+ },
+ function() {
+ assert.equal(windowUtils.activeWindow, testRunnerWindow,
+ "Correct active window [5]");
+ assert.equal(windowUtils.activeBrowserWindow, browserWindow,
+ "Correct active browser window [6]");
+ testRunnerWindow = null;
+ browserWindow = null;
+ done();
+ }
+ ];
+
+ let nextTest = function() {
+ let func = testSteps.shift();
+ if (func) {
+ func();
+ }
+ }
+
+ 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) {
+ nextTest();
+ } else {
+ childTargetWindow.addEventListener("focus", function focusListener() {
+ childTargetWindow.removeEventListener("focus", focusListener, true);
+ nextTest();
+ }, true);
+ }
+
+ }
+
+ nextTest();
+};
+
+exports['test windowIterator'] = function(assert, done) {
+ // make a new window
+ let window = makeEmptyWindow();
+
+ // make sure that the window hasn't loaded yet
+ assert.notEqual(
+ window.document.readyState,
+ "complete",
+ "window hasn't loaded yet.");
+
+ // this window should only appear in windowIterator() while its loading
+ assert.ok(toArray(windowUtils.windowIterator()).indexOf(window) === -1,
+ "window isn't in windowIterator()");
+
+ // Then it should be in windowIterator()
+ window.addEventListener("load", function onload() {
+ window.addEventListener("load", onload, false);
+ assert.ok(toArray(windowUtils.windowIterator()).indexOf(window) !== -1,
+ "window is now in windowIterator(false)");
+
+ // Wait for the window unload before ending test
+ window.addEventListener("unload", function onunload() {
+ window.addEventListener("unload", onunload, false);
+ done();
+ }, false);
+ window.close();
+ }, false);
+}
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-window-utils2.js b/tools/addon-sdk-1.12/test/test-window-utils2.js
new file mode 100644
index 0000000..a169746
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-window-utils2.js
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+const { Ci } = require('chrome');
+const { open, backgroundify, windows,
+ getXULWindow, getBaseWindow } = require('sdk/window/utils');
+const windowUtils = require('sdk/deprecated/window-utils');
+
+exports['test get nsIBaseWindow from nsIDomWindow'] = function(assert) {
+ let active = windowUtils.activeBrowserWindow;
+
+ assert.ok(!(active instanceof Ci.nsIBaseWindow),
+ 'active window is not nsIBaseWindow');
+
+ assert.ok(getBaseWindow(active) instanceof Ci.nsIBaseWindow,
+ 'base returns nsIBaseWindow');
+};
+
+exports['test get nsIXULWindow from nsIDomWindow'] = function(assert) {
+ let active = windowUtils.activeBrowserWindow;
+ assert.ok(!(active instanceof Ci.nsIXULWindow),
+ 'active window is not nsIXULWindow');
+ assert.ok(getXULWindow(active) instanceof Ci.nsIXULWindow,
+ 'base returns nsIXULWindow');
+};
+
+exports['test top window creation'] = function(assert) {
+ let window = open('data:text/html;charset=utf-8,Hello top window');
+ assert.ok(~windows().indexOf(window), 'window was opened');
+ window.close();
+};
+
+exports['test new top window with options'] = function(assert) {
+ let window = open('data:text/html;charset=utf-8,Hi custom top window', {
+ name: 'test',
+ features: { height: 100, width: 200, toolbar: true }
+ });
+ assert.ok(~windows().indexOf(window), 'window was opened');
+ assert.equal(window.name, 'test', 'name was set');
+ assert.equal(window.innerHeight, 100, 'height is set');
+ assert.equal(window.innerWidth, 200, 'height is set');
+ assert.equal(window.toolbar.visible, true, 'toolbar was set');
+ window.close();
+};
+
+exports['test backgroundify'] = function(assert) {
+ let window = open('data:text/html;charset=utf-8,backgroundy');
+ assert.ok(~windows().indexOf(window),
+ 'window is in the list of windows');
+ let backgroundy = backgroundify(window);
+ assert.equal(backgroundy, window, 'backgroundify returs give window back');
+ assert.ok(!~windows().indexOf(window),
+ 'backgroundifyied window is in the list of windows');
+ window.close();
+};
+
+require('test').run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-windows-common.js b/tools/addon-sdk-1.12/test/test-windows-common.js
new file mode 100644
index 0000000..05cd8bf
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-windows-common.js
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+const { Loader } = require('sdk/test/loader');
+const { browserWindows } = require('sdk/windows');
+
+// TEST: browserWindows Iterator
+exports.testBrowserWindowsIterator = function(test) {
+ let activeWindowCount = 0;
+ let windows = [];
+ let i = 0;
+ for each (let window in browserWindows) {
+ if (window === browserWindows.activeWindow)
+ activeWindowCount++;
+
+ test.assertEqual(windows.indexOf(window), -1, 'window not already in iterator');
+ test.assertEqual(browserWindows[i++], window, 'browserWindows[x] works');
+ windows.push(window);
+ }
+ test.assertEqual(activeWindowCount, 1, 'activeWindow was found in the iterator');
+
+ i = 0;
+ for (let j in browserWindows) {
+ test.assertEqual(j, i++, 'for (x in browserWindows) works');
+ }
+};
+
+exports.testWindowTabsObject_alt = function(test) {
+ test.waitUntilDone();
+
+ let window = browserWindows.activeWindow;
+ window.tabs.open({
+ url: "data:text/html;charset=utf-8,<title>tab 2</title>",
+ inBackground: true,
+ onReady: function onReady(tab) {
+ test.assertEqual(tab.title, "tab 2", "Correct new tab title");
+ test.assertNotEqual(window.tabs.activeTab, tab, "Correct active tab");
+
+ // end test
+ tab.close(test.done());
+ }
+ });
+};
+
+// TEST: browserWindows.activeWindow
+exports.testWindowActivateMethod_simple = function(test) {
+ let window = browserWindows.activeWindow;
+ let tab = window.tabs.activeTab;
+
+ window.activate();
+
+ test.assertEqual(browserWindows.activeWindow, window,
+ "Active window is active after window.activate() call");
+ test.assertEqual(window.tabs.activeTab, tab,
+ "Active tab is active after window.activate() call");
+
+};
diff --git a/tools/addon-sdk-1.12/test/test-windows-private-browsing.js b/tools/addon-sdk-1.12/test/test-windows-private-browsing.js
new file mode 100644
index 0000000..13bef93
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-windows-private-browsing.js
@@ -0,0 +1,35 @@
+/* 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 { browserWindows } = require('sdk/windows');
+const { pb, pbUtils } = require('private-browsing-helper');
+
+const wm = Cc['@mozilla.org/appshell/window-mediator;1'].
+ getService(Ci.nsIWindowMediator);
+
+if (pbUtils.isWindowPBEnabled(wm.getMostRecentWindow('navigator:browser'))) {
+ exports.testPerWindowPrivateBrowsing_getter = function(test) {
+ let activeWindow = wm.
+ getMostRecentWindow('navigator:browser');
+
+ // is per-window PB implemented?
+ let currentState = activeWindow.gPrivateBrowsingUI.privateWindow;
+
+ pbUtils.setMode(false, activeWindow);
+
+ test.assertEqual(activeWindow.gPrivateBrowsingUI.privateWindow,
+ browserWindows.activeWindow.isPrivateBrowsing,
+ 'Active window is not in PB mode');
+
+ pbUtils.setMode(true, activeWindow);
+
+ test.assertEqual(activeWindow.gPrivateBrowsingUI.privateWindow,
+ browserWindows.activeWindow.isPrivateBrowsing,
+ 'Active window is in PB mode');
+
+ pbUtils.setMode(currentState, activeWindow);
+ };
+} \ No newline at end of file
diff --git a/tools/addon-sdk-1.12/test/test-windows.js b/tools/addon-sdk-1.12/test/test-windows.js
new file mode 100644
index 0000000..064a0f6
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-windows.js
@@ -0,0 +1,11 @@
+/* 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';
+
+if (require("sdk/system/xul-app").is("Firefox")) {
+ module.exports = require("./windows/test-firefox-windows");
+}
+else if (require("sdk/system/xul-app").is("Fennec")) {
+ module.exports = require("./windows/test-fennec-windows");
+}
diff --git a/tools/addon-sdk-1.12/test/test-xhr.js b/tools/addon-sdk-1.12/test/test-xhr.js
new file mode 100644
index 0000000..6cf585b
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-xhr.js
@@ -0,0 +1,82 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var xhr = require("sdk/net/xhr");
+var timer = require("sdk/timers");
+var { Loader } = require("sdk/test/loader");
+var xulApp = require("sdk/system/xul-app");
+
+/* Test is intentionally disabled until platform bug 707256 is fixed.
+exports.testAbortedXhr = function(test) {
+ var req = new xhr.XMLHttpRequest();
+ test.assertEqual(xhr.getRequestCount(), 1);
+ req.abort();
+ test.assertEqual(xhr.getRequestCount(), 0);
+};
+*/
+
+exports.testLocalXhr = function(test) {
+ var req = new xhr.XMLHttpRequest();
+ req.overrideMimeType("text/plain");
+ req.open("GET", module.uri);
+ req.onreadystatechange = function() {
+ if (req.readyState == 4 && (req.status == 0 || req.status == 200)) {
+ test.assertMatches(req.responseText,
+ /onreadystatechange/,
+ "XMLHttpRequest should get local files");
+ timer.setTimeout(
+ function() { test.assertEqual(xhr.getRequestCount(), 0);
+ test.done(); },
+ 0
+ );
+ }
+ };
+ req.send(null);
+ test.assertEqual(xhr.getRequestCount(), 1);
+ test.waitUntilDone(4000);
+};
+
+exports.testUnload = function(test) {
+ var loader = Loader(module);
+ var sbxhr = loader.require("sdk/net/xhr");
+ var req = new sbxhr.XMLHttpRequest();
+ req.overrideMimeType("text/plain");
+ req.open("GET", module.uri);
+ req.send(null);
+ test.assertEqual(sbxhr.getRequestCount(), 1);
+ loader.unload();
+ test.assertEqual(sbxhr.getRequestCount(), 0);
+};
+
+exports.testResponseHeaders = function(test) {
+ var req = new xhr.XMLHttpRequest();
+ req.overrideMimeType("text/plain");
+ req.open("GET", module.uri);
+ req.onreadystatechange = function() {
+ if (req.readyState == 4 && (req.status == 0 || req.status == 200)) {
+ var headers = req.getAllResponseHeaders();
+ if (xulApp.versionInRange(xulApp.platformVersion, "13.0a1", "*")) {
+ headers = headers.split("\r\n");
+ if(headers.length == 1) {
+ headers = headers[0].split("\n");
+ }
+ for(let i in headers) {
+ if(headers[i] && headers[i].search("Content-Type") >= 0) {
+ test.assertEqual(headers[i], "Content-Type: text/plain",
+ "XHR's headers are valid");
+ }
+ }
+ }
+ else {
+ test.assert(headers === null || headers === "",
+ "XHR's headers are empty");
+ }
+ test.done();
+ }
+ };
+ req.send(null);
+ test.assertEqual(xhr.getRequestCount(), 1);
+ test.waitUntilDone(4000);
+}
+
diff --git a/tools/addon-sdk-1.12/test/test-xpcom.js b/tools/addon-sdk-1.12/test/test-xpcom.js
new file mode 100644
index 0000000..17c7f1c
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-xpcom.js
@@ -0,0 +1,223 @@
+/* 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 xpcom = require("sdk/platform/xpcom");
+const { Cc, Ci, Cm, Cr } = require("chrome");
+const { isCIDRegistered } = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+const { Class } = require("sdk/core/heritage");
+const { Loader } = require("sdk/test/loader");
+
+exports['test Unknown implements nsISupports'] = function(assert) {
+ let actual = xpcom.Unknown();
+ assert.equal(actual.QueryInterface(Ci.nsISupports),
+ actual,
+ 'component implements nsISupports');
+};
+
+exports['test implement xpcom interfaces'] = function(assert) {
+ let WeakReference = Class({
+ extends: xpcom.Unknown,
+ interfaces: [ 'nsIWeakReference' ],
+ QueryReferent: function() {}
+ });
+ let weakReference = WeakReference()
+
+ assert.equal(weakReference.QueryInterface(Ci.nsISupports),
+ weakReference,
+ 'component implements nsISupports');
+ assert.equal(weakReference.QueryInterface(Ci.nsIWeakReference),
+ weakReference,
+ 'component implements specified interface');
+
+ assert.throws(function() {
+ component.QueryInterface(Ci.nsIObserver);
+ }, "component does not implements interface");
+
+ let Observer = Class({
+ extends: WeakReference,
+ interfaces: [ 'nsIObserver', 'nsIRequestObserver' ],
+ observe: function() {},
+ onStartRequest: function() {},
+ onStopRequest: function() {}
+ });
+ let observer = Observer()
+
+ assert.equal(observer.QueryInterface(Ci.nsISupports),
+ observer,
+ 'derived component implements nsISupports');
+ assert.equal(observer.QueryInterface(Ci.nsIWeakReference),
+ observer,
+ 'derived component implements supers interface');
+ assert.equal(observer.QueryInterface(Ci.nsIObserver),
+ observer.QueryInterface(Ci.nsIRequestObserver),
+ 'derived component implements specified interfaces');
+};
+
+exports['test implement factory without contract'] = function(assert) {
+ let actual = xpcom.Factory({
+ get wrappedJSObject() this,
+ });
+
+ assert.ok(isCIDRegistered(actual.id), 'factory is regiseterd');
+ xpcom.unregister(actual);
+ assert.ok(!isCIDRegistered(actual.id), 'factory is unregistered');
+};
+
+exports['test implement xpcom factory'] = function(assert) {
+ let Component = Class({
+ extends: xpcom.Unknown,
+ interfaces: [ 'nsIObserver' ],
+ get wrappedJSObject() this,
+ observe: function() {}
+ });
+
+ let factory = xpcom.Factory({
+ register: false,
+ contract: '@jetpack/test/factory;1',
+ Component: Component
+ });
+
+ assert.ok(!isCIDRegistered(factory.id), 'factory is not registered');
+ xpcom.register(factory);
+ assert.ok(isCIDRegistered(factory.id), 'factory is registered');
+
+ let actual = Cc[factory.contract].createInstance(Ci.nsIObserver);
+
+ assert.ok(actual.wrappedJSObject instanceof Component,
+ "createInstance returnes wrapped factory instances");
+
+ assert.notEqual(Cc[factory.contract].createInstance(Ci.nsIObserver),
+ Cc[factory.contract].createInstance(Ci.nsIObserver),
+ "createInstance returns new instance each time");
+};
+
+exports['test implement xpcom service'] = function(assert) {
+ let actual = xpcom.Service({
+ contract: '@jetpack/test/service;1',
+ register: false,
+ Component: Class({
+ extends: xpcom.Unknown,
+ interfaces: [ 'nsIObserver'],
+ get wrappedJSObject() this,
+ observe: function() {},
+ name: 'my-service'
+ })
+ });
+
+ assert.ok(!isCIDRegistered(actual.id), 'component is not registered');
+ xpcom.register(actual);
+ assert.ok(isCIDRegistered(actual.id), 'service is regiseterd');
+ assert.ok(Cc[actual.contract].getService(Ci.nsIObserver).observe,
+ 'service can be accessed via get service');
+ assert.equal(Cc[actual.contract].getService(Ci.nsIObserver).wrappedJSObject,
+ actual.component,
+ 'wrappedJSObject is an actual component');
+ xpcom.unregister(actual);
+ assert.ok(!isCIDRegistered(actual.id), 'service is unregistered');
+};
+
+
+function testRegister(assert, text) {
+
+ const service = xpcom.Service({
+ description: 'test about:boop page',
+ contract: '@mozilla.org/network/protocol/about;1?what=boop',
+ register: false,
+ Component: Class({
+ extends: xpcom.Unknown,
+ get wrappedJSObject() this,
+ interfaces: [ 'nsIAboutModule' ],
+ newChannel : function(aURI) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ var channel = ios.newChannel(
+ "data:text/plain;charset=utf-8," + text,
+ null,
+ null
+ );
+
+ channel.originalURI = aURI;
+ return channel;
+ },
+ getURIFlags: function(aURI) {
+ return Ci.nsIAboutModule.ALLOW_SCRIPT;
+ }
+ })
+ });
+
+ xpcom.register(service);
+
+ assert.equal(isCIDRegistered(service.id), true);
+
+ var aboutFactory = xpcom.factoryByContract(service.contract);
+ var about = aboutFactory.createInstance(Ci.nsIAboutModule);
+
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ assert.equal(
+ about.getURIFlags(ios.newURI("http://foo.com", null, null)),
+ Ci.nsIAboutModule.ALLOW_SCRIPT
+ );
+
+ var aboutURI = ios.newURI("about:boop", null, null);
+ var channel = ios.newChannelFromURI(aboutURI);
+ var iStream = channel.open();
+ var siStream = Cc['@mozilla.org/scriptableinputstream;1']
+ .createInstance(Ci.nsIScriptableInputStream);
+ siStream.init(iStream);
+ var data = new String();
+ data += siStream.read(-1);
+ siStream.close();
+ iStream.close();
+ assert.equal(data, text);
+
+ xpcom.unregister(service);
+ assert.equal(isCIDRegistered(service.id), false);
+}
+
+exports["test register"] = function(assert) {
+ testRegister(assert, "hai2u");
+};
+
+exports["test re-register"] = function(assert) {
+ testRegister(assert, "hai2u again");
+};
+
+exports["test unload"] = function(assert) {
+ let loader = Loader(module);
+ let sbxpcom = loader.require("sdk/platform/xpcom");
+
+ let auto = sbxpcom.Factory({
+ contract: "@mozilla.org/test/auto-unload;1",
+ description: "test auto",
+ name: "auto"
+ });
+
+ let manual = sbxpcom.Factory({
+ contract: "@mozilla.org/test/manual-unload;1",
+ description: "test manual",
+ register: false,
+ unregister: false,
+ name: "manual"
+ });
+
+ assert.equal(isCIDRegistered(auto.id), true, 'component registered');
+ assert.equal(isCIDRegistered(manual.id), false, 'component not registered');
+
+ sbxpcom.register(manual)
+ assert.equal(isCIDRegistered(manual.id), true,
+ 'component was automatically registered on first instance');
+ loader.unload();
+
+ assert.equal(isCIDRegistered(auto.id), false,
+ 'component was atumatically unregistered on unload');
+ assert.equal(isCIDRegistered(manual.id), true,
+ 'component was not automatically unregistered on unload');
+ sbxpcom.unregister(manual);
+ assert.equal(isCIDRegistered(manual.id), false,
+ 'component was manually unregistered on unload');
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/test-xul-app.js b/tools/addon-sdk-1.12/test/test-xul-app.js
new file mode 100644
index 0000000..c73f316
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/test-xul-app.js
@@ -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/. */
+
+var xulApp = require("sdk/system/xul-app");
+
+exports.testXulApp = function(test) {
+ test.assertEqual(typeof(xulApp.ID), "string",
+ "ID is a string");
+ test.assertEqual(typeof(xulApp.name), "string",
+ "name is a string");
+ test.assertEqual(typeof(xulApp.version), "string",
+ "version is a string");
+ test.assertEqual(typeof(xulApp.platformVersion), "string",
+ "platformVersion is a string");
+
+ test.assertRaises(function() { xulApp.is("blargy"); },
+ "Unkown Mozilla Application: blargy",
+ "is() throws error on bad app name");
+ test.assertRaises(function() { xulApp.isOneOf(["blargy"]); },
+ "Unkown Mozilla Application: blargy",
+ "isOneOf() throws error on bad app name");
+
+ function testSupport(name) {
+ var item = xulApp.is(name);
+ test.assert(item === true || item === false,
+ "is('" + name + "') is true or false.");
+ }
+
+ var apps = ["Firefox", "Mozilla", "Sunbird", "SeaMonkey",
+ "Fennec", "Thunderbird"];
+
+ apps.forEach(function(name) { testSupport(name); });
+
+ test.assert(xulApp.isOneOf(apps) == true ||
+ xulApp.isOneOf(apps) == false,
+ "isOneOf() returns true or false.");
+
+ test.assertEqual(xulApp.versionInRange(xulApp.platformVersion, "1.9", "*"),
+ true, "platformVersion in range [1.9, *)");
+ test.assertEqual(xulApp.versionInRange("3.6.4", "3.6.4", "3.6.*"),
+ true, "3.6.4 in [3.6.4, 3.6.*)");
+ test.assertEqual(xulApp.versionInRange("1.9.3", "1.9.2", "1.9.3"),
+ false, "1.9.3 not in [1.9.2, 1.9.3)");
+};
diff --git a/tools/addon-sdk-1.12/test/traits/assert.js b/tools/addon-sdk-1.12/test/traits/assert.js
new file mode 100644
index 0000000..5117ffb
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/traits/assert.js
@@ -0,0 +1,98 @@
+/* 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 BaseAssert = require("sdk/test/assert").Assert;
+
+/**
+ * Whether or not given property descriptors are equivalent. They are
+ * equivalent either if both are marked as "conflict" or "required" property
+ * or if all the properties of descriptors are equal.
+ * @param {Object} actual
+ * @param {Object} expected
+ */
+function equivalentDescriptors(actual, expected) {
+ return (actual.conflict && expected.conflict) ||
+ (actual.required && expected.required) ||
+ equalDescriptors(actual, expected);
+}
+
+function equalDescriptors(actual, expected) {
+ return actual.get === expected.get &&
+ actual.set === expected.set &&
+ actual.value === expected.value &&
+ !!actual.enumerable === !!expected.enumerable &&
+ !!actual.configurable === !!expected.configurable &&
+ !!actual.writable === !!expected.writable;
+}
+
+/**
+ * Whether or not given `target` array contains all the element
+ * from a given `source` array.
+ */
+function containsSet(source, target) {
+ return source.some(function(element) {
+ return 0 > target.indexOf(element);
+ });
+}
+
+/**
+ * Whether or not given two arrays contain all elements from another.
+ */
+function equivalentSets(source, target) {
+ return containsSet(source, target) && containsSet(target, source);
+}
+
+/**
+ * Finds name of the property from `source` property descriptor map, that
+ * is not equivalent of the name named property in the `target` property
+ * descriptor map. If not found `null` is returned instead.
+ */
+function findNonEquivalentPropertyName(source, target) {
+ var value = null;
+ Object.getOwnPropertyNames(source).some(function(key) {
+ var areEquivalent = false;
+ if (!equivalentDescriptors(source[key], target[key])) {
+ value = key;
+ areEquivalent = true;
+ }
+ return areEquivalent;
+ });
+ return value;
+}
+
+var AssertDescriptor = {
+ equalTraits: {
+ value: function equivalentTraits(actual, expected, message) {
+ var difference;
+ var actualKeys = Object.getOwnPropertyNames(actual);
+ var expectedKeys = Object.getOwnPropertyNames(expected);
+
+ if (equivalentSets(actualKeys, expectedKeys)) {
+ this.fail({
+ operator: "equalTraits",
+ message: "Traits define different properties",
+ actual: actualKeys.sort().join(","),
+ expected: expectedKeys.sort().join(","),
+ });
+ }
+ else if ((difference = findNonEquivalentPropertyName(actual, expected))) {
+ this.fail({
+ operator: "equalTraits",
+ message: "Traits define non-equivalent property `" + difference + "`",
+ actual: actual[difference],
+ expected: expected[difference]
+ });
+ }
+ else {
+ this.pass(message || "Traits are equivalent.");
+ }
+ }
+ }
+};
+
+exports.Assert = function Assert() {
+ return Object.create(BaseAssert.apply(null, arguments), AssertDescriptor);
+};
diff --git a/tools/addon-sdk-1.12/test/traits/descriptor-tests.js b/tools/addon-sdk-1.12/test/traits/descriptor-tests.js
new file mode 100644
index 0000000..e11bc46
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/traits/descriptor-tests.js
@@ -0,0 +1,335 @@
+/* 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 Trait = require("sdk/deprecated/light-traits").Trait;
+var utils = require("./utils");
+var Data = utils.Data;
+var Method = utils.Method;
+var Accessor = utils.Accessor;
+var Required = utils.Required;
+var Conflict = utils.Conflict;
+
+function method() {}
+
+exports.Assert = require("./assert").Assert
+exports["test simple composition"] = function(assert) {
+ var actual = Trait.compose(
+ Trait({ a: 0, b: 1 }),
+ { c: { value: 2 }, d: { value: method, enumerable: true } }
+ );
+
+ var expected = {
+ a: Data(0),
+ b: Data(1),
+ c: Data(2, false, false, false),
+ d: Method(method, true, false, false)
+ };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test composition with conflict"] = function(assert) {
+ var actual = Trait.compose(
+ Trait({ a: 0, b: 1 }),
+ {
+ a: {
+ value: 2,
+ writable: true,
+ configurable: true,
+ enumerable: true
+ },
+ c: {
+ value: method,
+ configurable: true
+ }
+ }
+ );
+
+ var expected = {
+ a: Conflict("a"),
+ b: Data(1),
+ c: Method(method, false, true, false)
+ };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test identical props does not cause conflict"] = function(assert) {
+ var actual = Trait.compose(
+ {
+ a: {
+ value: 0,
+ writable: true,
+ configurable: true,
+ enumerable: true
+ },
+ b: {
+ value: 1
+ }
+ },
+ Trait({
+ a: 0,
+ c: method
+ })
+ );
+
+ var expected = {
+ a: Data(0),
+ b: Data(1, false, false, false),
+ c: Method(method)
+ }
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test composition with identical required props"] = function(assert) {
+ var actual = Trait.compose(
+ Trait({ a: Trait.required, b: 1 }),
+ { a: { required: true }, c: { value: method } }
+ );
+
+ var expected = {
+ a: Required(),
+ b: Data(1),
+ c: Method(method, false, false, false)
+ };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test composition satisfying a required prop"] = function(assert) {
+ var actual = Trait.compose(
+ Trait({ a: Trait.required, b: 1 }),
+ { a: { value: method, enumerable: true } }
+ );
+
+ var expected = {
+ a: Method(method, true, false, false),
+ b: Data(1)
+ };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test compose is neutral wrt conflicts"] = function(assert) {
+ var actual = Trait.compose(
+ Trait({ a: { value: 1 } }, Trait({ a: 2 })),
+ { b: { value: 0, writable: true, configurable: true, enumerable: false } }
+ );
+
+ var expected = { a: Conflict("a"), b: Data(0, false) };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test conflicting prop overrides Trait.required"] = function(assert) {
+ var actual = Trait.compose(
+ Trait.compose(
+ Trait({ a: 1 }),
+ { a: { value: 2 } }
+ ),
+ { a: { value: Trait.required } }
+ );
+
+ var expected = { a: Conflict("a") };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test compose is commutative"] = function(assert) {
+ var actual = Trait.compose(
+ Trait({ a: 0, b: 1 }),
+ { c: { value: 2 }, d: { value: method } }
+ );
+
+ var expected = Trait.compose(
+ { c: { value: 2 }, d: { value: method } },
+ Trait({ a: 0, b: 1 })
+ );
+
+ assert.equalTraits(actual, expected);
+}
+
+exports["test compose is commutative, also for required/conflicting props"] = function(assert) {
+ var actual = Trait.compose(
+ {
+ a: { value: 0 },
+ b: { value: 1 },
+ c: { value: 3 },
+ e: { value: Trait.required }
+ },
+ {
+ c: { value: 2 },
+ d: { get: method }
+ }
+ );
+
+ var expected = Trait.compose(
+ Trait({ c: 3 }),
+ {
+ c: { value: 2 },
+ d: { get: method },
+ a: { value: 0 },
+ b: { value: 1 },
+ e: { value: Trait.required },
+ }
+ );
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test compose is associative"] = function(assert) {
+ var actual = Trait.compose(
+ {
+ a: { value: 0 },
+ b: { value: 1 },
+ c: { value: 3 },
+ d: { value: Trait.required }
+ },
+ Trait.compose(
+ { c: { value: 3 }, d: { value: Trait.required } },
+ { c: { value: 2 }, d: { value: method }, e: { value: "foo" } }
+ )
+ );
+
+ var expected = Trait.compose(
+ Trait.compose(
+ {
+ a: { value: 0 },
+ b: { value: 1 },
+ c: { value: 3 },
+ d: { value: Trait.required }
+ },
+ {
+ c: { value: 3 },
+ d: { value: Trait.required }
+ }
+ ),
+ {
+ c: { value: 2 },
+ d: { value: method },
+ e: { value: "foo" }
+ }
+ );
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test diamond import of same prop do not conflict"] = function(assert) {
+ var actual = Trait.compose(
+ Trait.compose(
+ { b: { value: 2 } },
+ { a: { value: 1, enumerable: true, configurable: true, writable: true } }
+ ),
+ Trait.compose(
+ { c: { value: 3 } },
+ Trait({ a: 1 })
+ ),
+ Trait({ d: 4 })
+ );
+
+ var expected = {
+ a: Data(1),
+ b: Data(2, false, false, false),
+ c: Data(3, false, false, false),
+ d: Data(4)
+ };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test create simple"] = function(assert) {
+ var o1 = Trait.compose(
+ Trait({ a: 1 }),
+ {
+ b: {
+ value: function() {
+ return this.a;
+ }
+ }
+ }
+ ).create(Object.prototype);
+
+ assert.equal(Object.getPrototypeOf(o1), Object.prototype, "o1 prototype");
+ assert.equal(1, o1.a, "o1.a");
+ assert.equal(1, o1.b(), "o1.b()");
+ assert.equal(Object.keys(o1).length, 1, "Object.keys(o1).length === 2");
+};
+
+exports["test create with Array.prototype"] = function(assert) {
+ var o2 = Trait.compose({}, {}).create(Array.prototype);
+ assert.equal(Object.getPrototypeOf(o2), Array.prototype, "o2 prototype");
+};
+
+exports["test exception for incomplete required properties"] = function(assert) {
+ assert.throws(function() {
+ Trait({ foo: Trait.required }).create(Object.prototype)
+ }, /Missing required property: `foo`/, "required prop error");
+}
+
+exports["test exception for unresolved conflicts"] = function(assert) {
+ assert.throws(function() {
+ Trait(Trait({ a: 0 }), Trait({ a: 1 })).create({})
+ }, /Remaining conflicting property: `a`/, "conflicting prop error");
+}
+
+exports["test conflicting properties are present"] = function(assert) {
+ var o5 = Object.create(Object.prototype, Trait.compose(
+ { a: { value: 0 } },
+ { a: { value: 1 } }
+ ));
+
+ assert.ok("a" in o5, "conflicting property present");
+ assert.throws(function() {
+ o5.a
+ }, /Remaining conflicting property: `a`/, "conflicting prop access error");
+};
+
+exports["test diamond with conflicts"] = function(assert) {
+ function makeT1(x) {
+ return {
+ m: {
+ value: function() {
+ return x
+ }
+ }
+ };
+ };
+
+ function makeT2(x) {
+ return Trait.compose(
+ Trait({ t2: "foo" }),
+ makeT1(x)
+ );
+ };
+
+ function makeT3(x) {
+ return Trait.compose(
+ {
+ t3: { value: "bar" }
+ },
+ makeT1(x)
+ );
+ };
+
+ var T4 = Trait.compose(makeT2(5), makeT3(5));
+
+ assert.throws(function() {
+ T4.create(Object.prototype);
+ }, /Remaining conflicting property: `m`/, "diamond prop conflict");
+};
+
+exports["test providing requirements through proto"] = function(assert) {
+ var t = Trait.compose(
+ {},
+ { required: { required: true } }
+ ).create({ required: "test" });
+
+ assert.equal(t.required, "test", "property from proto is inherited");
+};
+
+if (module == require.main)
+ require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/traits/inheritance-tests.js b/tools/addon-sdk-1.12/test/traits/inheritance-tests.js
new file mode 100644
index 0000000..9c3f8a7
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/traits/inheritance-tests.js
@@ -0,0 +1,104 @@
+/* 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 Trait = require("sdk/deprecated/light-traits").Trait;
+
+exports["test custom constructor and inherited toString"] = function(assert) {
+ function Type() {
+ return Object.create(Type.prototype);
+ }
+ Type.prototype = Trait({
+ method: function method() {
+ return 2;
+ }
+ }).create(Object.freeze(Type.prototype));
+
+ var fixture = Type();
+
+ assert.equal(fixture.constructor, Type, "must override constructor");
+ assert.equal(fixture.toString(), "[object Type]", "must inherit toString");
+};
+
+exports["test custom toString and inherited constructor"] = function(assert) {
+ function Type() {
+ return Object.create(Type.prototype);
+ }
+ Type.prototype = Trait({
+ toString: function toString() {
+ return "<toString>";
+ }
+ }).create();
+
+ var fixture = Type();
+
+ assert.equal(fixture.constructor, Trait, "must inherit constructor Trait");
+ assert.equal(fixture.toString(), "<toString>", "Must override toString");
+};
+
+exports["test custom toString and constructor"] = function(assert) {
+ function Type() {
+ return TypeTrait.create(Type.prototype);
+ }
+ Object.freeze(Type.prototype);
+ var TypeTrait = Trait({
+ toString: function toString() {
+ return "<toString>";
+ }
+ });
+
+ var fixture = Type();
+
+ assert.equal(fixture.constructor, Type, "constructor is provided to create");
+ assert.equal(fixture.toString(), "<toString>", "toString was overridden");
+};
+
+exports["test resolve constructor"] = function (assert) {
+ function Type() {}
+ var T1 = Trait({ constructor: Type }).resolve({ constructor: '_foo' });
+ var f1 = T1.create();
+
+ assert.equal(f1._foo, Type, "constructor was resolved");
+ assert.equal(f1.constructor, Trait, "constructor of prototype is inherited");
+ assert.equal(f1.toString(), "[object Trait]", "toString is inherited");
+};
+
+exports["test compose read-only"] = function (assert) {
+ function Type() {}
+ Type.prototype = Trait.compose(Trait({}), {
+ constructor: { value: Type },
+ a: { value: "b", enumerable: true }
+ }).resolve({ a: "b" }).create({ a: "a" });
+
+ var f1 = new Type();
+
+ assert.equal(Object.getPrototypeOf(f1), Type.prototype, "inherits correctly");
+ assert.equal(f1.constructor, Type, "constructor was overridden");
+ assert.equal(f1.toString(), "[object Type]", "toString was inherited");
+ assert.equal(f1.a, "a", "property a was resolved");
+ assert.equal(f1.b, "b", "property a was renamed to b");
+ assert.ok(!Object.getOwnPropertyDescriptor(Type.prototype, "a"),
+ "a is not on the prototype of the instance");
+
+ var proto = Object.getPrototypeOf(Type.prototype);
+ var dc = Object.getOwnPropertyDescriptor(Type.prototype, "constructor");
+ var db = Object.getOwnPropertyDescriptor(Type.prototype, "b");
+ var da = Object.getOwnPropertyDescriptor(proto, "a");
+
+ assert.ok(!dc.writable, "constructor is not writable");
+ assert.ok(!dc.enumerable, "constructor is not enumerable");
+ assert.ok(dc.configurable, "constructor inherits configurability");
+
+ assert.ok(!db.writable, "a -> b is not writable");
+ assert.ok(db.enumerable, "a -> b is enumerable");
+ assert.ok(!db.configurable, "a -> b is not configurable");
+
+ assert.ok(da.writable, "a is writable");
+ assert.ok(da.enumerable, "a is enumerable");
+ assert.ok(da.configurable, "a is configurable");
+};
+
+if (require.main == module)
+ require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/traits/object-tests.js b/tools/addon-sdk-1.12/test/traits/object-tests.js
new file mode 100644
index 0000000..ea53d2d
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/traits/object-tests.js
@@ -0,0 +1,321 @@
+/* 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 Trait = require("sdk/deprecated/light-traits").Trait;
+var utils = require("./utils");
+var Data = utils.Data;
+var Method = utils.Method;
+var Accessor = utils.Accessor;
+var Required = utils.Required;
+var Conflict = utils.Conflict;
+
+function method() {}
+
+exports.Assert = require("./assert").Assert;
+
+exports["test empty trait"] = function (assert) {
+ assert.equalTraits(Trait({}), {});
+};
+
+exports["test simple trait"] = function (assert) {
+ var expected = {
+ a: Data(0, true, true, true),
+ b: Method(method, true, true, true)
+ };
+
+ assert.equalTraits(Trait({ a: 0, b: method }), expected);
+};
+
+exports["test simple trait with Trait.required property"] = function (assert) {
+ var actual = Trait({ a: Trait.required, b: 1 });
+ var expected = { a: Required("a"), b: Data(1) };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test ordering of trait properties is irrelevant"] = function (assert) {
+ var actual = Trait({ a: 0, b: 1, c: Trait.required });
+ var expected = Trait({ b: 1, c: Trait.required, a: 0 });
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test trait with accessor property"] = function (assert) {
+ var record = { get a() {}, set a(v) {} };
+ var get = Object.getOwnPropertyDescriptor(record, "a").get;
+ var set = Object.getOwnPropertyDescriptor(record, "a").set;
+
+ assert.equalTraits(Trait(record), { a: Accessor(get, set) });
+};
+
+exports["test simple composition"] = function (assert) {
+ var actual = Trait.compose(Trait({ a: 0, b: 1 }), Trait({ c: 2, d: method }));
+ var expected = { a: Data(0), b: Data(1), c: Data(2), d: Method(method) };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test composition with conflict"] = function (assert) {
+ var actual = Trait.compose(Trait({ a: 0, b: 1 }), Trait({ a: 2, c: method }));
+ var expected = { a: Conflict("a"), b: Data(1), c: Method(method) };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test composition of identical props does not cause conflict"] = function (assert) {
+ var actual = Trait.compose(Trait({ a: 0, b: 1 }), Trait({ a: 0, c: method }));
+
+ assert.equalTraits(actual, { a: Data(0), b: Data(1), c: Method(method) });
+};
+
+exports["test composition with identical Trait.required props"] = function (assert) {
+ var actual = Trait.compose(Trait({ a: Trait.required, b: 1 }),
+ Trait({ a: Trait.required, c: method }));
+
+ assert.equalTraits(actual, { a: Required(), b: Data(1), c: Method(method) });
+};
+
+exports["test composition satisfying a Trait.required prop"] = function (assert) {
+ var actual = Trait.compose(Trait({ a: Trait.required, b: 1 }),
+ Trait({ a: method }));
+
+ assert.equalTraits(actual, { a: Method(method), b: Data(1) });
+};
+
+exports["test compose is neutral wrt conflicts"] = function (assert) {
+ var actual = Trait.compose(Trait.compose(Trait({ a: 1 }), Trait({ a: 2 })),
+ Trait({ b: 0 }));
+
+ assert.equalTraits(actual, { a: Conflict("a"), b: Data(0) });
+};
+
+exports["test conflicting prop overrides Trait.required prop"] = function (assert) {
+ var actual = Trait.compose(Trait.compose(Trait({ a: 1 }),
+ Trait({ a: 2 })),
+ Trait({ a: Trait.required }));
+
+ assert.equalTraits(actual, { a: Conflict("a") });
+};
+
+exports["test compose is commutative"] = function (assert) {
+ var actual = Trait.compose(Trait({ a: 0, b: 1 }), Trait({ c: 2, d: method }));
+ var expected = Trait.compose(Trait({ c: 2, d: method }),
+ Trait({ a: 0, b: 1 }));
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test compose is commutative, also for Trait.required/conflicting props"] = function (assert) {
+ var actual = Trait.compose(Trait({ a: 0, b: 1, c: 3, e: Trait.required }),
+ Trait({ c: 2, d: method }));
+
+ var expected = Trait.compose(Trait({ c: 2, d: method }),
+ Trait({ a: 0, b: 1, c: 3, e: Trait.required }));
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test compose is associative"] = function (assert) {
+ var actual = Trait.compose(Trait({ a: 0, b: 1, c: 3, d: Trait.required }),
+ Trait.compose(Trait({ c: 3, d: Trait.required }),
+ Trait({ c: 2, d: method,
+ e: "foo" })));
+
+ var expected = Trait.compose(
+ Trait.compose(Trait({ a: 0, b: 1, c: 3, d: Trait.required }),
+ Trait({ c: 3, d: Trait.required })),
+ Trait({ c: 2, d: method, e: "foo" }));
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test diamond import of same prop does not generate conflict"] = function (assert) {
+ var actual = Trait.compose(Trait.compose(Trait({ b: 2 }), Trait({ a: 1 })),
+ Trait.compose(Trait({ c: 3 }), Trait({ a: 1 })),
+ Trait({ d: 4 }));
+ var expected = { a: Data(1), b: Data(2), c: Data(3), d: Data(4) };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test resolve with empty resolutions has no effect"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: Trait.required, c: method }).resolve({}),
+ { a: Data(1), b: Required(), c: Method(method) });
+};
+
+exports["test resolve: renaming"] = function (assert) {
+ var actual = Trait({ a: 1, b: Trait.required, c: method });
+
+ assert.equalTraits(actual.resolve({ a: "A", c: "C" }),
+ { A: Data(1), b: Required(), C: Method(method),
+ a: Required(), c: Required() });
+};
+
+exports["test resolve: renaming to conflicting name causes conflict, order 1"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: "b" }),
+ { b: Conflict("b"), a: Required() });
+};
+
+exports["test resolve: renaming to conflicting name causes conflict, order 2"] = function (assert) {
+ assert.equalTraits(Trait({ b: 2, a: 1 }).resolve({ a: "b" }),
+ { b: Conflict("b"), a: Required() });
+};
+
+exports["test resolve: simple exclusion"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: undefined }),
+ { a: Required(), b: Data(2) });
+};
+
+exports["test resolve: exclusion to empty trait"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: null, b: undefined }),
+ { a: Required(), b: Required() });
+};
+
+exports["test resolve: exclusion and renaming of disjoint props"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: undefined, b: "c" }),
+ { a: Required(), c: Data(2), b: Required() });
+};
+
+exports["test resolve: exclusion and renaming of overlapping props"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: undefined, b: "a" }),
+ { a: Data(2), b: Required() });
+};
+
+exports["test resolve: renaming to a common alias causes conflict"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: "c", b: "c" }),
+ { c: Conflict("c"), a: Required(), b: Required() });
+};
+
+exports["test resolve: renaming overrides Trait.required target"] = function (assert) {
+ assert.equalTraits(Trait({ a: Trait.required, b: 2 }).resolve({ b: "a" }),
+ { a: Data(2), b: Required() });
+};
+
+exports["test resolve: renaming Trait.required properties has no effect"] = function (assert) {
+ assert.equalTraits(Trait({ a: 2, b: Trait.required }).resolve({ b: "a" }),
+ { a: Data(2), b: Required() });
+};
+
+exports["test resolve: renaming of non-existent props has no effect"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: "c", d: "c" }),
+ { c: Data(1), b: Data(2), a: Required() });
+};
+
+exports["test resolve: exclusion of non-existent props has no effect"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1 }).resolve({ b: undefined }), { a: Data(1) });
+};
+
+exports["test resolve is neutral w.r.t. Trait.required properties"] = function (assert) {
+ var actual = Trait({ a: Trait.required, b: Trait.required, c: "foo", d: 1 });
+ var expected = { a: Required(), b: Required(), c: Data("foo"), d: Data(1) };
+ assert.equalTraits(actual.resolve({ a: "c", b: undefined }), expected);
+};
+
+exports["test resolve supports swapping of property names, ordering 1"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: "b", b: "a" }),
+ { a: Data(2), b: Data(1) });
+};
+
+exports["test resolve supports swapping of property names, ordering 2"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ b: "a", a: "b" }),
+ { a: Data(2), b: Data(1) });
+};
+
+exports["test resolve supports swapping of property names, ordering 3"] = function (assert) {
+ assert.equalTraits(Trait({ b: 2, a: 1 }).resolve({ b: "a", a: "b" }),
+ { a: Data(2), b: Data(1) });
+};
+
+exports["test resolve supports swapping of property names, ordering 4"] = function (assert) {
+ assert.equalTraits(Trait({ b: 2, a: 1 }).resolve({ a: "b", b: "a" }),
+ { a: Data(2), b: Data(1) });
+};
+
+exports["test create simple"] = function (assert) {
+ var o1 = Trait({
+ a: 1,
+ b: function () {
+ return this.a;
+ }
+ }).create(Object.prototype);
+
+ assert.equal(Object.getPrototypeOf(o1), Object.prototype, "o1 prototype");
+ assert.equal(1, o1.a, "o1.a");
+ assert.equal(1, o1.b(), "o1.b()");
+ assert.equal(Object.keys(o1).length, 2, "Object.keys(o1).length === 2");
+};
+
+exports["test create with Array.prototype"] = function (assert) {
+ var o2 = Trait({}).create(Array.prototype);
+ assert.equal(Object.getPrototypeOf(o2), Array.prototype, "o2 prototype");
+};
+
+exports["test exception for incomplete required properties"] = function (assert) {
+ assert.throws(function () {
+ Trait({ foo: Trait.required }).create(Object.prototype);
+ }, /Missing required property: `foo`/, "required prop error");
+};
+
+exports["test exception for unresolved conflicts"] = function (assert) {
+ assert.throws(function () {
+ Trait.compose(Trait({ a: 0 }), Trait({ a: 1 })).create({});
+ }, /Remaining conflicting property: `a`/, "conflicting prop error");
+};
+
+exports["test verify that required properties are present but undefined"] = function (assert) {
+ var o4 = Object.create(Object.prototype, Trait({ foo: Trait.required }));
+
+ assert.ok("foo" in o4, "required property present");
+ assert.throws(function () {
+ o4.foo;
+ }, /Missing required property: `foo`/, "required prop error");
+};
+
+exports["test verify that conflicting properties are present"] = function (assert) {
+ var o5 = Object.create(Object.prototype, Trait.compose(Trait({ a: 0 }),
+ Trait({ a: 1 })));
+
+ assert.ok("a" in o5, "conflicting property present");
+ assert.throws(function () {
+ o5.a;
+ }, /Remaining conflicting property: `a`/, "conflicting prop access error");
+};
+
+exports["test diamond with conflicts"] = function (assert) {
+ function makeT1(x) {
+ return Trait({
+ m: function () {
+ return x
+ }
+ })
+ };
+
+ function makeT2(x) {
+ return Trait.compose(Trait({
+ t2: "foo"
+ }), makeT1(x));
+ };
+
+ function makeT3(x) {
+ return Trait.compose(Trait({
+ t3: "bar"
+ }), makeT1(x));
+ };
+
+ var T4 = Trait.compose(makeT2(5), makeT3(5));
+
+ assert.throws(function () {
+ T4.create(Object.prototype);
+ }, /Remaining conflicting property: `m`/, "diamond prop conflict");
+};
+
+exports["test providing requirements through proto"] = function (assert) {
+ var t = Trait({ required: Trait.required }).create({ required: "test" });
+ assert.equal(t.required, "test", "property from proto is inherited");
+};
+
+if (module == require.main)
+ require("test").run(exports);
diff --git a/tools/addon-sdk-1.12/test/traits/utils.js b/tools/addon-sdk-1.12/test/traits/utils.js
new file mode 100644
index 0000000..8426af7
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/traits/utils.js
@@ -0,0 +1,56 @@
+/* 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 ERR_CONFLICT = "Remaining conflicting property: ";
+var ERR_REQUIRED = "Missing required property: ";
+
+exports.Data = function Data(value, enumerable, configurable, writable) {
+ return ({
+ value: value,
+ enumerable: enumerable !== false,
+ configurable: configurable !== false,
+ writable: writable !== false
+ });
+};
+
+exports.Method = function Method(method, enumerable, configurable, writable) {
+ return ({
+ value: method,
+ enumerable: enumerable !== false,
+ configurable: configurable !== false,
+ writable: writable !== false
+ });
+};
+
+exports.Accessor = function Accessor(get, set, enumerable, configurable) {
+ return ({
+ get: get,
+ set: set,
+ enumerable: enumerable !== false,
+ configurable: configurable !== false
+ });
+};
+
+exports.Required = function Required(name) {
+ function required() { throw new Error(ERR_REQUIRED + name) }
+
+ return ({
+ get: required,
+ set: required,
+ required: true
+ });
+};
+
+exports.Conflict = function Conflict(name) {
+ function conflict() { throw new Error(ERR_CONFLICT + name) }
+
+ return ({
+ get: conflict,
+ set: conflict,
+ conflict: true
+ });
+};
+
diff --git a/tools/addon-sdk-1.12/test/windows/test-fennec-windows.js b/tools/addon-sdk-1.12/test/windows/test-fennec-windows.js
new file mode 100644
index 0000000..892d301
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/windows/test-fennec-windows.js
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+const { Cc, Ci } = require('chrome');
+const { setTimeout } = require('sdk/timers');
+const { Loader } = require('sdk/test/loader');
+const WM = Cc['@mozilla.org/appshell/window-mediator;1'].
+ getService(Ci.nsIWindowMediator);
+const { browserWindows } = require('sdk/windows');
+
+const ERR_MSG = 'This method is not yet supported by Fennec, consider using require("tabs") instead';
+
+// TEST: browserWindows.length for Fennec
+exports.testBrowserWindowsLength = function(test) {
+ test.assertEqual(browserWindows.length, 1, "Only one window open");
+};
+
+// TEST: open & close window
+exports.testOpenWindow = function(test) {
+ let tabCount = browserWindows.activeWindow.tabs.length;
+ let url = "data:text/html;charset=utf-8,<title>windows%20API%20test</title>";
+
+ try {
+ browserWindows.open({url: url});
+ test.fail('Error was not thrown');
+ }
+ catch(e) {
+ test.assertEqual(e.message, ERR_MSG, 'Error is thrown on windows.open');
+ test.assertEqual(browserWindows.length, 1, "Only one window open");
+ }
+};
+
+exports.testCloseWindow = function(test) {
+ let window = browserWindows.activeWindow;
+
+ try {
+ window.close();
+ test.fail('Error was not thrown');
+ }
+ catch(e) {
+ test.assertEqual(e.message, ERR_MSG, 'Error is thrown on windows.close');
+ test.assertEqual(browserWindows.length, 1, "Only one window open");
+ }
+};
diff --git a/tools/addon-sdk-1.12/test/windows/test-firefox-windows.js b/tools/addon-sdk-1.12/test/windows/test-firefox-windows.js
new file mode 100644
index 0000000..a0cd4f9
--- /dev/null
+++ b/tools/addon-sdk-1.12/test/windows/test-firefox-windows.js
@@ -0,0 +1,369 @@
+/* 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 { setTimeout } = require('sdk/timers');
+const { Loader } = require('sdk/test/loader');
+const wm = Cc['@mozilla.org/appshell/window-mediator;1'].
+ getService(Ci.nsIWindowMediator);
+let browserWindows;
+
+function getTestRunnerWindow() wm.getMostRecentWindow("test:runner");
+
+// TEST: open & close window
+exports.testOpenAndCloseWindow = function(test) {
+ test.waitUntilDone();
+
+ test.assertEqual(browserWindows.length, 1, "Only one window open");
+
+ browserWindows.open({
+ url: "data:text/html;charset=utf-8,<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("sdk/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;charset=utf-8,foo",
+ onOpen: function(window) {
+ setTimeout(function () {
+ test.assert(!called,
+ "Unloaded windows instance is destroyed and inactive");
+ window.close(function () {
+ test.done();
+ });
+ });
+ }
+ });
+};
+
+exports.testWindowTabsObject = function(test) {
+ test.waitUntilDone();
+
+ browserWindows.open({
+ url: "data:text/html;charset=utf-8,<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;charset=utf-8,<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.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;charset=utf-8,foo",
+ onOpen: function(window) {
+ window.close(verify);
+ }
+ });
+};
+
+exports.testActiveWindow = function(test) {
+ const xulApp = require("sdk/system/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;charset=utf-8,<title>window 2</title>",
+ onOpen: function(window) {
+ window2 = window;
+ rawWindow2 = wm.getMostRecentWindow("navigator:browser");
+
+ windows.open({
+ url: "data:text/html;charset=utf-8,<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();
+ });
+ });
+ }
+};
+
+exports.testTrackWindows = function(test) {
+ test.waitUntilDone();
+
+ let windows = [];
+ let actions = [];
+
+ let expects = [
+ "activate 0", "global activate 0", "deactivate 0", "global deactivate 0",
+ "activate 1", "global activate 1", "deactivate 1", "global deactivate 1",
+ "activate 2", "global activate 2"
+ ];
+
+ function shutdown(window) {
+ if (this.length === 1) {
+ test.assertEqual(actions.join(), expects.join(),
+ "correct activate and deactivate sequence")
+
+ test.done();
+ }
+ }
+
+ function openWindow() {
+ windows.push(browserWindows.open({
+ url: "data:text/html;charset=utf-8,<i>Hi</i>",
+
+ onActivate: function(window) {
+ let index = windows.indexOf(window);
+
+ actions.push("activate " + index);
+
+ if (windows.length < 3)
+ openWindow()
+ else
+ for each (let win in windows)
+ win.close(shutdown)
+ },
+
+ onDeactivate: function(window) {
+ let index = windows.indexOf(window);
+
+ actions.push("deactivate " + index)
+ }
+ }));
+ }
+
+ browserWindows.on("activate", function (window) {
+ let index = windows.indexOf(window);
+
+ actions.push("global activate " + index)
+ })
+
+ browserWindows.on("deactivate", function (window) {
+ let index = windows.indexOf(window);
+
+ actions.push("global deactivate " + index)
+ })
+
+ openWindow();
+}
+
+// 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("sdk/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;
+
+ module.exports = {
+ testAppNotSupported: function (test) {
+ test.pass("the windows module does not support this application.");
+ }
+ }
+}