Backout 2815bfd2ccb5 (bug 1114752) for apparently breaking b2g builds and tests on a CLOSED TREE

This commit is contained in:
Wes Kocher 2015-01-11 22:02:02 -08:00
parent 2c9a274487
commit 2e012fb283
294 changed files with 3516 additions and 15748 deletions

View File

@ -9,6 +9,8 @@
# 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/.
HAS_MISC_RULE = True
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
JETPACK_PACKAGE_MANIFESTS += ['source/test/jetpack-package.ini']
JETPACK_ADDON_MANIFESTS += ['source/test/addons/jetpack-addon.ini']
@ -18,7 +20,8 @@ EXTRA_JS_MODULES.sdk += [
]
EXTRA_JS_MODULES.sdk.system += [
'source/modules/system/Startup.js'
'source/modules/system/Startup.js',
'source/modules/system/XulApp.js',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk":
@ -36,7 +39,6 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk":
'source/lib/sdk/deprecated/list.js',
'source/lib/sdk/deprecated/memory.js',
'source/lib/sdk/deprecated/symbiont.js',
'source/lib/sdk/deprecated/sync-worker.js',
'source/lib/sdk/deprecated/traits-worker.js',
'source/lib/sdk/deprecated/traits.js',
'source/lib/sdk/deprecated/unit-test-finder.js',
@ -52,6 +54,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk":
EXTRA_JS_MODULES.commonjs.sdk.panel += [
'source/lib/sdk/panel/events.js',
'source/lib/sdk/panel/utils.js',
'source/lib/sdk/panel/window.js',
]
EXTRA_JS_MODULES.commonjs.sdk.places += [
@ -96,7 +99,6 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk":
]
EXTRA_JS_MODULES.commonjs.sdk.ui += [
'source/lib/sdk/ui/component.js',
'source/lib/sdk/ui/frame.js',
'source/lib/sdk/ui/id.js',
'source/lib/sdk/ui/sidebar.js',
@ -131,13 +133,13 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk":
'source/lib/sdk/windows/dom.js',
'source/lib/sdk/windows/fennec.js',
'source/lib/sdk/windows/firefox.js',
'source/lib/sdk/windows/loader.js',
'source/lib/sdk/windows/observer.js',
'source/lib/sdk/windows/tabs-fennec.js',
'source/lib/sdk/windows/tabs-firefox.js',
]
EXTRA_JS_MODULES.commonjs += [
'source/lib/index.js',
'source/lib/test.js',
]
@ -171,13 +173,10 @@ EXTRA_JS_MODULES.commonjs.diffpatcher.test += [
]
EXTRA_JS_MODULES.commonjs.framescript += [
'source/lib/framescript/context-menu.js',
'source/lib/framescript/contextmenu-events.js',
'source/lib/framescript/FrameScriptManager.jsm',
'source/lib/framescript/LoaderHelper.jsm',
'source/lib/framescript/manager.js',
'source/lib/framescript/tab-events.js',
'source/lib/framescript/util.js',
]
EXTRA_JS_MODULES.commonjs.method += [
@ -192,7 +191,6 @@ EXTRA_JS_MODULES.commonjs.sdk += [
'source/lib/sdk/base64.js',
'source/lib/sdk/clipboard.js',
'source/lib/sdk/context-menu.js',
'source/lib/sdk/context-menu@2.js',
'source/lib/sdk/hotkeys.js',
'source/lib/sdk/indexed-db.js',
'source/lib/sdk/l10n.js',
@ -220,7 +218,6 @@ EXTRA_JS_MODULES.commonjs.sdk += [
]
EXTRA_JS_MODULES.commonjs.sdk.addon += [
'source/lib/sdk/addon/bootstrap.js',
'source/lib/sdk/addon/events.js',
'source/lib/sdk/addon/host.js',
'source/lib/sdk/addon/installer.js',
@ -249,15 +246,10 @@ EXTRA_JS_MODULES.commonjs.sdk.content += [
'source/lib/sdk/content/thumbnail.js',
'source/lib/sdk/content/utils.js',
'source/lib/sdk/content/worker-child.js',
'source/lib/sdk/content/worker-parent.js',
'source/lib/sdk/content/worker.js',
]
EXTRA_JS_MODULES.commonjs.sdk['context-menu'] += [
'source/lib/sdk/context-menu/context.js',
'source/lib/sdk/context-menu/core.js',
'source/lib/sdk/context-menu/readers.js',
]
EXTRA_JS_MODULES.commonjs.sdk.core += [
'source/lib/sdk/core/disposable.js',
'source/lib/sdk/core/heritage.js',
@ -403,7 +395,6 @@ EXTRA_JS_MODULES.commonjs.sdk.system += [
'source/lib/sdk/system/runtime.js',
'source/lib/sdk/system/unload.js',
'source/lib/sdk/system/xul-app.js',
'source/lib/sdk/system/xul-app.jsm',
]
EXTRA_JS_MODULES.commonjs.sdk.system.child_process += [
@ -435,17 +426,12 @@ EXTRA_JS_MODULES.commonjs.sdk.ui.toolbar += [
'source/lib/sdk/ui/toolbar/view.js',
]
EXTRA_JS_MODULES.commonjs.sdk.uri += [
'source/lib/sdk/uri/resource.js',
]
EXTRA_JS_MODULES.commonjs.sdk.url += [
'source/lib/sdk/url/utils.js',
]
EXTRA_JS_MODULES.commonjs.sdk.util += [
'source/lib/sdk/util/array.js',
'source/lib/sdk/util/bond.js',
'source/lib/sdk/util/collection.js',
'source/lib/sdk/util/contract.js',
'source/lib/sdk/util/deprecate.js',
@ -453,6 +439,7 @@ EXTRA_JS_MODULES.commonjs.sdk.util += [
'source/lib/sdk/util/list.js',
'source/lib/sdk/util/match-pattern.js',
'source/lib/sdk/util/object.js',
'source/lib/sdk/util/registry.js',
'source/lib/sdk/util/rules.js',
'source/lib/sdk/util/sequence.js',
'source/lib/sdk/util/uuid.js',

View File

@ -13,5 +13,6 @@ EXTRA_JS_MODULES.sdk += [
]
EXTRA_JS_MODULES.sdk.system += [
'source/modules/system/Startup.js',
'source/modules/system/XulApp.js',
]

View File

@ -8,7 +8,6 @@ doc/index.html
doc/modules/
doc/status.md5
packages/*
node_modules
# Python
*.pyc
@ -18,3 +17,4 @@ node_modules
# Windows
*Thumbs.db

View File

@ -5,7 +5,6 @@ testdocs.tgz
jetpack-sdk-docs.tgz
.test_tmp
jetpack-sdk-docs
node_modules
# These should really be in a global .hgignore, but such a thing
# seems ridiculously confusing to set up, so we'll include some

View File

@ -1,17 +0,0 @@
local.json
mapping.json
CONTRIBUTING.md
@addon-sdk.xpi
.*
app-extension/
bin/
modules/
node_modules/
examples/
# Python
python-lib/
*.pyc
# Windows
*Thumbs.db

View File

@ -1,23 +0,0 @@
sudo: false
language: node_js
node_js:
- "0.10"
notifications:
irc: "irc.mozilla.org#jetpack"
before_install:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
- "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 -extension RANDR"
before_script:
- npm install mozilla-download -g
- npm install jpm -g
- cd ..
- mozilla-download --branch nightly -c prerelease --host ftp.mozilla.org firefox
- export JPM_FIREFOX_BINARY=$TRAVIS_BUILD_DIR/../firefox/firefox
- cd $TRAVIS_BUILD_DIR
script:
- npm test

41
addon-sdk/source/README Normal file
View File

@ -0,0 +1,41 @@
Add-on SDK README
==================
Before proceeding, please make sure you've installed Python 2.5,
2.6, or 2.7 (if it's not already on your system):
http://python.org/download/
Note that Python 3 is not supported.
For Windows users, MozillaBuild (https://wiki.mozilla.org/MozillaBuild)
will install the correct version of Python and the MSYS package, which
will make it easier to work with the SDK.
To get started, first enter the same directory that this README file
is in (the SDK's root directory) using a shell program. On Unix systems
or on Windows with MSYS, you can execute the following command:
source bin/activate
Windows users using cmd.exe should instead run:
bin\activate.bat
Then go to https://developer.mozilla.org/en-US/Add-ons/SDK/
to browse the SDK documentation.
If you get an error when running cfx or have any other problems getting
started, see the "Troubleshooting" guide at:
https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Troubleshooting
Bugs
-------
* file a bug: https://bugzilla.mozilla.org/enter_bug.cgi?product=Add-on%20SDK
Style Guidelines
--------------------
* https://github.com/mozilla/addon-sdk/wiki/Coding-style-guide

View File

@ -1,31 +0,0 @@
# Mozilla Add-on SDK [![Build Status](https://travis-ci.org/mozilla/addon-sdk.png)](https://travis-ci.org/mozilla/addon-sdk)
Using the Add-on SDK you can create Firefox add-ons using standard Web technologies: JavaScript, HTML, and CSS. The SDK includes JavaScript APIs which you can use to create add-ons, and tools for creating, running, testing, and packaging add-ons.
If you find a problem, please [report the bug here](https://bugzilla.mozilla.org/enter_bug.cgi?product=Add-on%20SDK).
## Developing Add-ons
These resources should provide some help:
* [Add-on SDK Documentation](https://developer.mozilla.org/en-US/Add-ons/SDK)
* [Community Developed Modules](https://github.com/mozilla/addon-sdk/wiki/Community-developed-modules)
* [Jetpack FAQ](https://wiki.mozilla.org/Jetpack/FAQ)
* [StackOverflow Questions](http://stackoverflow.com/questions/tagged/firefox-addon-sdk)
* [Mailing List](https://wiki.mozilla.org/Jetpack#Mailing_list)
* #jetpack on irc.mozilla.org
## Contributing Code
Please read these two guides if you wish to contribute some patches to the addon-sdk:
* [Contribute Guide](https://github.com/mozilla/addon-sdk/wiki/Contribute)
* [Style Guide](https://github.com/mozilla/addon-sdk/wiki/Coding-style-guide)
## Issues
We use [bugzilla](https://bugzilla.mozilla.org/) as our issue tracker, here are some useful links:
* [File a bug](https://bugzilla.mozilla.org/enter_bug.cgi?product=Add-on%20SDK)
* [Open bugs](https://bugzilla.mozilla.org/buglist.cgi?bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&columnlist=bug_severity%2Cpriority%2Cassigned_to%2Cbug_status%2Ctarget_milestone%2Cresolution%2Cshort_desc%2Cchangeddate&product=Add-on%20SDK&query_format=advanced&order=priority)
* [Good first bugs](https://bugzilla.mozilla.org/buglist.cgi?status_whiteboard=[good+first+bug]&&resolution=---&product=Add-on+SDK)

View File

@ -1,28 +0,0 @@
/* 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 BLACKLIST = [];
var readParam = require("./node-scripts/utils").readParam;
var path = require("path");
var Mocha = require("mocha");
var mocha = new Mocha({
ui: "bdd",
reporter: "spec",
timeout: 900000
});
var type = readParam("type");
[
(!type || type == "modules") && require.resolve("../bin/node-scripts/test.modules"),
(!type || type == "addons") && require.resolve("../bin/node-scripts/test.addons"),
(!type || type == "examples") && require.resolve("../bin/node-scripts/test.examples"),
].sort().forEach(function(filepath) {
filepath && mocha.addFile(filepath);
})
mocha.run(function (failures) {
process.exit(failures);
});

View File

@ -1,48 +0,0 @@
/* 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("./utils");
var path = require("path");
var fs = require("fs");
var jpm = utils.run;
var readParam = utils.readParam;
var addonsPath = path.join(__dirname, "..", "..", "test", "addons");
var binary = process.env.JPM_FIREFOX_BINARY || "nightly";
var filterPattern = readParam("filter");
describe("jpm test sdk addons", function () {
fs.readdirSync(addonsPath)
.filter(fileFilter.bind(null, addonsPath))
.forEach(function (file) {
it(file, function (done) {
var addonPath = path.join(addonsPath, file);
process.chdir(addonPath);
var options = { cwd: addonPath, env: { JPM_FIREFOX_BINARY: binary }};
if (process.env.DISPLAY) {
options.env.DISPLAY = process.env.DISPLAY;
}
if (/^e10s/.test(file)) {
options.e10s = true;
}
jpm("run", options).then(done).catch(done);
});
});
});
function fileFilter(root, file) {
var matcher = filterPattern && new RegExp(filterPattern);
if (/^(l10n|simple-prefs|page-mod-debugger)/.test(file)) {
return false;
}
if (matcher && !matcher.test(file)) {
return false;
}
var stat = fs.statSync(path.join(root, file))
return (stat && stat.isDirectory());
}

View File

@ -1,45 +0,0 @@
/* 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("./utils");
var path = require("path");
var fs = require("fs");
var jpm = utils.run;
var readParam = utils.readParam;
var examplesPath = path.join(__dirname, "..", "..", "examples");
var binary = process.env.JPM_FIREFOX_BINARY || "nightly";
var filterPattern = readParam("filter");
describe("jpm test sdk examples", function () {
fs.readdirSync(examplesPath)
.filter(fileFilter.bind(null, examplesPath))
.forEach(function (file) {
it(file, function (done) {
var addonPath = path.join(examplesPath, file);
process.chdir(addonPath);
var options = { cwd: addonPath, env: { JPM_FIREFOX_BINARY: binary }};
if (process.env.DISPLAY) {
options.env.DISPLAY = process.env.DISPLAY;
}
jpm("test", options).then(done);
});
});
});
function fileFilter(root, file) {
var matcher = filterPattern && new RegExp(filterPattern);
if (/^(reading-data)/.test(file)) {
return false;
}
if (matcher && !matcher.test(file)) {
return false;
}
var stat = fs.statSync(path.join(root, file))
return (stat && stat.isDirectory());
}

View File

@ -1,28 +0,0 @@
/* 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("./utils");
var readParam = utils.readParam;
var path = require("path");
var fs = require("fs");
var jpm = utils.run;
var sdk = path.join(__dirname, "..", "..");
var binary = process.env.JPM_FIREFOX_BINARY || "nightly";
var filterPattern = readParam("filter");
describe("jpm test sdk modules", function () {
it("SDK Modules", function (done) {
process.chdir(sdk);
var options = { cwd: sdk, env: { JPM_FIREFOX_BINARY: binary } };
if (process.env.DISPLAY) {
options.env.DISPLAY = process.env.DISPLAY;
}
options.filter = filterPattern;
jpm("test", options, process).then(done);
});
});

View File

@ -1,70 +0,0 @@
/* 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 _ = require("lodash");
var path = require("path");
var child_process = require("child_process");
var jpm = require.resolve("../../node_modules/jpm/bin/jpm");
var Promise = require("promise");
var chai = require("chai");
var expect = chai.expect;
var assert = chai.assert;
var DEFAULT_PROCESS = process;
var sdk = path.join(__dirname, "..", "..");
var prefsPath = path.join(sdk, "test", "preferences", "test-preferences.js");
var e10sPrefsPath = path.join(sdk, "test", "preferences", "test-e10s-preferences.js");
function spawn (cmd, options) {
options = options || {};
var env = _.extend({}, options.env, process.env);
var e10s = options.e10s || false;
return child_process.spawn("node", [
jpm, cmd, "-v",
"--prefs", e10s ? e10sPrefsPath : prefsPath,
"-o", sdk,
"-f", options.filter || ""
], {
cwd: options.cwd || tmpOutputDir,
env: env
});
}
exports.spawn = spawn;
function run (cmd, options, p) {
return new Promise(function(resolve) {
var output = [];
var proc = spawn(cmd, options);
proc.stderr.pipe(process.stderr);
proc.stdout.on("data", function (data) {
output.push(data);
});
if (p) {
proc.stdout.pipe(p.stdout);
}
proc.on("close", function(code) {
var out = output.join("");
var noTests = /No tests were run/.test(out);
var hasSuccess = /All tests passed!/.test(out);
var hasFailure = /There were test failures\.\.\./.test(out);
if (noTests || hasFailure || !hasSuccess || code != 0) {
DEFAULT_PROCESS.stdout.write(out);
}
expect(code).to.equal(hasFailure ? 1 : 0);
expect(hasFailure).to.equal(false);
expect(hasSuccess).to.equal(true);
expect(noTests).to.equal(false);
resolve();
});
});
}
exports.run = run;
function readParam(name) {
var index = process.argv.indexOf("--" + name)
return index >= 0 && process.argv[index + 1]
}
exports.readParam = readParam;

View File

@ -1,13 +0,0 @@
/* 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";
// Note that this file is temporary workaroud until JPM is smart enough
// to cover it on it's own.
const { utils: Cu } = Components;
const rootURI = __SCRIPT_URI_SPEC__.replace("bootstrap.js", "");
const { require } = Cu.import(`${rootURI}/lib/toolkit/require.js`, {});
const { Bootstrap } = require(`${rootURI}/lib/sdk/addon/bootstrap.js`);
const { startup, shutdown, install, uninstall } = new Bootstrap(rootURI);

View File

@ -1,10 +0,0 @@
/* 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.testMain = function(assert) {
assert.pass("TODO: Write some tests.");
};
require("sdk/test").run(exports);

View File

@ -1,11 +1,9 @@
{
"license": "MPL 2.0",
"name": "annotator",
"contributors": [],
"author": "Will Bamberg",
"keywords": [],
"version": "0.1.1",
"id": "anonid0-annotator@jetpack",
"description": "Add notes to Web pages",
"main": "./lib/main.js"
"license": "MPL 2.0",
"name": "annotator",
"contributors": [],
"author": "Will Bamberg",
"keywords": [],
"id": "anonid0-annotator",
"description": "Add notes to Web pages"
}

View File

@ -1,10 +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.testMain = function(assert) {
assert.pass("TODO: Write some tests.");
exports.testMain = function(test) {
test.pass("TODO: Write some tests.");
};
require("sdk/test").run(exports);

View File

@ -1,10 +0,0 @@
/* 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.testMain = function(assert) {
assert.pass("TODO: Write some tests.");
};
require("sdk/test").run(exports);

View File

@ -1,10 +1,9 @@
{
"name": "library-detector-sdk",
"license": "MPL 2.0",
"author": "",
"version": "0.1.1",
"title": "library-detector-sdk",
"id": "jid1-R4rSVNkBANnvGQ@jetpack",
"description": "a basic add-on",
"main": "./lib/main.js"
"name": "library-detector-sdk",
"license": "MPL 2.0",
"author": "",
"version": "0.1",
"title": "library-detector-sdk",
"id": "jid1-R4rSVNkBANnvGQ",
"description": "a basic add-on"
}

View File

@ -1,10 +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.testMain = function(assert) {
assert.pass("TODO: Write some tests.");
exports.testMain = function(test) {
test.pass("TODO: Write some tests.");
};
require("sdk/test").run(exports);

View File

@ -5,7 +5,7 @@
"description": "a toolbar api example",
"author": "",
"license": "MPL 2.0",
"version": "0.1.1",
"version": "0.1",
"engines": {
"firefox": ">=27.0 <=30.0"
}

View File

@ -1,10 +0,0 @@
/* 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.testMain = function(assert) {
assert.pass("TODO: Write some tests.");
};
require("sdk/test").run(exports);

View File

@ -5,6 +5,5 @@
"description": "A Button API example",
"author": "jeff@canuckistani.ca (Jeff Griffiths | @canuckistani)",
"license": "MPL 2.0",
"version": "0.1.1",
"main": "./lib/main.js"
"version": "0.1"
}

View File

@ -3,15 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
try {
// CFX use case..
var { actionButton, toggleButton, icon } = require("main");
}
catch (e) {
// JPM use case..
let mainURI = "../lib/main";
var { actionButton, toggleButton, icon } = require(mainURI);
}
var { actionButton, toggleButton, icon } = require("main");
var self = require("sdk/self");
exports.testActionButton = function(assert) {

View File

@ -16,13 +16,11 @@ const targetFor = target => {
return devtools.TargetFactory.forTab(target);
};
const getId = id => ((id.prototype && id.prototype.id) || id.id || id);
const getCurrentPanel = toolbox => toolbox.getCurrentPanel();
exports.getCurrentPanel = getCurrentPanel;
const openToolbox = (id, tab) => {
id = getId(id);
id = id.prototype.id || id.id || id;
return gDevTools.showToolbox(targetFor(tab), id);
};
exports.openToolbox = openToolbox;
@ -34,7 +32,7 @@ const getToolbox = tab => gDevTools.getToolbox(targetFor(tab));
exports.getToolbox = getToolbox;
const openToolboxPanel = (id, tab) => {
id = getId(id);
id = id.prototype.id || id.id || id;
return gDevTools.showToolbox(targetFor(tab), id).then(getCurrentPanel);
};
exports.openToolboxPanel = openToolboxPanel;

View File

@ -1,212 +0,0 @@
"use strict";
const { query, constant, cache } = require("sdk/lang/functional");
const { pairs, each, map, object } = require("sdk/util/sequence");
const { nodeToMessageManager } = require("./util");
// Decorator function that takes `f` function and returns one that attempts
// to run `f` with given arguments. In case of exception error is logged
// and `fallback` is returned instead.
const Try = (fn, fallback=null) => (...args) => {
try {
return fn(...args);
} catch(error) {
console.error(error);
return fallback;
}
};
// Decorator funciton that takes `f` function and returns one that returns
// JSON cloned result of whatever `f` returns for given arguments.
const JSONReturn = f => (...args) => JSON.parse(JSON.stringify(f(...args)));
const Null = constant(null);
// Table of readers mapped to field names they're going to be reading.
const readers = Object.create(null);
// Read function takes "contextmenu" event target `node` and returns table of
// read field names mapped to appropriate values. Read uses above defined read
// table to read data for all registered readers.
const read = node =>
object(...map(([id, read]) => [id, read(node, id)], pairs(readers)));
// Table of built-in readers, each takes a descriptor and returns a reader:
// descriptor -> node -> JSON
const parsers = Object.create(null)
// Function takes a descriptor of the remotely defined reader and parsese it
// to construct a local reader that's going to read out data from context menu
// target.
const parse = descriptor => {
const parser = parsers[descriptor.category];
if (!parser) {
console.error("Unknown reader descriptor was received", descriptor, `"${descriptor.category}"`);
return Null
}
return Try(parser(descriptor));
}
// TODO: Test how chrome's mediaType behaves to try and match it's behavior.
const HTML_NS = "http://www.w3.org/1999/xhtml";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const SVG_NS = "http://www.w3.org/2000/svg";
// Firefox always creates a HTMLVideoElement when loading an ogg file
// directly. If the media is actually audio, be smarter and provide a
// context menu with audio operations.
// Source: https://github.com/mozilla/gecko-dev/blob/28c2fca3753c5371643843fc2f2f205146b083b7/browser/base/content/nsContextMenu.js#L632-L637
const isVideoLoadingAudio = node =>
node.readyState >= node.HAVE_METADATA &&
(node.videoWidth == 0 || node.videoHeight == 0)
const isVideo = node =>
node instanceof node.ownerDocument.defaultView.HTMLVideoElement &&
!isVideoLoadingAudio(node);
const isAudio = node => {
const {HTMLVideoElement, HTMLAudioElement} = node.ownerDocument.defaultView;
return node instanceof HTMLAudioElement ? true :
node instanceof HTMLVideoElement ? isVideoLoadingAudio(node) :
false;
};
const isImage = ({namespaceURI, localName}) =>
namespaceURI === HTML_NS && localName === "img" ? true :
namespaceURI === XUL_NS && localName === "image" ? true :
namespaceURI === SVG_NS && localName === "image" ? true :
false;
parsers["reader/MediaType()"] = constant(node =>
isImage(node) ? "image" :
isAudio(node) ? "audio" :
isVideo(node) ? "video" :
null);
const readLink = node =>
node.namespaceURI === HTML_NS && node.localName === "a" ? node.href :
readLink(node.parentNode);
parsers["reader/LinkURL()"] = constant(node =>
node.matches("a, a *") ? readLink(node) : null);
// Reader that reads out `true` if "contextmenu" `event.target` matches
// `descriptor.selector` and `false` if it does not.
parsers["reader/SelectorMatch()"] = ({selector}) =>
node => node.matches(selector);
// Accessing `selectionStart` and `selectionEnd` properties on non
// editable input nodes throw exceptions, there for we need this util
// function to guard us against them.
const getInputSelection = node => {
try {
if ("selectionStart" in node && "selectionEnd" in node) {
const {selectionStart, selectionEnd} = node;
return {selectionStart, selectionEnd}
}
}
catch(_) {}
return null;
}
// Selection reader does not really cares about descriptor so it is
// a constant function returning selection reader. Selection reader
// returns string of the selected text or `null` if there is no selection.
parsers["reader/Selection()"] = constant(node => {
const selection = node.ownerDocument.getSelection();
if (!selection.isCollapsed) {
return selection.toString();
}
// If target node is editable (text, input, textarea, etc..) document does
// not really handles selections there. There for we fallback to checking
// `selectionStart` `selectionEnd` properties and if they are present we
// extract selections manually from the `node.value`.
else {
const selection = getInputSelection(node);
const isSelected = selection &&
Number.isInteger(selection.selectionStart) &&
Number.isInteger(selection.selectionEnd) &&
selection.selectionStart !== selection.selectionEnd;
return isSelected ? node.value.substring(selection.selectionStart,
selection.selectionEnd) :
null;
}
});
// Query reader just reads out properties from the node, so we just use `query`
// utility function.
parsers["reader/Query()"] = ({path}) => JSONReturn(query(path));
// Attribute reader just reads attribute of the event target node.
parsers["reader/Attribute()"] = ({name}) => node => node.getAttribute(name);
// Extractor reader defines generates a reader out of serialized function, who's
// return value is JSON cloned. Note: We do know source will evaluate to function
// as that's what we serialized on the other end, it's also ok if generated function
// is going to throw as registered readers are wrapped in try catch to avoid breakting
// unrelated readers.
parsers["reader/Extractor()"] = ({source}) =>
JSONReturn(new Function("return (" + source + ")")());
// If the context-menu target node or any of its ancestors is one of these,
// Firefox uses a tailored context menu, and so the page context doesn't apply.
// There for `reader/isPage()` will read `false` in that case otherwise it's going
// to read `true`.
const nonPageElements = ["a", "applet", "area", "button", "canvas", "object",
"embed", "img", "input", "map", "video", "audio", "menu",
"option", "select", "textarea", "[contenteditable=true]"];
const nonPageSelector = nonPageElements.
concat(nonPageElements.map(tag => `${tag} *`)).
join(", ");
// Note: isPageContext implementation could have actually used SelectorMatch reader,
// but old implementation was also checked for collapsed selection there for to keep
// the behavior same we end up implementing a new reader.
parsers["reader/isPage()"] = constant(node =>
node.ownerDocument.defaultView.getSelection().isCollapsed &&
!node.matches(nonPageSelector));
// Reads `true` if node is in an iframe otherwise returns true.
parsers["reader/isFrame()"] = constant(node =>
!!node.ownerDocument.defaultView.frameElement);
parsers["reader/isEditable()"] = constant(node => {
const selection = getInputSelection(node);
return selection ? !node.readOnly && !node.disabled : node.isContentEditable;
});
// TODO: Add some reader to read out tab id.
const onReadersUpdate = message => {
each(([id, descriptor]) => {
if (descriptor) {
readers[id] = parse(descriptor);
}
else {
delete readers[id];
}
}, pairs(message.data));
};
exports.onReadersUpdate = onReadersUpdate;
const onContextMenu = event => {
if (!event.defaultPrevented) {
const manager = nodeToMessageManager(event.target);
manager.sendSyncMessage("sdk/context-menu/read", read(event.target), readers);
}
};
exports.onContextMenu = onContextMenu;
const onContentFrame = (frame) => {
// Listen for contextmenu events in on this frame.
frame.addEventListener("contextmenu", onContextMenu);
// Listen to registered reader changes and update registry.
frame.addMessageListener("sdk/context-menu/readers", onReadersUpdate);
// Request table of readers (if this is loaded in a new process some table
// changes may be missed, this is way to sync up).
frame.sendAsyncMessage("sdk/context-menu/readers?");
};
exports.onContentFrame = onContentFrame;

View File

@ -1,26 +0,0 @@
/* 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";
module.metadata = {
"stability": "unstable"
};
const mime = "application/javascript";
const requireURI = module.uri.replace("framescript/manager.js",
"toolkit/require.js");
const requireLoadURI = `data:${mime},this["Components"].utils.import("${requireURI}")`
// Loads module with given `id` into given `messageManager` via shared module loader. If `init`
// string is passed, will call module export with that name and pass frame script environment
// of the `messageManager` into it. Since module will load only once per process (which is
// once for chrome proces & second for content process) it is useful to have an init function
// to setup event listeners on each content frame.
const loadModule = (messageManager, id, allowDelayed, init) => {
const moduleLoadURI = `${requireLoadURI}.require("${id}")`
const uri = init ? `${moduleLoadURI}.${init}(this)` : moduleLoadURI;
messageManager.loadFrameScript(uri, allowDelayed);
};
exports.loadModule = loadModule;

View File

@ -1,25 +0,0 @@
/* 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";
module.metadata = {
"stability": "unstable"
};
const { Ci } = require("chrome");
const windowToMessageManager = window =>
window.
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDocShell).
sameTypeRootTreeItem.
QueryInterface(Ci.nsIDocShell).
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIContentFrameMessageManager);
exports.windowToMessageManager = windowToMessageManager;
const nodeToMessageManager = node =>
windowToMessageManager(node.ownerDocument.defaultView);
exports.nodeToMessageManager = nodeToMessageManager;

View File

@ -1,3 +0,0 @@
/* 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/. */

View File

@ -1,160 +0,0 @@
/* 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 { Cu } = require("chrome");
const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
const { Task: { spawn } } = require("resource://gre/modules/Task.jsm");
const { readURI } = require("sdk/net/url");
const { mount, unmount } = require("sdk/uri/resource");
const { setTimeout } = require("sdk/timers");
const { Loader, Require, Module, main, unload } = require("toolkit/loader");
const prefs = require("sdk/preferences/service");
// load below now, so that it can be used by sdk/addon/runner
// see bug https://bugzilla.mozilla.org/show_bug.cgi?id=1042239
const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {});
const REASON = [ "unknown", "startup", "shutdown", "enable", "disable",
"install", "uninstall", "upgrade", "downgrade" ];
const UUID_PATTERN = /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/;
// Takes add-on ID and normalizes it to a domain name so that add-on
// can be mapped to resource://domain/
const readDomain = id =>
// If only `@` character is the first one, than just substract it,
// otherwise fallback to legacy normalization code path. Note: `.`
// is valid character for resource substitutaiton & we intend to
// make add-on URIs intuitive, so it's best to just stick to an
// add-on author typed input.
id.lastIndexOf("@") === 0 ? id.substr(1).toLowerCase() :
id.toLowerCase().
replace(/@/g, "-at-").
replace(/\./g, "-dot-").
replace(UUID_PATTERN, "$1");
const readPaths = id => {
const base = `extensions.modules.${id}.path.`;
const domain = readDomain(id);
return prefs.keys(base).reduce((paths, key) => {
const value = prefs.get(key);
const name = key.replace(base, "");
const path = name.split(".").join("/");
const prefix = path.length ? `${path}/` : path;
const uri = value.endsWith("/") ? value : `${value}/`;
const root = `extensions.modules.${domain}.commonjs.path.${name}`;
mount(root, uri);
paths[prefix] = `resource://${root}/`;
return paths;
}, {});
};
const Bootstrap = function(mountURI) {
this.mountURI = mountURI;
this.install = this.install.bind(this);
this.uninstall = this.uninstall.bind(this);
this.startup = this.startup.bind(this);
this.shutdown = this.shutdown.bind(this);
};
Bootstrap.prototype = {
constructor: Bootstrap,
mount(domain, rootURI) {
mount(domain, rootURI);
this.domain = domain;
},
unmount() {
if (this.domain) {
unmount(this.domain);
this.domain = null;
}
},
install(addon, reason) {
},
uninstall(addon, reason) {
const {id} = addon;
prefs.reset(`extensions.${id}.sdk.domain`);
prefs.reset(`extensions.${id}.sdk.version`);
prefs.reset(`extensions.${id}.sdk.rootURI`);
prefs.reset(`extensions.${id}.sdk.baseURI`);
prefs.reset(`extensions.${id}.sdk.load.reason`);
},
startup(addon, reasonCode) {
const { id, version, resourceURI: {spec: addonURI} } = addon;
const rootURI = this.mountURI || addonURI;
const reason = REASON[reasonCode];
spawn(function*() {
const metadata = JSON.parse(yield readURI(`${rootURI}package.json`));
const domain = readDomain(id);
const baseURI = `resource://${domain}/`;
this.mount(domain, rootURI);
prefs.set(`extensions.${id}.sdk.domain`, domain);
prefs.set(`extensions.${id}.sdk.version`, version);
prefs.set(`extensions.${id}.sdk.rootURI`, rootURI);
prefs.set(`extensions.${id}.sdk.baseURI`, baseURI);
prefs.set(`extensions.${id}.sdk.load.reason`, reason);
const command = prefs.get(`extensions.${id}.sdk.load.command`);
const loader = Loader({
id,
isNative: true,
checkCompatibility: true,
prefixURI: baseURI,
rootURI: baseURI,
name: metadata.name,
paths: Object.assign({
"": "resource://gre/modules/commonjs/",
"devtools/": "resource://gre/modules/devtools/",
"./": baseURI
}, readPaths(id)),
manifest: metadata,
metadata: metadata,
modules: {
"@test/options": {}
},
noQuit: prefs.get(`extensions.${id}.sdk.test.no-quit`, false)
});
this.loader = loader;
const module = Module("package.json", `${baseURI}package.json`);
const require = Require(loader, module);
const main = command === "test" ? "sdk/test/runner" : null;
const prefsURI = `${baseURI}defaults/preferences/prefs.js`;
const { startup } = require("sdk/addon/runner");
startup(reason, {loader, main, prefsURI});
}.bind(this)).catch(error => {
console.error(`Failed to start ${id} addon`, error);
throw error;
});
},
shutdown(addon, code) {
const { loader, domain } = this;
this.unmount();
this.unload(REASON[code]);
},
unload(reason) {
const {loader} = this;
if (loader) {
this.loader = null;
unload(loader, reason);
setTimeout(() => {
for (let uri of Object.keys(loader.sandboxes)) {
Cu.nukeSandbox(loader.sandboxes[uri]);
delete loader.sandboxes[uri];
delete loader.modules[uri];
}
}, 1000);
}
}
};
exports.Bootstrap = Bootstrap;

View File

@ -9,8 +9,7 @@ module.metadata = {
"engines": {
// TODO Fennec Support 789757
"Firefox": "*",
"SeaMonkey": "*",
"Thunderbird": "*"
"SeaMonkey": "*"
}
};
@ -125,24 +124,26 @@ exports.set = function(aData, aDataType) {
switch (flavor) {
case "text/html":
// add text/html flavor
let str = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
str.data = options.data;
xferable.addDataFlavor(flavor);
xferable.setTransferData(flavor, str, str.data.length * 2);
let (str = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString))
{
str.data = options.data;
xferable.addDataFlavor(flavor);
xferable.setTransferData(flavor, str, str.data.length * 2);
}
// add a text/unicode flavor (html converted to plain text)
str = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
let converter = Cc["@mozilla.org/feed-textconstruct;1"].
createInstance(Ci.nsIFeedTextConstruct);
converter.type = "html";
converter.text = options.data;
str.data = converter.plainText();
xferable.addDataFlavor("text/unicode");
xferable.setTransferData("text/unicode", str, str.data.length * 2);
let (str = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString),
converter = Cc["@mozilla.org/feed-textconstruct;1"].
createInstance(Ci.nsIFeedTextConstruct))
{
converter.type = "html";
converter.text = options.data;
str.data = converter.plainText();
xferable.addDataFlavor("text/unicode");
xferable.setTransferData("text/unicode", str, str.data.length * 2);
}
break;
// Set images to the clipboard is not straightforward, to have an idea how

View File

@ -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";
module.metadata = {
"stability": "unstable"
};
const { emit } = require('../event/core');
const { omit } = require('../util/object');
const { Class } = require('../core/heritage');
const { method } = require('../lang/functional');
const { getInnerId } = require('../window/utils');
const { EventTarget } = require('../event/target');
const { when, ensure } = require('../system/unload');
const { getTabForWindow } = require('../tabs/helpers');
const { getTabForContentWindow, getBrowserForTab } = require('../tabs/utils');
const { isPrivate } = require('../private-browsing/utils');
const { getFrameElement } = require('../window/utils');
const { attach, detach, destroy } = require('./utils');
const { on: observe } = require('../system/events');
const { uuid } = require('../util/uuid');
const { Ci, Cc } = require('chrome');
const ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"].
getService(Ci.nsIMessageBroadcaster);
// null-out cycles in .modules to make @loader/options JSONable
const ADDON = omit(require('@loader/options'), ['modules', 'globals']);
const workers = new WeakMap();
let modelFor = (worker) => workers.get(worker);
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.";
const ERR_FROZEN = "The page is currently hidden and can no longer be used " +
"until it is visible again.";
// a handle for communication between content script and addon code
const Worker = Class({
implements: [EventTarget],
initialize(options = {}) {
let model = {
inited: false,
earlyEvents: [], // fired before worker was inited
frozen: true, // document is in BFcache, let it go
options,
};
workers.set(this, model);
ensure(this, 'destroy');
this.on('detach', this.detach);
EventTarget.prototype.initialize.call(this, options);
this.receive = this.receive.bind(this);
model.observe = ({ subject }) => {
let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
if (model.window && getInnerId(model.window) === id)
this.detach();
}
observe('inner-window-destroyed', model.observe);
this.port = EventTarget();
this.port.emit = this.send.bind(this, 'event');
this.postMessage = this.send.bind(this, 'message');
if ('window' in options)
attach(this, options.window);
},
// messages
receive({ data: { id, args }}) {
let model = modelFor(this);
if (id !== model.id || !model.childWorker)
return;
if (args[0] === 'event')
emit(this.port, ...args.slice(1))
else
emit(this, ...args);
},
send(...args) {
let model = modelFor(this);
if (!model.inited) {
model.earlyEvents.push(args);
return;
}
if (!model.childWorker && args[0] !== 'detach')
throw new Error(ERR_DESTROYED);
if (model.frozen && args[0] !== 'detach')
throw new Error(ERR_FROZEN);
try {
model.manager.sendAsyncMessage('sdk/worker/message', { id: model.id, args });
} catch (e) {
//
}
},
// properties
get url() {
let { window } = modelFor(this);
return window && window.document.location.href;
},
get contentURL() {
let { window } = modelFor(this);
return window && window.document.URL;
},
get tab() {
let { window } = modelFor(this);
return window && getTabForWindow(window);
},
toString: () => '[object Worker]',
// methods
attach: method(attach),
detach: method(detach),
destroy: method(destroy),
})
exports.Worker = Worker;
attach.define(Worker, function(worker, window) {
let model = modelFor(worker);
model.window = window;
model.options.window = getInnerId(window);
model.id = model.options.id = String(uuid());
let tab = getTabForContentWindow(window);
if (tab) {
model.manager = getBrowserForTab(tab).messageManager;
} else {
model.manager = getFrameElement(window.top).frameLoader.messageManager;
}
model.manager.addMessageListener('sdk/worker/event', worker.receive);
model.manager.addMessageListener('sdk/worker/attach', attach);
model.manager.sendAsyncMessage('sdk/worker/create', {
options: model.options,
addon: ADDON
});
function attach({ data }) {
if (data.id !== model.id)
return;
model.manager.removeMessageListener('sdk/worker/attach', attach);
model.childWorker = true;
worker.on('pageshow', () => model.frozen = false);
worker.on('pagehide', () => model.frozen = true);
model.inited = true;
model.frozen = false;
model.earlyEvents.forEach(args => worker.send(...args));
emit(worker, 'attach', window);
}
})
// unload and release the child worker, release window reference
detach.define(Worker, function(worker, reason) {
let model = modelFor(worker);
worker.send('detach', reason);
if (!model.childWorker)
return;
model.childWorker = null;
model.earlyEvents = [];
model.window = null;
emit(worker, 'detach');
model.manager.removeMessageListener('sdk/worker/event', this.receive);
})
isPrivate.define(Worker, ({ tab }) => isPrivate(tab));
// unlod worker, release references
destroy.define(Worker, function(worker, reason) {
detach(worker, reason);
modelFor(worker).inited = true;
})
// unload Loaders used for creating WorkerChild instances in each process
when(() => ppmm.broadcastAsyncMessage('sdk/loader/unload', { data: ADDON }));

View File

@ -7,178 +7,280 @@ module.metadata = {
"stability": "unstable"
};
const { emit } = require('../event/core');
const { omit } = require('../util/object');
const { Class } = require('../core/heritage');
const { method } = require('../lang/functional');
const { getInnerId } = require('../window/utils');
const { EventTarget } = require('../event/target');
const { when, ensure } = require('../system/unload');
const { on, off, emit, setListeners } = require('../event/core');
const {
attach, detach, destroy
} = require('./utils');
const { method } = require('../lang/functional');
const { Ci, Cu, Cc } = require('chrome');
const unload = require('../system/unload');
const events = require('../system/events');
const { getInnerId } = require("../window/utils");
const { WorkerSandbox } = require('./sandbox');
const { getTabForWindow } = require('../tabs/helpers');
const { getTabForContentWindow, getBrowserForTab } = require('../tabs/utils');
const { isPrivate } = require('../private-browsing/utils');
const { getFrameElement } = require('../window/utils');
const { attach, detach, destroy } = require('./utils');
const { on: observe } = require('../system/events');
const { uuid } = require('../util/uuid');
const { Ci, Cc } = require('chrome');
const ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"].
getService(Ci.nsIMessageBroadcaster);
// null-out cycles in .modules to make @loader/options JSONable
const ADDON = omit(require('@loader/options'), ['modules', 'globals']);
// A weak map of workers to hold private attributes that
// should not be exposed
const workers = new WeakMap();
let modelFor = (worker) => workers.get(worker);
const ERR_DESTROYED = "Couldn't find the worker to receive this message. " +
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.";
const ERR_FROZEN = "The page is currently hidden and can no longer be used " +
"until it is visible again.";
// a handle for communication between content script and addon code
/**
* Message-passing facility for communication between code running
* in the content and add-on process.
* @see https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/content_worker
*/
const Worker = Class({
implements: [EventTarget],
initialize(options = {}) {
let model = {
inited: false,
earlyEvents: [], // fired before worker was inited
frozen: true, // document is in BFcache, let it go
options,
};
initialize: function WorkerConstructor (options) {
// Save model in weak map to not expose properties
let model = createModel();
workers.set(this, model);
ensure(this, 'destroy');
this.on('detach', this.detach);
EventTarget.prototype.initialize.call(this, options);
options = options || {};
this.receive = this.receive.bind(this);
if ('contentScriptFile' in options)
this.contentScriptFile = options.contentScriptFile;
if ('contentScriptOptions' in options)
this.contentScriptOptions = options.contentScriptOptions;
if ('contentScript' in options)
this.contentScript = options.contentScript;
if ('injectInDocument' in options)
this.injectInDocument = !!options.injectInDocument;
model.observe = ({ subject }) => {
let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
if (model.window && getInnerId(model.window) === id)
this.detach();
}
setListeners(this, options);
observe('inner-window-destroyed', model.observe);
unload.ensure(this, "destroy");
this.port = EventTarget();
this.port.emit = this.send.bind(this, 'event');
this.postMessage = this.send.bind(this, 'message');
// Ensure that worker.port is initialized for contentWorker to be able
// to send events during worker initialization.
this.port = createPort(this);
model.documentUnload = documentUnload.bind(this);
model.pageShow = pageShow.bind(this);
model.pageHide = pageHide.bind(this);
if ('window' in options)
attach(this, options.window);
},
// messages
receive({ data: { id, args }}) {
let model = modelFor(this);
if (id !== model.id || !model.childWorker)
return;
if (args[0] === 'event')
emit(this.port, ...args.slice(1))
else
emit(this, ...args);
},
send(...args) {
/**
* Sends a message to the worker's global scope. Method takes single
* argument, which represents data to be sent to the worker. The data may
* be any primitive type value or `JSON`. Call of this method asynchronously
* emits `message` event with data value in the global scope of this
* symbiont.
*
* `message` event listeners can be set either by calling
* `self.on` with a first argument string `"message"` or by
* implementing `onMessage` function in the global scope of this worker.
* @param {Number|String|JSON} data
*/
postMessage: function (...data) {
let model = modelFor(this);
let args = ['message'].concat(data);
if (!model.inited) {
model.earlyEvents.push(args);
return;
}
if (!model.childWorker && args[0] !== 'detach')
throw new Error(ERR_DESTROYED);
if (model.frozen && args[0] !== 'detach')
throw new Error(ERR_FROZEN);
try {
model.manager.sendAsyncMessage('sdk/worker/message', { id: model.id, args });
} catch (e) {
//
}
processMessage.apply(null, [this].concat(args));
},
// properties
get url() {
let { window } = modelFor(this);
return window && window.document.location.href;
get url () {
let model = modelFor(this);
// model.window will be null after detach
return model.window ? model.window.document.location.href : null;
},
get contentURL() {
let { window } = modelFor(this);
return window && window.document.URL;
get contentURL () {
let model = modelFor(this);
return model.window ? model.window.document.URL : null;
},
get tab() {
let { window } = modelFor(this);
return window && getTabForWindow(window);
get tab () {
let model = modelFor(this);
// model.window will be null after detach
if (model.window)
return getTabForWindow(model.window);
return null;
},
toString: () => '[object Worker]',
// methods
// Implemented to provide some of the previous features of exposing sandbox
// so that Worker can be extended
getSandbox: function () {
return modelFor(this).contentWorker;
},
toString: function () { return '[object Worker]'; },
attach: method(attach),
detach: method(detach),
destroy: method(destroy),
})
destroy: method(destroy)
});
exports.Worker = Worker;
attach.define(Worker, function(worker, window) {
attach.define(Worker, function (worker, window) {
let model = modelFor(worker);
model.window = window;
model.options.window = getInnerId(window);
model.id = model.options.id = String(uuid());
// Track document unload to destroy this worker.
// We can't watch for unload event on page's window object as it
// prevents bfcache from working:
// https://developer.mozilla.org/En/Working_with_BFCache
model.windowID = getInnerId(model.window);
events.on("inner-window-destroyed", model.documentUnload);
let tab = getTabForContentWindow(window);
if (tab) {
model.manager = getBrowserForTab(tab).messageManager;
} else {
model.manager = getFrameElement(window.top).frameLoader.messageManager;
}
// will set model.contentWorker pointing to the private API:
model.contentWorker = WorkerSandbox(worker, model.window);
model.manager.addMessageListener('sdk/worker/event', worker.receive);
model.manager.addMessageListener('sdk/worker/attach', attach);
// Listen to pagehide event in order to freeze the content script
// while the document is frozen in bfcache:
model.window.addEventListener("pageshow", model.pageShow, true);
model.window.addEventListener("pagehide", model.pageHide, true);
model.manager.sendAsyncMessage('sdk/worker/create', {
options: model.options,
addon: ADDON
});
// Mainly enable worker.port.emit to send event to the content worker
model.inited = true;
model.frozen = false;
function attach({ data }) {
if (data.id !== model.id)
return;
model.manager.removeMessageListener('sdk/worker/attach', attach);
model.childWorker = true;
// Fire off `attach` event
emit(worker, 'attach', window);
worker.on('pageshow', () => model.frozen = false);
worker.on('pagehide', () => model.frozen = true);
// Process all events and messages that were fired before the
// worker was initialized.
model.earlyEvents.forEach(args => processMessage.apply(null, [worker].concat(args)));
});
model.inited = true;
model.frozen = false;
model.earlyEvents.forEach(args => worker.send(...args));
emit(worker, 'attach', window);
}
})
// unload and release the child worker, release window reference
detach.define(Worker, function(worker, reason) {
/**
* Remove all internal references to the attached document
* Tells _port to unload itself and removes all the references from itself.
*/
detach.define(Worker, function (worker, reason) {
let model = modelFor(worker);
worker.send('detach', reason);
if (!model.childWorker)
return;
model.childWorker = null;
model.earlyEvents = [];
// maybe unloaded before content side is created
if (model.contentWorker) {
model.contentWorker.destroy(reason);
}
model.contentWorker = null;
if (model.window) {
model.window.removeEventListener("pageshow", model.pageShow, true);
model.window.removeEventListener("pagehide", model.pageHide, true);
}
model.window = null;
emit(worker, 'detach');
model.manager.removeMessageListener('sdk/worker/event', this.receive);
})
// This method may be called multiple times,
// avoid dispatching `detach` event more than once
if (model.windowID) {
model.windowID = null;
events.off("inner-window-destroyed", model.documentUnload);
model.earlyEvents.length = 0;
emit(worker, 'detach');
}
model.inited = false;
});
isPrivate.define(Worker, ({ tab }) => isPrivate(tab));
// unlod worker, release references
destroy.define(Worker, function(worker, reason) {
/**
* Tells content worker to unload itself and
* removes all the references from itself.
*/
destroy.define(Worker, function (worker, reason) {
detach(worker, reason);
modelFor(worker).inited = true;
})
// Specifying no type or listener removes all listeners
// from target
off(worker);
off(worker.port);
});
// unload Loaders used for creating WorkerChild instances in each process
when(() => ppmm.broadcastAsyncMessage('sdk/loader/unload', { data: ADDON }));
/**
* Events fired by workers
*/
function documentUnload ({ subject, data }) {
let model = modelFor(this);
let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
if (innerWinID != model.windowID) return false;
detach(this);
return true;
}
function pageShow () {
let model = modelFor(this);
model.contentWorker.emitSync('pageshow');
emit(this, 'pageshow');
model.frozen = false;
}
function pageHide () {
let model = modelFor(this);
model.contentWorker.emitSync('pagehide');
emit(this, 'pagehide');
model.frozen = true;
}
/**
* Fired from postMessage and emitEventToContent, or from the earlyMessage
* queue when fired before the content is loaded. Sends arguments to
* contentWorker if able
*/
function processMessage (worker, ...args) {
let model = modelFor(worker) || {};
if (!model.contentWorker)
throw new Error(ERR_DESTROYED);
if (model.frozen)
throw new Error(ERR_FROZEN);
model.contentWorker.emit.apply(null, args);
}
function createModel () {
return {
// List of messages fired before worker is initialized
earlyEvents: [],
// Is worker connected to the content worker sandbox ?
inited: false,
// Is worker being frozen? i.e related document is frozen in bfcache.
// Content script should not be reachable if frozen.
frozen: true,
/**
* Reference to the content side of the worker.
* @type {WorkerGlobalScope}
*/
contentWorker: null,
/**
* Reference to the window that is accessible from
* the content scripts.
* @type {Object}
*/
window: null
};
}
function createPort (worker) {
let port = EventTarget();
port.emit = emitEventToContent.bind(null, worker);
return port;
}
/**
* Emit a custom event to the content script,
* i.e. emit this event on `self.port`
*/
function emitEventToContent (worker, ...eventArgs) {
let model = modelFor(worker);
let args = ['event'].concat(eventArgs);
if (!model.inited) {
model.earlyEvents.push(args);
return;
}
processMessage.apply(null, [worker].concat(args));
}

View File

@ -21,6 +21,7 @@ const { WindowTracker, browserWindowIterator } = require("./deprecated/window-ut
const { isBrowser, getInnerId } = require("./window/utils");
const { Ci, Cc, Cu } = require("chrome");
const { MatchPattern } = require("./util/match-pattern");
const { Worker } = require("./content/worker");
const { EventTarget } = require("./event/target");
const { emit } = require('./event/core');
const { when } = require('./system/unload');

View File

@ -1,143 +0,0 @@
const { Class } = require("../core/heritage");
const { extend } = require("../util/object");
const { MatchPattern } = require("../util/match-pattern");
const readers = require("./readers");
// Context class is required to implement a single `isCurrent(target)` method
// that must return boolean value indicating weather given target matches a
// context or not. Most context implementations below will have an associated
// reader that way context implementation can setup a reader to extract necessary
// information to make decision if target is matching a context.
const Context = Class({
isRequired: false,
isCurrent(target) {
throw Error("Context class must implement isCurrent(target) method");
},
get required() {
Object.defineProperty(this, "required", {
value: Object.assign(Object.create(Object.getPrototypeOf(this)),
this,
{isRequired: true})
});
return this.required;
}
});
Context.required = function(...params) {
return Object.assign(new this(...params), {isRequired: true});
};
exports.Context = Context;
// Next few context implementations use an associated reader to extract info
// from the context target and story it to a private symbol associtaed with
// a context implementation. That way name collisions are avoided while required
// information is still carried along.
const isPage = Symbol("context/page?")
const PageContext = Class({
extends: Context,
read: {[isPage]: new readers.isPage()},
isCurrent: target => target[isPage]
});
exports.Page = PageContext;
const isFrame = Symbol("context/frame?");
const FrameContext = Class({
extends: Context,
read: {[isFrame]: new readers.isFrame()},
isCurrent: target => target[isFrame]
});
exports.Frame = FrameContext;
const selection = Symbol("context/selection")
const SelectionContext = Class({
read: {[selection]: new readers.Selection()},
isCurrent: target => !!target[selection]
});
exports.Selection = SelectionContext;
const link = Symbol("context/link");
const LinkContext = Class({
extends: Context,
read: {[link]: new readers.LinkURL()},
isCurrent: target => !!target[link]
});
exports.Link = LinkContext;
const isEditable = Symbol("context/editable?")
const EditableContext = Class({
extends: Context,
read: {[isEditable]: new readers.isEditable()},
isCurrent: target => target[isEditable]
});
exports.Editable = EditableContext;
const mediaType = Symbol("context/mediaType")
const ImageContext = Class({
extends: Context,
read: {[mediaType]: new readers.MediaType()},
isCurrent: target => target[mediaType] === "image"
});
exports.Image = ImageContext;
const VideoContext = Class({
extends: Context,
read: {[mediaType]: new readers.MediaType()},
isCurrent: target => target[mediaType] === "video"
});
exports.Video = VideoContext;
const AudioContext = Class({
extends: Context,
read: {[mediaType]: new readers.MediaType()},
isCurrent: target => target[mediaType] === "audio"
});
exports.Audio = AudioContext;
const isSelectorMatch = Symbol("context/selector/mathches?")
const SelectorContext = Class({
extends: Context,
initialize(selector) {
this.selector = selector;
// Each instance of selector context will need to store read
// data into different field, so that case with multilpe selector
// contexts won't cause a conflicts.
this[isSelectorMatch] = Symbol(selector);
this.read = {[this[isSelectorMatch]]: new readers.SelectorMatch(selector)};
},
isCurrent(target) {
return target[this[isSelectorMatch]];
}
});
exports.Selector = SelectorContext;
const url = Symbol("context/url");
const URLContext = Class({
extends: Context,
initialize(pattern) {
this.pattern = new MatchPattern(pattern);
},
read: {[url]: new readers.PageURL()},
isCurrent(target) {
return this.pattern.test(target[url]);
}
});
exports.URL = URLContext;
var PredicateContext = Class({
extends: Context,
initialize(isMatch) {
if (typeof(isMatch) !== "function") {
throw TypeError("Predicate context mus be passed a function");
}
this.isMatch = isMatch
},
isCurrent(target) {
return this.isMatch(target);
}
});
exports.Predicate = PredicateContext;

View File

@ -1,382 +0,0 @@
"use strict";
const Contexts = require("./context");
const Readers = require("./readers");
const Component = require("../ui/component");
const { Class } = require("../core/heritage");
const { map, filter, object, reduce, keys, symbols,
pairs, values, each, some, isEvery, count } = require("../util/sequence");
const { loadModule } = require("framescript/manager");
const { Cu, Cc, Ci } = require("chrome");
const prefs = require("sdk/preferences/service");
const globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageListenerManager);
const preferencesService = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService).
getBranch(null);
const readTable = Symbol("context-menu/read-table");
const nameTable = Symbol("context-menu/name-table");
const onContext = Symbol("context-menu/on-context");
const isMatching = Symbol("context-menu/matching-handler?");
exports.onContext = onContext;
exports.readTable = readTable;
exports.nameTable = nameTable;
const propagateOnContext = (item, data) =>
each(child => child[onContext](data), item.state.children);
const isContextMatch = item => !item[isMatching] || item[isMatching]();
// For whatever reason addWeakMessageListener does not seems to work as our
// instance seems to dropped even though it's alive. This is simple workaround
// to avoid dead object excetptions.
const WeakMessageListener = function(receiver, handler="receiveMessage") {
this.receiver = receiver
this.handler = handler
};
WeakMessageListener.prototype = {
constructor: WeakMessageListener,
receiveMessage(message) {
if (Cu.isDeadWrapper(this.receiver)) {
message.target.messageManager.removeMessageListener(message.name, this);
}
else {
this.receiver[this.handler](message);
}
}
};
const OVERFLOW_THRESH = "extensions.addon-sdk.context-menu.overflowThreshold";
const onMessage = Symbol("context-menu/message-listener");
const onPreferceChange = Symbol("context-menu/preference-change");
const ContextMenuExtension = Class({
extends: Component,
initialize: Component,
setup() {
const messageListener = new WeakMessageListener(this, onMessage);
loadModule(globalMessageManager, "framescript/context-menu", true, "onContentFrame");
globalMessageManager.addMessageListener("sdk/context-menu/read", messageListener);
globalMessageManager.addMessageListener("sdk/context-menu/readers?", messageListener);
preferencesService.addObserver(OVERFLOW_THRESH, this, false);
},
observe(_, __, name) {
if (name === OVERFLOW_THRESH) {
const overflowThreshold = prefs.get(OVERFLOW_THRESH, 10);
this[Component.patch]({overflowThreshold});
}
},
[onMessage]({name, data, target}) {
if (name === "sdk/context-menu/read")
this[onContext]({target, data});
if (name === "sdk/context-menu/readers?")
target.messageManager.sendAsyncMessage("sdk/context-menu/readers",
JSON.parse(JSON.stringify(this.state.readers)));
},
[Component.initial](options={}, children) {
const element = options.element || null;
const target = options.target || null;
const readers = Object.create(null);
const users = Object.create(null);
const registry = new WeakSet();
const overflowThreshold = prefs.get(OVERFLOW_THRESH, 10);
return { target, children: [], readers, users, element,
registry, overflowThreshold };
},
[Component.isUpdated](before, after) {
// Update only if target changed, since there is no point in re-rendering
// when children are. Also new items added won't be in sync with a latest
// context target so we should really just render before drawing context
// menu.
return before.target !== after.target;
},
[Component.render]({element, children, overflowThreshold}) {
if (!element) return null;
const items = children.filter(isContextMatch);
const body = items.length === 0 ? items :
items.length < overflowThreshold ? [new Separator(),
...items] :
[{tagName: "menu",
className: "sdk-context-menu-overflow-menu",
label: "Add-ons",
accesskey: "A",
children: [{tagName: "menupopup",
children: items}]}];
return {
element: element,
tagName: "menugroup",
style: "-moz-box-orient: vertical;",
className: "sdk-context-menu-extension",
children: body
}
},
// Adds / remove child to it's own list.
add(item) {
this[Component.patch]({children: this.state.children.concat(item)});
},
remove(item) {
this[Component.patch]({
children: this.state.children.filter(x => x !== item)
});
},
register(item) {
const { users, registry } = this.state;
if (registry.has(item)) return;
registry.add(item);
// Each (ContextHandler) item has a readTable that is a
// map of keys to readers extracting them from the content.
// During the registraction we update intrnal record of unique
// readers and users per reader. Most context will have a reader
// shared across all instances there for map of users per reader
// is stored separately from the reader so that removing reader
// will occur only when no users remain.
const table = item[readTable];
// Context readers store data in private symbols so we need to
// collect both table keys and private symbols.
const names = [...keys(table), ...symbols(table)];
const readers = map(name => table[name], names);
// Create delta for registered readers that will be merged into
// internal readers table.
const added = filter(x => !users[x.id], readers);
const delta = object(...map(x => [x.id, x], added));
const update = reduce((update, reader) => {
const n = update[reader.id] || 0;
update[reader.id] = n + 1;
return update;
}, Object.assign({}, users), readers);
// Patch current state with a changes that registered item caused.
this[Component.patch]({users: update,
readers: Object.assign(this.state.readers, delta)});
if (count(added)) {
globalMessageManager.broadcastAsyncMessage("sdk/context-menu/readers",
JSON.parse(JSON.stringify(delta)));
}
},
unregister(item) {
const { users, registry } = this.state;
if (!registry.has(item)) return;
registry.delete(item);
const table = item[readTable];
const names = [...keys(table), ...symbols(table)];
const readers = map(name => table[name], names);
const update = reduce((update, reader) => {
update[reader.id] = update[reader.id] - 1;
return update;
}, Object.assign({}, users), readers);
const removed = filter(id => !update[id], keys(update));
const delta = object(...map(x => [x, null], removed));
this[Component.patch]({users: update,
readers: Object.assign(this.state.readers, delta)});
if (count(removed)) {
globalMessageManager.broadcastAsyncMessage("sdk/context-menu/readers",
JSON.parse(JSON.stringify(delta)));
}
},
[onContext]({data, target}) {
propagateOnContext(this, data);
const document = target.ownerDocument;
const element = document.getElementById("contentAreaContextMenu");
this[Component.patch]({target: data, element: element});
}
});this,
exports.ContextMenuExtension = ContextMenuExtension;
// Takes an item options and
const makeReadTable = ({context, read}) => {
// Result of this function is a tuple of all readers &
// name, reader id pairs.
// Filter down to contexts that have a reader associated.
const contexts = filter(context => context.read, context);
// Merge all contexts read maps to a single hash, note that there should be
// no name collisions as context implementations expect to use private
// symbols for storing it's read data.
return Object.assign({}, ...map(({read}) => read, contexts), read);
}
const readTarget = (nameTable, data) =>
object(...map(([name, id]) => [name, data[id]], nameTable))
const ContextHandler = Class({
extends: Component,
initialize: Component,
get context() {
return this.state.options.context;
},
get read() {
return this.state.options.read;
},
[Component.initial](options) {
return {
table: makeReadTable(options),
requiredContext: filter(context => context.isRequired, options.context),
optionalContext: filter(context => !context.isRequired, options.context)
}
},
[isMatching]() {
const {target, requiredContext, optionalContext} = this.state;
return isEvery(context => context.isCurrent(target), requiredContext) &&
(count(optionalContext) === 0 ||
some(context => context.isCurrent(target), optionalContext));
},
setup() {
const table = makeReadTable(this.state.options);
this[readTable] = table;
this[nameTable] = [...map(symbol => [symbol, table[symbol].id], symbols(table)),
...map(name => [name, table[name].id], keys(table))];
contextMenu.register(this);
each(child => contextMenu.remove(child), this.state.children);
contextMenu.add(this);
},
dispose() {
contextMenu.remove(this);
each(child => contextMenu.unregister(child), this.state.children);
contextMenu.unregister(this);
},
// Internal `Symbol("onContext")` method is invoked when "contextmenu" event
// occurs in content process. Context handles with children delegate to each
// child and patch it's internal state to reflect new contextmenu target.
[onContext](data) {
propagateOnContext(this, data);
this[Component.patch]({target: readTarget(this[nameTable], data)});
}
});
const isContextHandler = item => item instanceof ContextHandler;
exports.ContextHandler = ContextHandler;
const Menu = Class({
extends: ContextHandler,
[isMatching]() {
return ContextHandler.prototype[isMatching].call(this) &&
this.state.children.filter(isContextHandler)
.some(isContextMatch);
},
[Component.render]({children, options}) {
const items = children.filter(isContextMatch);
return {tagName: "menu",
className: "sdk-context-menu menu-iconic",
label: options.label,
accesskey: options.accesskey,
image: options.icon,
children: [{tagName: "menupopup",
children: items}]};
}
});
exports.Menu = Menu;
const onCommand = Symbol("context-menu/item/onCommand");
const Item = Class({
extends: ContextHandler,
get onClick() {
return this.state.options.onClick;
},
[Component.render]({options}) {
const {label, icon, accesskey} = options;
return {tagName: "menuitem",
className: "sdk-context-menu-item menuitem-iconic",
label,
accesskey,
image: icon,
oncommand: this};
},
handleEvent(event) {
if (this.onClick)
this.onClick(this.state.target);
}
});
exports.Item = Item;
var Separator = Class({
extends: Component,
initialize: Component,
[Component.render]() {
return {tagName: "menuseparator",
className: "sdk-context-menu-separator"}
},
[onContext]() {
}
});
exports.Separator = Separator;
exports.Contexts = Contexts;
exports.Readers = Readers;
const createElement = (vnode, {document}) => {
const node = vnode.namespace ?
document.createElementNS(vnode.namespace, vnode.tagName) :
document.createElement(vnode.tagName);
node.setAttribute("data-component-path", vnode[Component.path]);
each(([key, value]) => {
if (key === "tagName") {
return;
}
if (key === "children") {
return;
}
if (key.startsWith("on")) {
node.addEventListener(key.substr(2), value)
return;
}
if (typeof(value) !== "object" &&
typeof(value) !== "function" &&
value !== void(0) &&
value !== null)
{
if (key === "className") {
node[key] = value;
}
else {
node.setAttribute(key, value);
}
return;
}
}, pairs(vnode));
each(child => node.appendChild(createElement(child, {document})), vnode.children);
return node;
};
const htmlWriter = tree => {
if (tree !== null) {
const root = tree.element;
const node = createElement(tree, {document: root.ownerDocument});
const before = root.querySelector("[data-component-path='/']");
if (before) {
root.replaceChild(node, before);
} else {
root.appendChild(node);
}
}
};
const contextMenu = ContextMenuExtension();
exports.contextMenu = contextMenu;
Component.mount(contextMenu, htmlWriter);

View File

@ -1,109 +0,0 @@
const { Class } = require("../core/heritage");
const { extend } = require("../util/object");
const { memoize, method, identity } = require("../lang/functional");
const serializeCategory = ({type}) => ({ category: `reader/${type}()` });
const Reader = Class({
initialize() {
this.id = `reader/${this.type}()`
},
toJSON() {
return serializeCategory(this);
}
});
const MediaTypeReader = Class({ extends: Reader, type: "MediaType" });
exports.MediaType = MediaTypeReader;
const LinkURLReader = Class({ extends: Reader, type: "LinkURL" });
exports.LinkURL = LinkURLReader;
const SelectionReader = Class({ extends: Reader, type: "Selection" });
exports.Selection = SelectionReader;
const isPageReader = Class({ extends: Reader, type: "isPage" });
exports.isPage = isPageReader;
const isFrameReader = Class({ extends: Reader, type: "isFrame" });
exports.isFrame = isFrameReader;
const isEditable = Class({ extends: Reader, type: "isEditable"});
exports.isEditable = isEditable;
const ParameterizedReader = Class({
extends: Reader,
readParameter: function(value) {
return value;
},
toJSON: function() {
var json = serializeCategory(this);
json[this.parameter] = this[this.parameter];
return json;
},
initialize(...params) {
if (params.length) {
this[this.parameter] = this.readParameter(...params);
}
this.id = `reader/${this.type}(${JSON.stringify(this[this.parameter])})`;
}
});
exports.ParameterizedReader = ParameterizedReader;
const QueryReader = Class({
extends: ParameterizedReader,
type: "Query",
parameter: "path"
});
exports.Query = QueryReader;
const AttributeReader = Class({
extends: ParameterizedReader,
type: "Attribute",
parameter: "name"
});
exports.Attribute = AttributeReader;
const SrcURLReader = Class({
extends: AttributeReader,
name: "src",
});
exports.SrcURL = SrcURLReader;
const PageURLReader = Class({
extends: QueryReader,
path: "ownerDocument.URL",
});
exports.PageURL = PageURLReader;
const SelectorMatchReader = Class({
extends: ParameterizedReader,
type: "SelectorMatch",
parameter: "selector"
});
exports.SelectorMatch = SelectorMatchReader;
const extractors = new WeakMap();
extractors.id = 0;
var Extractor = Class({
extends: ParameterizedReader,
type: "Extractor",
parameter: "source",
initialize: function(f) {
this[this.parameter] = String(f);
if (!extractors.has(f)) {
extractors.id = extractors.id + 1;
extractors.set(f, extractors.id);
}
this.id = `reader/${this.type}.for(${extractors.get(f)})`
}
});
exports.Extractor = Extractor;

View File

@ -1,29 +0,0 @@
"use strict";
const shared = require("toolkit/require");
const { Item, Separator, Menu, Contexts, Readers } = shared.require("sdk/context-menu/core");
const { setupDisposable, disposeDisposable, Disposable } = require("sdk/core/disposable")
const { Class } = require("sdk/core/heritage")
const makeDisposable = Type => Class({
extends: Type,
implements: [Disposable],
initialize: Type.prototype.initialize,
setup(...params) {
Type.prototype.setup.call(this, ...params);
setupDisposable(this);
},
dispose(...params) {
disposeDisposable(this);
Type.prototype.dispose.call(this, ...params);
}
});
exports.Separator = Separator;
exports.Contexts = Contexts;
exports.Readers = Readers;
// Subclass Item & Menu shared classes so their items
// will be unloaded when add-on is unloaded.
exports.Item = makeDisposable(Item);
exports.Menu = makeDisposable(Menu);

View File

@ -52,13 +52,11 @@ setup.define(Object, (object, ...args) => object.setup(...args));
const setupDisposable = disposable => {
subscribe(disposable, addonUnloadTopic, isWeak(disposable));
};
exports.setupDisposable = setupDisposable;
// Tears down disposable instance.
const disposeDisposable = disposable => {
unsubscribe(disposable, addonUnloadTopic);
};
exports.disposeDisposable = disposeDisposable;
// Base type that takes care of disposing it's instances on add-on unload.
// Also makes sure to remove unload listener if it's already being disposed.
@ -131,3 +129,4 @@ uninstall.define(Disposable, dispose);
// increase shutdown time. Although specefic components may choose
// to implement shutdown handler that does something better.
shutdown.define(Disposable, disposable => {});

View File

@ -1,6 +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";
module.metadata = {

View File

@ -1,6 +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";
module.metadata = {

View File

@ -4,7 +4,7 @@
"use strict";
const { Class } = require("../../core/heritage");
const { Trait } = require("../light-traits");
const { removeListener, on } = require("../../dom/events");
/**
@ -15,7 +15,7 @@ const { removeListener, on } = require("../../dom/events");
* `supportedEventsTypes` and function for handling all those events as
* `handleEvent` property.
*/
exports.DOMEventAssembler = Class({
exports.DOMEventAssembler = Trait({
/**
* Function that is supposed to handle all the supported events (that are
* present in the `supportedEventsTypes`) from all the observed
@ -23,16 +23,12 @@ exports.DOMEventAssembler = Class({
* @param {Event} event
* Event being dispatched.
*/
handleEvent() {
throw new TypeError("Instance of DOMEventAssembler must implement `handleEvent` method");
},
handleEvent: Trait.required,
/**
* Array of supported event names.
* @type {String[]}
*/
get supportedEventsTypes() {
throw new TypeError("Instance of DOMEventAssembler must implement `handleEvent` field");
},
supportedEventsTypes: Trait.required,
/**
* Adds `eventTarget` to the list of observed `eventTarget`s. Listeners for
* supported events will be registered on the given `eventTarget`.

View File

@ -1,297 +0,0 @@
/* 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/. */
/**
*
* `deprecated/sync-worker` was previously `content/worker`, that was
* incompatible with e10s. we are in the process of switching to the new
* asynchronous `Worker`, which behaves slightly differently in some edge
* cases, so we are keeping this one around for a short period.
* try to switch to the new one as soon as possible..
*
*/
"use strict";
module.metadata = {
"stability": "unstable"
};
const { Class } = require('../core/heritage');
const { EventTarget } = require('../event/target');
const { on, off, emit, setListeners } = require('../event/core');
const {
attach, detach, destroy
} = require('../content/utils');
const { method } = require('../lang/functional');
const { Ci, Cu, Cc } = require('chrome');
const unload = require('../system/unload');
const events = require('../system/events');
const { getInnerId } = require("../window/utils");
const { WorkerSandbox } = require('../content/sandbox');
const { getTabForWindow } = require('../tabs/helpers');
const { isPrivate } = require('../private-browsing/utils');
// A weak map of workers to hold private attributes that
// should not be exposed
const workers = new WeakMap();
let modelFor = (worker) => workers.get(worker);
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.";
const ERR_FROZEN = "The page is currently hidden and can no longer be used " +
"until it is visible again.";
/**
* Message-passing facility for communication between code running
* in the content and add-on process.
* @see https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/content_worker
*/
const Worker = Class({
implements: [EventTarget],
initialize: function WorkerConstructor (options) {
// Save model in weak map to not expose properties
let model = createModel();
workers.set(this, model);
options = options || {};
if ('contentScriptFile' in options)
this.contentScriptFile = options.contentScriptFile;
if ('contentScriptOptions' in options)
this.contentScriptOptions = options.contentScriptOptions;
if ('contentScript' in options)
this.contentScript = options.contentScript;
if ('injectInDocument' in options)
this.injectInDocument = !!options.injectInDocument;
setListeners(this, options);
unload.ensure(this, "destroy");
// Ensure that worker.port is initialized for contentWorker to be able
// to send events during worker initialization.
this.port = createPort(this);
model.documentUnload = documentUnload.bind(this);
model.pageShow = pageShow.bind(this);
model.pageHide = pageHide.bind(this);
if ('window' in options)
attach(this, options.window);
},
/**
* Sends a message to the worker's global scope. Method takes single
* argument, which represents data to be sent to the worker. The data may
* be any primitive type value or `JSON`. Call of this method asynchronously
* emits `message` event with data value in the global scope of this
* symbiont.
*
* `message` event listeners can be set either by calling
* `self.on` with a first argument string `"message"` or by
* implementing `onMessage` function in the global scope of this worker.
* @param {Number|String|JSON} data
*/
postMessage: function (...data) {
let model = modelFor(this);
let args = ['message'].concat(data);
if (!model.inited) {
model.earlyEvents.push(args);
return;
}
processMessage.apply(null, [this].concat(args));
},
get url () {
let model = modelFor(this);
// model.window will be null after detach
return model.window ? model.window.document.location.href : null;
},
get contentURL () {
let model = modelFor(this);
return model.window ? model.window.document.URL : null;
},
get tab () {
let model = modelFor(this);
// model.window will be null after detach
if (model.window)
return getTabForWindow(model.window);
return null;
},
// Implemented to provide some of the previous features of exposing sandbox
// so that Worker can be extended
getSandbox: function () {
return modelFor(this).contentWorker;
},
toString: function () { return '[object Worker]'; },
attach: method(attach),
detach: method(detach),
destroy: method(destroy)
});
exports.Worker = Worker;
attach.define(Worker, function (worker, window) {
let model = modelFor(worker);
model.window = window;
// Track document unload to destroy this worker.
// We can't watch for unload event on page's window object as it
// prevents bfcache from working:
// https://developer.mozilla.org/En/Working_with_BFCache
model.windowID = getInnerId(model.window);
events.on("inner-window-destroyed", model.documentUnload);
// will set model.contentWorker pointing to the private API:
model.contentWorker = WorkerSandbox(worker, model.window);
// Listen to pagehide event in order to freeze the content script
// while the document is frozen in bfcache:
model.window.addEventListener("pageshow", model.pageShow, true);
model.window.addEventListener("pagehide", model.pageHide, true);
// Mainly enable worker.port.emit to send event to the content worker
model.inited = true;
model.frozen = false;
// Fire off `attach` event
emit(worker, 'attach', window);
// Process all events and messages that were fired before the
// worker was initialized.
model.earlyEvents.forEach(args => processMessage.apply(null, [worker].concat(args)));
});
/**
* Remove all internal references to the attached document
* Tells _port to unload itself and removes all the references from itself.
*/
detach.define(Worker, function (worker, reason) {
let model = modelFor(worker);
// maybe unloaded before content side is created
if (model.contentWorker) {
model.contentWorker.destroy(reason);
}
model.contentWorker = null;
if (model.window) {
model.window.removeEventListener("pageshow", model.pageShow, true);
model.window.removeEventListener("pagehide", model.pageHide, true);
}
model.window = null;
// This method may be called multiple times,
// avoid dispatching `detach` event more than once
if (model.windowID) {
model.windowID = null;
events.off("inner-window-destroyed", model.documentUnload);
model.earlyEvents.length = 0;
emit(worker, 'detach');
}
model.inited = false;
});
isPrivate.define(Worker, ({ tab }) => isPrivate(tab));
/**
* Tells content worker to unload itself and
* removes all the references from itself.
*/
destroy.define(Worker, function (worker, reason) {
detach(worker, reason);
modelFor(worker).inited = true;
// Specifying no type or listener removes all listeners
// from target
off(worker);
off(worker.port);
});
/**
* Events fired by workers
*/
function documentUnload ({ subject, data }) {
let model = modelFor(this);
let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
if (innerWinID != model.windowID) return false;
detach(this);
return true;
}
function pageShow () {
let model = modelFor(this);
model.contentWorker.emitSync('pageshow');
emit(this, 'pageshow');
model.frozen = false;
}
function pageHide () {
let model = modelFor(this);
model.contentWorker.emitSync('pagehide');
emit(this, 'pagehide');
model.frozen = true;
}
/**
* Fired from postMessage and emitEventToContent, or from the earlyMessage
* queue when fired before the content is loaded. Sends arguments to
* contentWorker if able
*/
function processMessage (worker, ...args) {
let model = modelFor(worker) || {};
if (!model.contentWorker)
throw new Error(ERR_DESTROYED);
if (model.frozen)
throw new Error(ERR_FROZEN);
model.contentWorker.emit.apply(null, args);
}
function createModel () {
return {
// List of messages fired before worker is initialized
earlyEvents: [],
// Is worker connected to the content worker sandbox ?
inited: false,
// Is worker being frozen? i.e related document is frozen in bfcache.
// Content script should not be reachable if frozen.
frozen: true,
/**
* Reference to the content side of the worker.
* @type {WorkerGlobalScope}
*/
contentWorker: null,
/**
* Reference to the window that is accessible from
* the content scripts.
* @type {Object}
*/
window: null
};
}
function createPort (worker) {
let port = EventTarget();
port.emit = emitEventToContent.bind(null, worker);
return port;
}
/**
* Emit a custom event to the content script,
* i.e. emit this event on `self.port`
*/
function emitEventToContent (worker, ...eventArgs) {
let model = modelFor(worker);
let args = ['event'].concat(eventArgs);
if (!model.inited) {
model.earlyEvents.push(args);
return;
}
processMessage.apply(null, [worker].concat(args));
}

View File

@ -10,7 +10,7 @@ module.metadata = {
const memory = require("./memory");
const timer = require("../timers");
const cfxArgs = require("../test/options");
const { getTabs, closeTab, getURI, getTabId, getSelectedTab } = require("../tabs/utils");
const { getTabs, closeTab, getURI } = require("../tabs/utils");
const { windows, isBrowser, getMostRecentBrowserWindow } = require("../window/utils");
const { defer, all, Debugging: PromiseDebugging, resolve } = require("../core/promise");
const { getInnerId } = require("../window/utils");
@ -35,16 +35,10 @@ const findAndRunTests = function findAndRunTests(options) {
exports.findAndRunTests = findAndRunTests;
let runnerWindows = new WeakMap();
let runnerTabs = new WeakMap();
const TestRunner = function TestRunner(options) {
options = options || {};
// remember the id's for the open window and tab
let window = getMostRecentBrowserWindow();
runnerWindows.set(this, getInnerId(window));
runnerTabs.set(this, getTabId(getSelectedTab(window)));
runnerWindows.set(this, getInnerId(getMostRecentBrowserWindow()));
this.fs = options.fs;
this.console = options.console || console;
memory.track(this);
@ -324,26 +318,15 @@ TestRunner.prototype = {
return all(winPromises).then(() => {
let browserWins = wins.filter(isBrowser);
let tabs = browserWins.reduce((tabs, window) => tabs.concat(getTabs(window)), []);
let newTabID = getTabId(getSelectedTab(wins[0]));
let oldTabID = runnerTabs.get(this);
let hasMoreTabsOpen = browserWins.length && tabs.length != 1;
let failure = false;
if (wins.length != 1 || getInnerId(wins[0]) !== runnerWindows.get(this)) {
failure = true;
if (wins.length != 1 || getInnerId(wins[0]) !== runnerWindows.get(this))
this.fail("Should not be any unexpected windows open");
}
else if (hasMoreTabsOpen) {
failure = true;
this.fail("Should not be any unexpected tabs open");
}
else if (oldTabID != newTabID) {
failure = true;
runnerTabs.set(this, newTabID);
this.fail("Should not be any new tabs left open, old id: " + oldTabID + " new id: " + newTabID);
}
if (failure) {
let hasMoreTabsOpen = browserWins.length && tabs.length != 1;
if (hasMoreTabsOpen)
this.fail("Should not be any unexpected tabs open");
if (hasMoreTabsOpen || wins.length != 1) {
console.log("Windows open:");
for (let win of wins) {
if (isBrowser(win)) {
@ -373,7 +356,7 @@ TestRunner.prototype = {
timer.setTimeout(_ => onDone(this));
}
}).
catch(console.exception);
catch(e => console.exception(e));
},
// Set of assertion functions to wait for an assertion to become true

View File

@ -192,10 +192,8 @@ const InputStream = Class({
},
resume: function resume() {
this.paused = false;
if (nsIInputStreamPump(this).isPending()) {
nsIInputStreamPump(this).resume();
emit(this, "resume");
}
nsIInputStreamPump(this).resume();
emit(this, "resume");
},
close: function close() {
this.readable = false;

View File

@ -8,9 +8,8 @@ module.metadata = {
"stability": "unstable"
};
const { Class } = require("../core/heritage");
const { EventTarget } = require("../event/target");
const { emit } = require("../event/core");
const { Trait } = require("../deprecated/light-traits");
const { EventEmitterTrait: EventEmitter } = require("../deprecated/events");
const { DOMEventAssembler } = require("../deprecated/events/assembler");
const { browserWindowIterator } = require('../deprecated/window-utils');
const { isBrowser } = require('../window/utils');
@ -18,26 +17,12 @@ const { observer: windowObserver } = require("../windows/observer");
// Event emitter objects used to register listeners and emit events on them
// when they occur.
const Observer = Class({
implements: [DOMEventAssembler, EventTarget],
initialize() {
// Adding each opened window to a list of observed windows.
windowObserver.on("open", window => {
if (isBrowser(window))
this.observe(window);
});
// Removing each closed window form the list of observed windows.
windowObserver.on("close", window => {
if (isBrowser(window))
this.ignore(window);
});
// Making observer aware of already opened windows.
for (let window of browserWindowIterator()) {
this.observe(window);
}
},
const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
/**
* Method is implemented by `EventEmitter` and is used just for emitting
* events on registered listeners.
*/
_emit: Trait.required,
/**
* Events that are supported and emitted by the module.
*/
@ -49,9 +34,24 @@ const Observer = Class({
* @param {Event} event
* Keyboard event being emitted.
*/
handleEvent(event) {
emit(this, event.type, event, event.target.ownerDocument.defaultView);
handleEvent: function handleEvent(event) {
this._emit(event.type, event, event.target.ownerDocument.defaultView);
}
});
exports.observer = new Observer();
// Adding each opened window to a list of observed windows.
windowObserver.on("open", function onOpen(window) {
if (isBrowser(window))
observer.observe(window);
});
// Removing each closed window form the list of observed windows.
windowObserver.on("close", function onClose(window) {
if (isBrowser(window))
observer.ignore(window);
});
// Making observer aware of already opened windows.
for (let window of browserWindowIterator())
observer.observe(window);
exports.observer = observer;

View File

@ -50,8 +50,7 @@ function getPreferedLocales(caseSensitve) {
addLocale(browserUiLocale);
// Third priority is the list of locales used for web content
let contentLocales = prefs.getLocalized(PREF_ACCEPT_LANGUAGES, "") ||
prefs.get(PREF_ACCEPT_LANGUAGES, "");
let contentLocales = prefs.get(PREF_ACCEPT_LANGUAGES, "");
if (contentLocales) {
// This list is a string of locales seperated by commas.
// There is spaces after commas, so strip each item

View File

@ -107,18 +107,6 @@ function isObject(value) {
}
exports.isObject = isObject;
/**
* Detect whether a value is a generator.
*
* @param aValue
* The value to identify.
* @return A boolean indicating whether the value is a generator.
*/
function isGenerator(aValue) {
return !!(aValue && aValue.isGenerator && aValue.isGenerator());
}
exports.isGenerator = isGenerator;
/**
* Returns true if `value` is an Array.
* @examples

View File

@ -1,7 +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";
'use strict';
module.metadata = {
"stability": "unstable"
@ -14,6 +14,7 @@ module.metadata = {
require('chrome') // Otherwise CFX will complain about Components
require('toolkit/loader') // Otherwise CFX will stip out loader.js
require('sdk/addon/runner') // Otherwise CFX will stip out addon/runner.js
require('sdk/system/xul-app') // Otherwise CFX will stip out sdk/system/xul-app
*/
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
@ -22,17 +23,65 @@ const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
const loaderURI = module.uri.replace("sdk/loader/cuddlefish.js",
"toolkit/loader.js");
const xulappURI = module.uri.replace("loader/cuddlefish.js",
"system/xul-app.jsm");
"system/xul-app.js");
// We need to keep a reference to the sandbox in order to unload it in
// bootstrap.js
const loaderSandbox = loadSandbox(loaderURI);
const loaderModule = loaderSandbox.exports;
const { incompatibility } = Cu.import(xulappURI, {}).XulApp;
const xulappSandbox = loadSandbox(xulappURI);
const xulappModule = xulappSandbox.exports;
const { override, load } = loaderModule;
/**
* Ensure the current application satisfied the requirements specified in the
* module given. If not, an exception related to the incompatibility is
* returned; `null` otherwise.
*
* @param {Object} module
* The module to check
* @returns {Error}
*/
function incompatibility(module) {
let { metadata, id } = module;
// if metadata or engines are not specified we assume compatibility is not
// an issue.
if (!metadata || !("engines" in metadata))
return null;
let { engines } = metadata;
if (engines === null || typeof(engines) !== "object")
return new Error("Malformed engines' property in metadata");
let applications = Object.keys(engines);
let versionRange;
applications.forEach(function(name) {
if (xulappModule.is(name)) {
versionRange = engines[name];
// Continue iteration. We want to ensure the module doesn't
// contain a typo in the applications' name or some unknown
// application - `is` function throws an exception in that case.
}
});
if (typeof(versionRange) === "string") {
if (xulappModule.satisfiesVersion(versionRange))
return null;
return new Error("Unsupported Application version: The module " + id +
" currently supports only version " + versionRange + " of " +
xulappModule.name + ".");
}
return new Error("Unsupported Application: The module " + id +
" currently supports only " + applications.join(", ") + ".")
}
function CuddlefishLoader(options) {
let { manifest } = options;
@ -41,7 +90,8 @@ function CuddlefishLoader(options) {
// cache to avoid subsequent loads via `require`.
modules: override({
'toolkit/loader': loaderModule,
'sdk/loader/cuddlefish': exports
'sdk/loader/cuddlefish': exports,
'sdk/system/xul-app': xulappModule
}, options.modules),
resolve: function resolve(id, requirer) {
let entry = requirer && requirer in manifest && manifest[requirer];

View File

@ -14,7 +14,7 @@ const { getAttachEventType, WorkerHost } = require('./content/utils');
const { Class } = require('./core/heritage');
const { Disposable } = require('./core/disposable');
const { WeakReference } = require('./core/reference');
const { Worker } = require('./content/worker');
const { Worker } = require('./content/worker-parent');
const { EventTarget } = require('./event/target');
const { on, emit, once, setListeners } = require('./event/core');
const { on: domOn, removeListener: domOff } = require('./dom/events');
@ -189,10 +189,6 @@ function applyOnExistingDocuments (mod) {
getTabs().forEach(tab => {
// Fake a newly created document
let window = getTabContentWindow(tab);
// on startup with e10s, contentWindow might not exist yet,
// in which case we will get notified by "document-element-inserted".
if (!window || !window.frames)
return;
let uri = getTabURI(tab);
if (has(mod.attachTo, "top") && modMatchesURI(mod, uri))
onContent(mod, window);
@ -220,7 +216,7 @@ function createWorker (mod, window) {
// page-mod's "attach" event needs a worker
if (event === 'attach')
emit(mod, event, worker)
else
else
emit(mod, event, ...args);
})
once(worker, 'detach', () => worker.destroy());

View File

@ -28,9 +28,9 @@ const { Rules } = require('./util/rules');
const { merge } = require('./util/object');
const { data } = require('./self');
const views = new WeakMap();
const workers = new WeakMap();
const pages = new WeakMap();
const views = WeakMap();
const workers = WeakMap();
const pages = WeakMap();
const readyEventNames = [
'DOMContentLoaded',
@ -136,7 +136,7 @@ const Page = Class({
// page-worker doesn't have a model like other APIs, so to be consitent
// with the behavior "what you set is what you get", we need to store
// the original `contentURL` given.
// the original `contentURL` given.
// Even if XUL elements doesn't support `dataset`, properties, to
// indicate that is a custom attribute the syntax "data-*" is used.
view.setAttribute('data-src', contentURL);

View File

@ -15,10 +15,12 @@ module.metadata = {
const { Ci } = require("chrome");
const { setTimeout } = require('./timers');
const { isPrivateBrowsingSupported } = require('./self');
const { isWindowPBSupported } = require('./private-browsing/utils');
const { Class } = require("./core/heritage");
const { merge } = require("./util/object");
const { WorkerHost } = require("./content/utils");
const { Worker } = require("./deprecated/sync-worker");
const { Worker } = require("./content/worker");
const { Disposable } = require("./core/disposable");
const { WeakReference } = require('./core/reference');
const { contract: loaderContract } = require("./content/loader");
@ -153,7 +155,7 @@ const Panel = Class({
// Load panel content.
domPanel.setURL(view, model.contentURL);
// Allow context menu
domPanel.allowContextMenu(view, model.contextMenu);
@ -193,7 +195,7 @@ const Panel = Class({
/* Public API: Panel.position */
get position() modelFor(this).position,
/* Public API: Panel.contextMenu */
get contextMenu() modelFor(this).contextMenu,
set contextMenu(allow) {
@ -201,7 +203,7 @@ const Panel = Class({
model.contextMenu = panelContract({ contextMenu: allow }).contextMenu;
domPanel.allowContextMenu(viewFor(this), model.contextMenu);
},
get contentURL() modelFor(this).contentURL,
set contentURL(value) {
let model = modelFor(this);

View File

@ -213,7 +213,7 @@ function show(panel, options, anchor) {
// Prevent the panel from getting focus when showing up
// if focus is set to false
panel.setAttribute("noautofocus", !options.focus);
let window = anchor && getOwnerBrowserWindow(anchor);
let { document } = window ? window : getMostRecentBrowserWindow();
attach(panel, document);
@ -286,7 +286,8 @@ function make(document) {
events.emit(type, { subject: panel });
}
function onContentChange({subject: document, type}) {
function onContentChange({subject, type}) {
let document = subject;
if (document === getContentDocument(panel) && document.defaultView)
events.emit(type, { subject: panel });
}
@ -410,9 +411,9 @@ function setURL(panel, url) {
exports.setURL = setURL;
function allowContextMenu(panel, allow) {
if (allow) {
if(allow) {
panel.setAttribute("context", "contentAreaContextMenu");
}
}
else {
panel.removeAttribute("context");
}

View File

@ -0,0 +1,64 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
// The panel module currently supports only Firefox.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps
module.metadata = {
'stability': 'unstable',
'engines': {
'Firefox': '*'
}
};
const { getMostRecentBrowserWindow, windows: getWindows } = require('../window/utils');
const { ignoreWindow } = require('../private-browsing/utils');
const { isPrivateBrowsingSupported } = require('../self');
function getWindow(anchor) {
let window;
let windows = getWindows("navigator:browser", {
includePrivate: isPrivateBrowsingSupported
});
if (anchor) {
let anchorWindow = anchor.ownerDocument.defaultView.top;
let anchorDocument = anchorWindow.document;
// loop thru supported windows
for (let enumWindow of windows) {
// Check if the anchor is in this browser window.
if (enumWindow == anchorWindow) {
window = anchorWindow;
break;
}
// Check if the anchor is in a browser tab in this browser window.
try {
let browser = enumWindow.gBrowser.getBrowserForDocument(anchorDocument);
if (browser) {
window = enumWindow;
break;
}
}
catch (e) {
}
// Look in other subdocuments (sidebar, etc.)?
}
}
// If we didn't find the anchor's window (or we have no anchor),
// return the most recent browser window.
if (!window)
window = getMostRecentBrowserWindow();
// if the window is not supported, then it should be ignored
if (ignoreWindow(window)) {
return null;
}
return window;
}
exports.getWindow = getWindow;

View File

@ -7,8 +7,7 @@
module.metadata = {
"stability": "unstable",
"engines": {
"Firefox": "*",
"SeaMonkey": "*"
"Firefox": "*"
}
};

View File

@ -7,8 +7,7 @@
module.metadata = {
'stability': 'experimental',
'engines': {
'Firefox': '*',
"SeaMonkey": '*'
'Firefox': '*'
}
};

View File

@ -7,8 +7,7 @@
module.metadata = {
"stability": "unstable",
"engines": {
"Firefox": "*",
"SeaMonkey": "*"
"Firefox": "*"
}
};

View File

@ -7,8 +7,7 @@
module.metadata = {
"stability": "unstable",
"engines": {
"Firefox": "*",
"SeaMonkey": "*"
"Firefox": "*"
}
};

View File

@ -7,8 +7,7 @@
module.metadata = {
"stability": "experimental",
"engines": {
"Firefox": "*",
"SeaMonkey": "*"
"Firefox": "*"
}
};

View File

@ -7,8 +7,7 @@
module.metadata = {
"stability": "experimental",
"engines": {
"Firefox": "*",
"SeaMonkey": "*"
"Firefox": "*"
}
};

View File

@ -7,8 +7,7 @@
module.metadata = {
"stability": "experimental",
"engines": {
"Firefox": "*",
"SeaMonkey": "*"
"Firefox": "*"
}
};

View File

@ -7,8 +7,7 @@
module.metadata = {
"stability": "experimental",
"engines": {
"Firefox": "*",
"SeaMonkey": "*"
"Firefox": "*"
}
};

View File

@ -123,8 +123,7 @@ function injectOptions({ preferences, preferencesBranch, document, parent, id })
setting.setAttribute('pref', 'extensions.' + preferencesBranch + '.' + name);
setting.setAttribute('type', type);
setting.setAttribute('title', title);
if (description)
setting.setAttribute('desc', description);
setting.setAttribute('desc', description);
if (type === 'file' || type === 'directory') {
setting.setAttribute('fullpath', 'true');

View File

@ -21,9 +21,6 @@ const prefService = Cc["@mozilla.org/preferences-service;1"].
const prefSvc = prefService.getBranch(null);
const defaultBranch = prefService.getDefaultBranch(null);
const { Preferences } = require("resource://gre/modules/Preferences.jsm");
const prefs = new Preferences({});
function Branch(branchName) {
function getPrefKeys() {
return keys(branchName).map(function(key) {
@ -45,9 +42,6 @@ function Branch(branchName) {
has: function hasPrefKey(pref) {
return has(branchName + pref)
},
hasOwn: function(pref) {
return has(branchName + pref)
},
getPropertyDescriptor: function(name) {
return {
value: get(branchName + name)
@ -59,27 +53,74 @@ function Branch(branchName) {
}
function get(name, defaultValue) {
return prefs.get(name, defaultValue);
switch (prefSvc.getPrefType(name)) {
case Ci.nsIPrefBranch.PREF_STRING:
return prefSvc.getComplexValue(name, Ci.nsISupportsString).data;
case Ci.nsIPrefBranch.PREF_INT:
return prefSvc.getIntPref(name);
case Ci.nsIPrefBranch.PREF_BOOL:
return prefSvc.getBoolPref(name);
case Ci.nsIPrefBranch.PREF_INVALID:
return defaultValue;
default:
// This should never happen.
throw new Error("Error getting pref " + name +
"; its value's type is " +
prefSvc.getPrefType(name) +
", which I don't know " +
"how to handle.");
}
}
exports.get = get;
function set(name, value) {
var prefType;
if (typeof value != "undefined" && value != null)
prefType = value.constructor.name;
switch (prefType) {
case "String":
{
var string = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
string.data = value;
prefSvc.setComplexValue(name, Ci.nsISupportsString, string);
}
break;
case "Number":
// We throw if the number is outside the range or not an integer, since
// the result will not be what the consumer wanted to store.
if (value > MAX_INT || value < MIN_INT)
throw new Error("you cannot set the " + name +
" pref to the number " + value +
", as number pref values must be in the signed " +
"32-bit integer range -(2^31) to 2^31-1. " +
"To store numbers outside that range, store " +
"them as strings.");
if (value % 1 != 0)
throw new Error("cannot store non-integer number: " + value);
}
prefSvc.setIntPref(name, value);
break;
prefs.set(name, value);
case "Boolean":
prefSvc.setBoolPref(name, value);
break;
default:
throw new Error("can't set pref " + name + " to value '" + value +
"'; it isn't a string, integer, or boolean");
}
}
exports.set = set;
const has = prefs.has.bind(prefs)
function has(name) {
return (prefSvc.getPrefType(name) != Ci.nsIPrefBranch.PREF_INVALID);
}
exports.has = has;
function keys(root) {
@ -87,7 +128,9 @@ function keys(root) {
}
exports.keys = keys;
const isSet = prefs.isSet.bind(prefs);
function isSet(name) {
return (has(name) && prefSvc.prefHasUserValue(name));
}
exports.isSet = isSet;
function reset(name) {

View File

@ -8,17 +8,20 @@ module.metadata = {
};
const { openTab, getBrowserForTab, getTabId } = require("sdk/tabs/utils");
const { defer, all } = require("sdk/core/promise");
const { on, off } = require("sdk/system/events");
const { setTimeout } = require("sdk/timers");
const { getMostRecentBrowserWindow } = require('../window/utils');
// Opens about:addons in a new tab, then displays the inline
// preferences of the provided add-on
const open = ({ id }) => new Promise((resolve, reject) => {
// opening the about:addons page in a new tab
let tab = openTab(getMostRecentBrowserWindow(), "about:addons");
const open = function open({ id }) {
let showing = defer();
let loaded = defer();
let result = { id: id };
let tab = openTab(getMostRecentBrowserWindow(), "about:addons", {
inBackground: true
});
let browser = getBrowserForTab(tab);
// waiting for the about:addons page to load
browser.addEventListener("load", function onPageLoad() {
browser.removeEventListener("load", onPageLoad, true);
let window = browser.contentWindow;
@ -27,16 +30,21 @@ const open = ({ id }) => new Promise((resolve, reject) => {
on("addon-options-displayed", function onPrefDisplayed({ subject: doc, data }) {
if (data === id) {
off("addon-options-displayed", onPrefDisplayed);
resolve({
id: id,
tabId: getTabId(tab),
"document": doc
});
result.tabId = getTabId(tab);
result.document = doc;
loaded.resolve();
}
}, true);
// display the add-on inline preferences page
window.gViewController.commands.cmd_showItemDetails.doCommand({ id: id }, true);
let { node } = window.gViewController.viewObjects.detail;
node.addEventListener("ViewChanged", function whenViewChanges() {
node.removeEventListener("ViewChanged", whenViewChanges, false);
showing.resolve();
}, false);
}, true);
});
return all([ showing.promise, loaded.promise ]).then(_ => result);
}
exports.open = open;

View File

@ -21,7 +21,7 @@ const name = readPref("name") || options.name;
const version = readPref("version") || options.version;
const loadReason = readPref("load.reason") || options.loadReason;
const rootURI = readPref("rootURI") || options.rootURI || "";
const baseURI = readPref("baseURI") || options.prefixURI + name + "/"
const baseURI = readPref("baseURI") || options.prefixURI + name + "/";
const addonDataURI = baseURI + "data/";
const metadata = options.metadata || {};
const permissions = metadata.permissions || {};
@ -30,10 +30,7 @@ const isPacked = rootURI && rootURI.indexOf("jar:") === 0;
const uri = (path="") =>
path.contains(":") ? path : addonDataURI + path.replace(/^\.\//, "");
let preferencesBranch = ("preferences-branch" in metadata)
? metadata["preferences-branch"]
: options.preferencesBranch
let { preferencesBranch } = options;
if (/[^\w{@}.-]/.test(preferencesBranch)) {
preferencesBranch = id;
console.warn("Ignoring preferences-branch (not a valid branch name)");

View File

@ -20,7 +20,7 @@ let { merge } = require('../util/object');
let { setTimeout, clearTimeout } = require('../timers');
let isWindows = platform.indexOf('win') === 0;
let processes = new WeakMap();
let processes = WeakMap();
/**

View File

@ -7,6 +7,7 @@ module.metadata = {
"stability": "experimental"
};
const { XulApp } = require("./xul-app.jsm");
var { Cu } = require("chrome");
var { XulApp } = Cu.import("resource://gre/modules/sdk/system/XulApp.js", {});
Object.keys(XulApp).forEach(k => exports[k] = XulApp[k]);

View File

@ -7,13 +7,12 @@ module.metadata = {
"stability": "unstable"
};
const { EventTarget } = require("../event/target");
const { emit } = require("../event/core");
const { EventEmitterTrait: EventEmitter } = require("../deprecated/events");
const { DOMEventAssembler } = require("../deprecated/events/assembler");
const { Class } = require("../core/heritage");
const { getActiveTab, getTabs } = require("./utils");
const { Trait } = require("../deprecated/light-traits");
const { getActiveTab, getTabs, getTabContainer } = require("./utils");
const { browserWindowIterator } = require("../deprecated/window-utils");
const { isBrowser, windows, getMostRecentBrowserWindow } = require("../window/utils");
const { isBrowser } = require('../window/utils');
const { observer: windowObserver } = require("../windows/observer");
const EVENTS = {
@ -25,69 +24,15 @@ const EVENTS = {
"TabUnpinned": "unpinned"
};
const selectedTab = Symbol("observer/state/selectedTab");
// Event emitter objects used to register listeners and emit events on them
// when they occur.
const Observer = Class({
implements: [EventTarget, DOMEventAssembler],
initialize() {
this[selectedTab] = null;
// Currently Gecko does not dispatch any event on the previously selected
// tab before / after "TabSelect" is dispatched. In order to work around this
// limitation we keep track of selected tab and emit "deactivate" event with
// that before emitting "activate" on selected tab.
this.on("select", tab => {
const selected = this[selectedTab];
if (selected !== tab) {
if (selected) {
emit(this, 'deactivate', selected);
}
if (tab) {
this[selectedTab] = tab;
emit(this, 'activate', this[selectedTab]);
}
}
});
// We also observe opening / closing windows in order to add / remove it's
// containers to the observed list.
windowObserver.on("open", chromeWindow => {
if (isBrowser(chromeWindow)) {
this.observe(chromeWindow);
}
});
windowObserver.on("close", chromeWindow => {
if (isBrowser(chromeWindow)) {
// Bug 751546: Emit `deactivate` event on window close immediatly
// Otherwise we are going to face "dead object" exception on `select` event
if (getActiveTab(chromeWindow) === this[selectedTab]) {
emit(this, "deactivate", this[selectedTab]);
this[selectedTab] = null;
}
this.ignore(chromeWindow);
}
});
// Currently gecko does not dispatches "TabSelect" events when different
// window gets activated. To work around this limitation we emulate "select"
// event for this case.
windowObserver.on("activate", chromeWindow => {
if (isBrowser(chromeWindow)) {
emit(this, "select", getActiveTab(chromeWindow));
}
});
// We should synchronize state, since probably we already have at least one
// window open.
for (let chromeWindow of browserWindowIterator()) {
this.observe(chromeWindow);
}
},
const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
/**
* Method is implemented by `EventEmitter` and is used just for emitting
* events on registered listeners.
*/
_emit: Trait.required,
/**
* Events that are supported and emitted by the module.
*/
@ -100,8 +45,54 @@ const Observer = Class({
* Keyboard event being emitted.
*/
handleEvent: function handleEvent(event) {
emit(this, EVENTS[event.type], event.target, event);
this._emit(EVENTS[event.type], event.target, event);
}
});
exports.observer = new Observer();
// Currently Gecko does not dispatch any event on the previously selected
// tab before / after "TabSelect" is dispatched. In order to work around this
// limitation we keep track of selected tab and emit "deactivate" event with
// that before emitting "activate" on selected tab.
var selectedTab = null;
function onTabSelect(tab) {
if (selectedTab !== tab) {
if (selectedTab) observer._emit('deactivate', selectedTab);
if (tab) observer._emit('activate', selectedTab = tab);
}
};
observer.on('select', onTabSelect);
// We also observe opening / closing windows in order to add / remove it's
// containers to the observed list.
function onWindowOpen(chromeWindow) {
if (!isBrowser(chromeWindow)) return; // Ignore if it's not a browser window.
observer.observe(getTabContainer(chromeWindow));
}
windowObserver.on("open", onWindowOpen);
function onWindowClose(chromeWindow) {
if (!isBrowser(chromeWindow)) return; // Ignore if it's not a browser window.
// Bug 751546: Emit `deactivate` event on window close immediatly
// Otherwise we are going to face "dead object" exception on `select` event
if (getActiveTab(chromeWindow) == selectedTab) {
observer._emit("deactivate", selectedTab);
selectedTab = null;
}
observer.ignore(getTabContainer(chromeWindow));
}
windowObserver.on("close", onWindowClose);
// Currently gecko does not dispatches "TabSelect" events when different
// window gets activated. To work around this limitation we emulate "select"
// event for this case.
windowObserver.on("activate", function onWindowActivate(chromeWindow) {
if (!isBrowser(chromeWindow)) return; // Ignore if it's not a browser window.
observer._emit("select", getActiveTab(chromeWindow));
});
// We should synchronize state, since probably we already have at least one
// window open.
for (let window of browserWindowIterator()) onWindowOpen(window);
exports.observer = observer;

View File

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const ContentWorker = require('../content/worker').Worker;
const ContentWorker = require('../content/worker-parent').Worker;
function Worker(options, window) {
options.window = window;

View File

@ -8,10 +8,10 @@ module.metadata = {
};
const { Cu } = require("chrome");
const { Task } = require("resource://gre/modules/Task.jsm", {});
const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
const { defer } = require("sdk/core/promise");
const BaseAssert = require("sdk/test/assert").Assert;
const { isFunction, isObject, isGenerator } = require("sdk/lang/type");
const { isFunction, isObject } = require("sdk/lang/type");
const { extend } = require("sdk/util/object");
exports.Assert = BaseAssert;
@ -49,10 +49,10 @@ function defineTestSuite(target, suite, prefix) {
// If test function is a generator use a task JS to allow yield-ing
// style test runs.
if (isGenerator(test)) {
if (test.isGenerator && test.isGenerator()) {
options.waitUntilDone();
Task.spawn(test.bind(null, assert)).
catch(assert.fail).
then(null, assert.fail).
then(assert.end);
}
@ -60,6 +60,7 @@ function defineTestSuite(target, suite, prefix) {
// it means that test is async and second argument is a callback
// to notify that test is finished.
else if (1 < test.length) {
// Letting test runner know that test is executed async and
// creating a callback function that CommonJS tests will call
// once it's done.

View File

@ -78,9 +78,9 @@ Assert.prototype = {
if ('operator' in e) {
message += [
" -",
source(e.actual),
source(e.expected),
e.operator,
source(e.expected)
source(e.actual)
].join(" ");
}
}
@ -89,7 +89,6 @@ Assert.prototype = {
},
pass: function pass(message) {
this._log.pass(message);
return true;
},
error: function error(e) {
this._log.exception(e);
@ -102,11 +101,10 @@ Assert.prototype = {
message: message,
operator: "=="
});
return false;
}
this.pass(message);
return true;
else {
this.pass(message);
}
},
/**
@ -117,16 +115,15 @@ Assert.prototype = {
equal: function equal(actual, expected, message) {
if (actual == expected) {
this.pass(message);
return true;
}
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "=="
});
return false;
else {
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "=="
});
}
},
/**
@ -138,16 +135,15 @@ Assert.prototype = {
notEqual: function notEqual(actual, expected, message) {
if (actual != expected) {
this.pass(message);
return true;
}
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "!=",
});
return false;
else {
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "!=",
});
}
},
/**
@ -158,16 +154,15 @@ Assert.prototype = {
deepEqual: function deepEqual(actual, expected, message) {
if (isDeepEqual(actual, expected)) {
this.pass(message);
return true;
}
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "deepEqual"
});
return false;
else {
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "deepEqual"
});
}
},
/**
@ -179,16 +174,15 @@ Assert.prototype = {
notDeepEqual: function notDeepEqual(actual, expected, message) {
if (!isDeepEqual(actual, expected)) {
this.pass(message);
return true;
}
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "notDeepEqual"
});
return false;
else {
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "notDeepEqual"
});
}
},
/**
@ -200,16 +194,15 @@ Assert.prototype = {
strictEqual: function strictEqual(actual, expected, message) {
if (actual === expected) {
this.pass(message);
return true;
}
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "==="
});
return false;
else {
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "==="
});
}
},
/**
@ -221,16 +214,15 @@ Assert.prototype = {
notStrictEqual: function notStrictEqual(actual, expected, message) {
if (actual !== expected) {
this.pass(message);
return true;
}
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "!=="
});
return false;
else {
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "!=="
})
}
},
/**
@ -283,36 +275,35 @@ Assert.prototype = {
if (threw && (isUndefined(Error) ||
// If passed `Error` is RegExp using it's test method to
// assert thrown exception message.
(isRegExp(Error) && (Error.test(exception.message) || Error.test(exception.toString()))) ||
(isRegExp(Error) && Error.test(exception.message)) ||
// If passed `Error` is a constructor function testing if
// thrown exception is an instance of it.
(isFunction(Error) && instanceOf(exception, Error))))
{
this.pass(message);
return true;
}
// Otherwise we report assertion failure.
let failure = {
message: message,
operator: "matches"
};
else {
let failure = {
message: message,
operator: "throws"
};
if (exception) {
failure.actual = exception.message || exception.toString();
if (exception)
failure.actual = exception;
if (Error)
failure.expected = Error;
this.fail(failure);
}
if (Error) {
failure.expected = Error.toString();
}
this.fail(failure);
return false;
}
};
exports.Assert = Assert;
function isDeepEqual(actual, expected) {
// 7.1. All identical values are equivalent, as determined by ===.
if (actual === expected) {
return true;

View File

@ -438,8 +438,7 @@ var POINTLESS_ERRORS = [
'file: "chrome://browser/content/',
'file: "chrome://global/content/',
'[JavaScript Warning: "The character encoding of a framed document was ' +
'not declared.',
'file: "chrome://browser/skin/'
'not declared.'
];
var consoleListener = {

View File

@ -12,9 +12,6 @@ const { setInterval, clearInterval } = require('../timers');
const { getTabs, closeTab } = require("../tabs/utils");
const { windows: getWindows } = require("../window/utils");
const { close: closeWindow } = require("../window/helpers");
const { isGenerator } = require("../lang/type");
const { Task } = require("resource://gre/modules/Task.jsm");
function getTestNames (exports)
Object.keys(exports).filter(name => /^test/.test(name))
@ -32,39 +29,18 @@ function isHelperAsync (fn) fn.length > 2
function before (exports, beforeFn) {
getTestNames(exports).map(name => {
let testFn = exports[name];
// GENERATOR TESTS
if (isGenerator(testFn) && isGenerator(beforeFn)) {
exports[name] = function*(assert) {
yield Task.spawn(beforeFn.bind(null, name, assert));
yield Task.spawn(testFn.bind(null, assert));
}
}
else if (isGenerator(testFn) && !isHelperAsync(beforeFn)) {
exports[name] = function*(assert) {
beforeFn(name, assert);
yield Task.spawn(testFn.bind(null, assert));
}
}
else if (isGenerator(testFn) && isHelperAsync(beforeFn)) {
exports[name] = function*(assert) {
yield new Promise(resolve => beforeFn(name, assert, resolve));
yield Task.spawn(testFn.bind(null, assert));
}
}
// SYNC TESTS
else if (!isTestAsync(testFn) && isGenerator(beforeFn)) {
exports[name] = function*(assert) {
yield Task.spawn(beforeFn.bind(null, name, assert));
testFn(assert);
};
}
else if (!isTestAsync(testFn) && !isHelperAsync(beforeFn)) {
if (!isTestAsync(testFn) && !isHelperAsync(beforeFn)) {
exports[name] = function (assert) {
beforeFn(name, assert);
testFn(assert);
};
}
else if (isTestAsync(testFn) && !isHelperAsync(beforeFn)) {
exports[name] = function (assert, done) {
beforeFn(name, assert);
testFn(assert, done);
};
}
else if (!isTestAsync(testFn) && isHelperAsync(beforeFn)) {
exports[name] = function (assert, done) {
beforeFn(name, assert, () => {
@ -72,21 +48,7 @@ function before (exports, beforeFn) {
done();
});
};
}
// ASYNC TESTS
else if (isTestAsync(testFn) && isGenerator(beforeFn)) {
exports[name] = function*(assert) {
yield Task.spawn(beforeFn.bind(null, name, assert));
yield new Promise(resolve => testFn(assert, resolve));
};
}
else if (isTestAsync(testFn) && !isHelperAsync(beforeFn)) {
exports[name] = function (assert, done) {
beforeFn(name, assert);
testFn(assert, done);
};
}
else if (isTestAsync(testFn) && isHelperAsync(beforeFn)) {
} else if (isTestAsync(testFn) && isHelperAsync(beforeFn)) {
exports[name] = function (assert, done) {
beforeFn(name, assert, () => {
testFn(assert, done);
@ -107,62 +69,30 @@ exports.before = before;
function after (exports, afterFn) {
getTestNames(exports).map(name => {
let testFn = exports[name];
// GENERATOR TESTS
if (isGenerator(testFn) && isGenerator(afterFn)) {
exports[name] = function*(assert) {
yield Task.spawn(testFn.bind(null, assert));
yield Task.spawn(afterFn.bind(null, name, assert));
}
}
else if (isGenerator(testFn) && !isHelperAsync(afterFn)) {
exports[name] = function*(assert) {
yield Task.spawn(testFn.bind(null, assert));
afterFn(name, assert);
}
}
else if (isGenerator(testFn) && isHelperAsync(afterFn)) {
exports[name] = function*(assert) {
yield Task.spawn(testFn.bind(null, assert));
yield new Promise(resolve => afterFn(name, assert, resolve));
}
}
// SYNC TESTS
else if (!isTestAsync(testFn) && isGenerator(afterFn)) {
exports[name] = function*(assert) {
testFn(assert);
yield Task.spawn(afterFn.bind(null, name, assert));
};
}
else if (!isTestAsync(testFn) && !isHelperAsync(afterFn)) {
if (!isTestAsync(testFn) && !isHelperAsync(afterFn)) {
exports[name] = function (assert) {
testFn(assert);
afterFn(name, assert);
};
}
else if (isTestAsync(testFn) && !isHelperAsync(afterFn)) {
exports[name] = function (assert, done) {
testFn(assert, () => {
afterFn(name, assert);
done();
});
};
}
else if (!isTestAsync(testFn) && isHelperAsync(afterFn)) {
exports[name] = function (assert, done) {
testFn(assert);
afterFn(name, assert, done);
};
}
// ASYNC TESTS
else if (isTestAsync(testFn) && isGenerator(afterFn)) {
exports[name] = function*(assert) {
yield new Promise(resolve => testFn(assert, resolve));
yield Task.spawn(afterFn.bind(null, name, assert));
};
}
else if (isTestAsync(testFn) && !isHelperAsync(afterFn)) {
exports[name] = function*(assert) {
yield new Promise(resolve => testFn(assert, resolve));
afterFn(name, assert);
};
}
else if (isTestAsync(testFn) && isHelperAsync(afterFn)) {
exports[name] = function*(assert) {
yield new Promise(resolve => testFn(assert, resolve));
yield new Promise(resolve => afterFn(name, assert, resolve));
} else if (isTestAsync(testFn) && isHelperAsync(afterFn)) {
exports[name] = function (assert, done) {
testFn(assert, () => {
afterFn(name, assert, done);
});
};
}
});

View File

@ -7,9 +7,7 @@
module.metadata = {
'stability': 'experimental',
'engines': {
'Firefox': '*',
'SeaMonkey': '*',
'Thunderbird': '*'
'Firefox': '*'
}
};

View File

@ -1,179 +0,0 @@
"use strict";
// Internal properties not exposed to the public.
const cache = Symbol("component/cache");
const writer = Symbol("component/writer");
const isFirstWrite = Symbol("component/writer/first-write?");
const currentState = Symbol("component/state/current");
const pendingState = Symbol("component/state/pending");
const isWriting = Symbol("component/writing?");
const isntNull = x => x !== null;
const Component = function(options, children) {
this[currentState] = null;
this[pendingState] = null;
this[writer] = null;
this[cache] = null;
this[isFirstWrite] = true;
this[Component.construct](options, children);
}
Component.Component = Component;
// Constructs component.
Component.construct = Symbol("component/construct");
// Called with `options` and `children` and must return
// initial state back.
Component.initial = Symbol("component/initial");
// Function patches current `state` with a given update.
Component.patch = Symbol("component/patch");
// Function that replaces current `state` with a passed state.
Component.reset = Symbol("component/reset");
// Function that must return render tree from passed state.
Component.render = Symbol("component/render");
// Path of the component with in the mount point.
Component.path = Symbol("component/path");
Component.isMounted = component => !!component[writer];
Component.isWriting = component => !!component[isWriting];
// Internal method that mounts component to a writer.
// Mounts component to a writer.
Component.mount = (component, write) => {
if (Component.isMounted(component)) {
throw Error("Can not mount already mounted component");
}
component[writer] = write;
Component.write(component);
if (component[Component.mounted]) {
component[Component.mounted]();
}
}
// Unmounts component from a writer.
Component.unmount = (component) => {
if (Component.isMounted(component)) {
component[writer] = null;
if (component[Component.unmounted]) {
component[Component.unmounted]();
}
} else {
console.warn("Unmounting component that is not mounted is redundant");
}
};
// Method invoked once after inital write occurs.
Component.mounted = Symbol("component/mounted");
// Internal method that unmounts component from the writer.
Component.unmounted = Symbol("component/unmounted");
// Function that must return true if component is changed
Component.isUpdated = Symbol("component/updated?");
Component.update = Symbol("component/update");
Component.updated = Symbol("component/updated");
const writeChild = base => (child, index) => Component.write(child, base, index)
Component.write = (component, base, index) => {
if (component === null) {
return component;
}
if (!(component instanceof Component)) {
const path = base ? `${base}${component.key || index}/` : `/`;
return Object.assign({}, component, {
[Component.path]: path,
children: component.children && component.children.
map(writeChild(path)).
filter(isntNull)
});
}
component[isWriting] = true;
try {
const current = component[currentState];
const pending = component[pendingState] || current;
const isUpdated = component[Component.isUpdated];
const isInitial = component[isFirstWrite];
if (isUpdated(current, pending) || isInitial) {
if (!isInitial && component[Component.update]) {
component[Component.update](pending, current)
}
// Note: [Component.update] could have caused more updates so can't use
// `pending` as `component[pendingState]` may have changed.
component[currentState] = component[pendingState] || current;
component[pendingState] = null;
const tree = component[Component.render](component[currentState]);
component[cache] = Component.write(tree, base, index);
if (component[writer]) {
component[writer].call(null, component[cache]);
}
if (!isInitial && component[Component.updated]) {
component[Component.updated](current, pending);
}
}
component[isFirstWrite] = false;
return component[cache];
} finally {
component[isWriting] = false;
}
};
Component.prototype = Object.freeze({
constructor: Component,
[Component.mounted]: null,
[Component.unmounted]: null,
[Component.update]: null,
[Component.updated]: null,
get state() {
return this[pendingState] || this[currentState];
},
[Component.construct](settings, items) {
const initial = this[Component.initial];
const base = initial(settings, items);
const options = Object.assign(Object.create(null), base.options, settings);
const children = base.children || items || null;
const state = Object.assign(Object.create(null), base, {options, children});
this[currentState] = state;
if (this.setup) {
this.setup(state);
}
},
[Component.initial](options, children) {
return Object.create(null);
},
[Component.patch](update) {
this[Component.reset](Object.assign({}, this.state, update));
},
[Component.reset](state) {
this[pendingState] = state;
if (Component.isMounted(this) && !Component.isWriting(this)) {
Component.write(this);
}
},
[Component.isUpdated](before, after) {
return before != after
},
[Component.render](state) {
throw Error("Component must implement [Component.render] member");
}
});
module.exports = Component;

View File

@ -24,7 +24,7 @@ const { isBrowser, getMostRecentBrowserWindow, windows, isWindowPrivate } = requ
const { ns } = require('../core/namespace');
const { remove: removeFromArray } = require('../util/array');
const { show, hide, toggle } = require('./sidebar/actions');
const { Worker } = require('../deprecated/sync-worker');
const { Worker } = require('../content/worker');
const { contract: sidebarContract } = require('./sidebar/contract');
const { create, dispose, updateTitle, updateURL, isSidebarShowing, showSidebar, hideSidebar } = require('./sidebar/view');
const { defer } = require('../core/promise');

View File

@ -8,9 +8,7 @@
module.metadata = {
'stability': 'experimental',
'engines': {
'Firefox': '*',
'SeaMonkey': '*',
'Thunderbird': '*'
'Firefox': '*'
}
};

View File

@ -7,9 +7,7 @@
module.metadata = {
'stability': 'experimental',
'engines': {
'Firefox': '*',
'SeaMonkey': '*',
'Thunderbird': '*'
'Firefox': '*'
}
};

View File

@ -1,37 +0,0 @@
/* 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";
module.metadata = {
"stability": "unstable"
};
const {Cc, Ci} = require("chrome");
const ioService = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
const resourceHandler = ioService.getProtocolHandler("resource").
QueryInterface(Ci.nsIResProtocolHandler);
const URI = (uri, base=null) =>
ioService.newURI(uri, null, base && URI(base))
const mount = (domain, uri) =>
resourceHandler.setSubstitution(domain, ioService.newURI(uri, null, null));
exports.mount = mount;
const unmount = (domain, uri) =>
resourceHandler.setSubstitution(domain, null);
exports.unmount = unmount;
const domain = 1;
const path = 2;
const resolve = (uri) => {
const match = /resource\:\/\/([^\/]+)\/{0,1}([\s\S]*)/.exec(uri);
const domain = match && match[1];
const path = match && match[2];
return !match ? null :
!resourceHandler.hasSubstitution(domain) ? null :
resourceHandler.resolveURI(URI(`/${path}`, `resource://${domain}/`));
}
exports.resolve = resolve;

View File

@ -1,36 +0,0 @@
/* 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";
module.metadata = {
"stability": "experimental"
};
const makeDescriptor = (name, method) => ({
get() {
if (!Object.hasOwnProperty.call(this, name)) {
Object.defineProperty(this, name, {value: method.bind(this)});
return this[name];
} else {
return method;
}
}
});
const Bond = function(methods) {
let descriptor = {};
let members = [...Object.getOwnPropertyNames(methods),
...Object.getOwnPropertySymbols(methods)];
for (let name of members) {
let method = methods[name];
if (typeof(method) !== "function") {
throw new TypeError(`Property named "${name}" passed to Bond must be a function`);
}
descriptor[name] = makeDescriptor(name, method);
}
return Object.create(Bond.prototype, descriptor);
}
exports.Bond = Bond;

View File

@ -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";
module.metadata = {
"stability": "unstable"
};
const { EventEmitter } = require('../deprecated/events');
const unload = require('../system/unload');
const Registry = EventEmitter.compose({
_registry: null,
_constructor: null,
constructor: function Registry(constructor) {
this._registry = [];
this._constructor = constructor;
this.on('error', this._onError = this._onError.bind(this));
unload.ensure(this, "_destructor");
},
_destructor: function _destructor() {
let _registry = this._registry.slice(0);
for (let instance of _registry)
this._emit('remove', instance);
this._registry.splice(0);
},
_onError: function _onError(e) {
if (!this._listeners('error').length)
console.error(e);
},
has: function has(instance) {
let _registry = this._registry;
return (
(0 <= _registry.indexOf(instance)) ||
(instance && instance._public && 0 <= _registry.indexOf(instance._public))
);
},
add: function add(instance) {
let { _constructor, _registry } = this;
if (!(instance instanceof _constructor))
instance = new _constructor(instance);
if (0 > _registry.indexOf(instance)) {
_registry.push(instance);
this._emit('add', instance);
}
return instance;
},
remove: function remove(instance) {
let _registry = this._registry;
let index = _registry.indexOf(instance)
if (0 <= index) {
this._emit('remove', instance);
_registry.splice(index, 1);
}
}
});
exports.Registry = Registry;

View File

@ -1,6 +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/. */
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
"use strict";
module.metadata = {
@ -21,15 +22,14 @@ module.metadata = {
// - `_` used for argument(s) or variable(s) who's values are ignored.
const { complement, flip, identity } = require("../lang/functional");
const { isArray, isArguments, isMap, isSet, isGenerator,
const { isArray, isArguments, isMap, isSet,
isString, isBoolean, isNumber } = require("../lang/type");
const Sequence = function Sequence(iterator) {
if (!isGenerator(iterator)) {
if (iterator.isGenerator && iterator.isGenerator())
this[Symbol.iterator] = iterator;
else
throw TypeError("Expected generator argument");
}
this[Symbol.iterator] = iterator;
};
exports.Sequence = Sequence;
@ -61,6 +61,9 @@ const seq = polymorphic({
});
exports.seq = seq;
// Function to cast seq to string.
const string = (...etc) => "".concat(...etc);
exports.string = string;
@ -108,27 +111,6 @@ const pairs = polymorphic({
});
exports.pairs = pairs;
const names = polymorphic({
null: empty,
void: empty,
default: object => seq(function*() {
for (let name of Object.getOwnPropertyNames(object)) {
yield name;
}
})
});
exports.names = names;
const symbols = polymorphic({
null: empty,
void: empty,
default: object => seq(function* () {
for (let symbol of Object.getOwnPropertySymbols(object)) {
yield symbol;
}
})
});
exports.symbols = symbols;
const keys = polymorphic({
null: empty,

View File

@ -26,9 +26,7 @@ const FM = Cc["@mozilla.org/focus-manager;1"].
const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
const BROWSER = 'navigator:browser',
URI_BROWSER = Cc['@mozilla.org/preferences-service;1'].
getService(Ci.nsIPrefService).
getBranch(null).getCharPref('browser.chromeURL'),
URI_BROWSER = 'chrome://browser/content/browser.xul',
NAME = '_blank',
FEATURES = 'chrome,all,dialog=no,non-private';

View File

@ -9,6 +9,7 @@ const { Cc, Ci, Cr } = require('chrome'),
{ EventEmitter } = require('../deprecated/events'),
{ WindowTabs, WindowTabTracker } = require('./tabs-firefox'),
{ WindowDom } = require('./dom'),
{ WindowLoader } = require('./loader'),
{ isBrowser, getWindowDocShell, isFocused,
windows: windowIterator, isWindowPrivate } = require('../window/utils'),
{ Options } = require('../tabs/common'),
@ -22,10 +23,7 @@ const { windowNS } = require('../window/namespace');
const { isPrivateBrowsingSupported } = require('../self');
const { ignoreWindow, isPrivate } = require('sdk/private-browsing/utils');
const { viewFor } = require('../view/core');
const { openDialog } = require('../window/utils');
const ON_LOAD = 'load',
ON_UNLOAD = 'unload',
STATE_LOADED = 'complete';
/**
* Window trait composes safe wrappers for browser window that are E10S
* compatible.
@ -35,96 +33,12 @@ const BrowserWindowTrait = Trait.compose(
WindowDom.resolve({ close: '_close' }),
WindowTabs,
WindowTabTracker,
WindowLoader,
/* WindowSidebars, */
Trait.compose({
_emit: Trait.required,
_close: Trait.required,
/**
* Private window who's load event is being tracked. Once window is loaded
* `_onLoad` is called.
* @type {nsIWindow}
*/
get _window() this.__window,
set _window(window) {
let _window = this.__window;
if (!window) window = null;
if (window !== _window) {
if (_window) {
if (this.__unloadListener)
_window.removeEventListener(ON_UNLOAD, this.__unloadListener, false);
if (this.__loadListener)
_window.removeEventListener(ON_LOAD, this.__loadListener, false);
}
if (window) {
window.addEventListener(
ON_UNLOAD,
this.__unloadListener ||
(this.__unloadListener = this._unloadListener.bind(this))
,
false
);
this.__window = window;
// If window is not loaded yet setting up a listener.
if (STATE_LOADED != window.document.readyState) {
window.addEventListener(
ON_LOAD,
this.__loadListener ||
(this.__loadListener = this._loadListener.bind(this))
,
false
);
}
else { // If window is loaded calling listener next turn of event loop.
this._onLoad(window)
}
}
else {
this.__window = null;
}
}
},
__window: null,
/**
* Internal method used for listening 'load' event on the `_window`.
* Method takes care of removing itself from 'load' event listeners once
* event is being handled.
*/
_loadListener: function _loadListener(event) {
let window = this._window;
if (!event.target || event.target.defaultView != window) return;
window.removeEventListener(ON_LOAD, this.__loadListener, false);
this._onLoad(window);
},
__loadListener: null,
/**
* Internal method used for listening 'unload' event on the `_window`.
* Method takes care of removing itself from 'unload' event listeners once
* event is being handled.
*/
_unloadListener: function _unloadListener(event) {
let window = this._window;
if (!event.target
|| event.target.defaultView != window
|| STATE_LOADED != window.document.readyState
) return;
window.removeEventListener(ON_UNLOAD, this.__unloadListener, false);
this._onUnload(window);
},
__unloadListener: null,
_load: function _load() {
if (this.__window)
return;
this._window = openDialog({
private: this._isPrivate,
args: this._tabOptions.map(function(options) options.url).join("|")
});
},
_load: Trait.required,
/**
* Constructor returns wrapper of the specified chrome window.
* @param {nsIWindow} window

View File

@ -0,0 +1,128 @@
/* 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';
module.metadata = {
"stability": "unstable"
};
const { Cc, Ci } = require('chrome'),
{ setTimeout } = require('../timers'),
{ Trait } = require('../deprecated/traits'),
{ openDialog } = require('../window/utils'),
ON_LOAD = 'load',
ON_UNLOAD = 'unload',
STATE_LOADED = 'complete';
/**
* Trait provides private `_window` property and requires `_onLoad` property
* that will be called when `_window` is loaded. If `_window` property value
* is changed with already loaded window `_onLoad` still will be called.
*/
const WindowLoader = Trait.compose({
/**
* Internal listener that is called when window is loaded.
* Please keep in mind that this trait will not handle exceptions that may
* be thrown by this method so method itself should take care of
* handling them.
* @param {nsIWindow} window
*/
_onLoad: Trait.required,
_tabOptions: Trait.required,
/**
* Internal listener that is called when `_window`'s DOM 'unload' event
* is dispatched. Please note that this trait will not handle exceptions that
* may be thrown by this method so method itself should take care of
* handling them.
*/
_onUnload: Trait.required,
_load: function _load() {
if (this.__window)
return;
this._window = openDialog({
private: this._isPrivate,
args: this._tabOptions.map(function(options) options.url).join("|")
});
},
/**
* Private window who's load event is being tracked. Once window is loaded
* `_onLoad` is called.
* @type {nsIWindow}
*/
get _window() this.__window,
set _window(window) {
let _window = this.__window;
if (!window) window = null;
if (window !== _window) {
if (_window) {
if (this.__unloadListener)
_window.removeEventListener(ON_UNLOAD, this.__unloadListener, false);
if (this.__loadListener)
_window.removeEventListener(ON_LOAD, this.__loadListener, false);
}
if (window) {
window.addEventListener(
ON_UNLOAD,
this.__unloadListener ||
(this.__unloadListener = this._unloadListener.bind(this))
,
false
);
this.__window = window;
// If window is not loaded yet setting up a listener.
if (STATE_LOADED != window.document.readyState) {
window.addEventListener(
ON_LOAD,
this.__loadListener ||
(this.__loadListener = this._loadListener.bind(this))
,
false
);
}
else { // If window is loaded calling listener next turn of event loop.
this._onLoad(window)
}
}
else {
this.__window = null;
}
}
},
__window: null,
/**
* Internal method used for listening 'load' event on the `_window`.
* Method takes care of removing itself from 'load' event listeners once
* event is being handled.
*/
_loadListener: function _loadListener(event) {
let window = this._window;
if (!event.target || event.target.defaultView != window) return;
window.removeEventListener(ON_LOAD, this.__loadListener, false);
this._onLoad(window);
},
__loadListener: null,
/**
* Internal method used for listening 'unload' event on the `_window`.
* Method takes care of removing itself from 'unload' event listeners once
* event is being handled.
*/
_unloadListener: function _unloadListener(event) {
let window = this._window;
if (!event.target
|| event.target.defaultView != window
|| STATE_LOADED != window.document.readyState
) return;
window.removeEventListener(ON_UNLOAD, this.__unloadListener, false);
this._onUnload(window);
},
__unloadListener: null
});
exports.WindowLoader = WindowLoader;

View File

@ -7,29 +7,19 @@ module.metadata = {
"stability": "unstable"
};
const { EventTarget } = require("../event/target");
const { emit } = require("../event/core");
const { EventEmitterTrait: EventEmitter } = require("../deprecated/events");
const { WindowTracker, windowIterator } = require("../deprecated/window-utils");
const { DOMEventAssembler } = require("../deprecated/events/assembler");
const { Class } = require("../core/heritage");
const { Trait } = require("../deprecated/light-traits");
// Event emitter objects used to register listeners and emit events on them
// when they occur.
const Observer = Class({
initialize() {
// Using `WindowTracker` to track window events.
WindowTracker({
onTrack: chromeWindow => {
emit(this, "open", chromeWindow);
this.observe(chromeWindow);
},
onUntrack: chromeWindow => {
emit(this, "close", chromeWindow);
this.ignore(chromeWindow);
}
});
},
implements: [EventTarget, DOMEventAssembler],
const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
/**
* Method is implemented by `EventEmitter` and is used just for emitting
* events on registered listeners.
*/
_emit: Trait.required,
/**
* Events that are supported and emitted by the module.
*/
@ -41,9 +31,21 @@ const Observer = Class({
* @param {Event} event
* Keyboard event being emitted.
*/
handleEvent(event) {
emit(this, event.type, event.target, event);
handleEvent: function handleEvent(event) {
this._emit(event.type, event.target, event);
}
});
exports.observer = new Observer();
// Using `WindowTracker` to track window events.
WindowTracker({
onTrack: function onTrack(chromeWindow) {
observer._emit("open", chromeWindow);
observer.observe(chromeWindow);
},
onUntrack: function onUntrack(chromeWindow) {
observer._emit("close", chromeWindow);
observer.ignore(chromeWindow);
}
});
exports.observer = observer;

View File

@ -158,7 +158,7 @@ function onTabSelect(event) {
emit(tab, 'activate', tab);
emit(gTabs, 'activate', tab);
for (let t of gTabs) {
for (let of in gTabs) {
if (t === tab) continue;
emit(t, 'deactivate', t);
emit(gTabs, 'deactivate', t);

View File

@ -2,20 +2,28 @@
* 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/. */
;(function(factory) { // Module boilerplate :(
if (typeof(require) === 'function') { // CommonJS
require("chrome").Cu.import(module.uri, exports);
;(function(id, factory) { // Module boilerplate :(
if (typeof(define) === 'function') { // RequireJS
define(factory);
} else if (typeof(require) === 'function') { // CommonJS
factory.call(this, require, exports, module);
} else if (~String(this).indexOf('BackstagePass')) { // JSM
this[factory.name] = {};
factory(function require(uri) {
var imports = {};
this['Components'].utils.import(uri, imports);
return imports;
}, this[factory.name], { uri: __URI__, id: id });
this.EXPORTED_SYMBOLS = [factory.name];
} else if (~String(this).indexOf('Sandbox')) { // Sandbox
factory(function require(uri) {}, this, { uri: __URI__, id: id });
} else { // Browser or alike
var globals = this
factory(function require(id) {
return globals[id];
}, (globals[id] = {}), { uri: document.location.href + '#' + id, id: id });
}
else if (~String(this).indexOf('BackstagePass')) { // JSM
let module = { uri: __URI__, id: "toolkit/loader", exports: Object.create(null) }
factory(module);
Object.assign(this, module.exports);
this.EXPORTED_SYMBOLS = Object.getOwnPropertyNames(module.exports);
}
else {
throw Error("Loading environment is not supported");
}
})(module => {
}).call(this, 'loader', function Loader(require, exports, module) {
'use strict';
@ -35,10 +43,6 @@ const { Reflect } = Cu.import("resource://gre/modules/reflect.jsm", {});
const { ConsoleAPI } = Cu.import("resource://gre/modules/devtools/Console.jsm");
const { join: pathJoin, normalize, dirname } = Cu.import("resource://gre/modules/osfile/ospath_unix.jsm");
const xulappURI = module.uri.replace("toolkit/loader.js",
"sdk/system/xul-app.jsm");
const { incompatibility } = Cu.import(xulappURI, {}).XulApp;
// Define some shortcuts.
const bind = Function.call.bind(Function.bind);
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
@ -86,7 +90,7 @@ const descriptor = iced(function descriptor(object) {
});
return value;
});
Loader.descriptor = descriptor;
exports.descriptor = descriptor;
// Freeze important built-ins so they can't be used by untrusted code as a
// message passing channel.
@ -123,15 +127,15 @@ const override = iced(function override(target, source) {
});
return define({}, properties);
});
Loader.override = override;
exports.override = override;
function sourceURI(uri) { return String(uri).split(" -> ").pop(); }
Loader.sourceURI = iced(sourceURI);
exports.sourceURI = iced(sourceURI);
function isntLoaderFrame(frame) { return frame.fileName !== module.uri }
function parseURI(uri) { return String(uri).split(" -> ").pop(); }
Loader.parseURI = parseURI;
exports.parseURI = parseURI;
function parseStack(stack) {
let lines = String(stack).split("\n");
@ -154,7 +158,7 @@ function parseStack(stack) {
return frames;
}, []);
}
Loader.parseStack = parseStack;
exports.parseStack = parseStack;
function serializeStack(frames) {
return frames.reduce(function(stack, frame) {
@ -165,7 +169,7 @@ function serializeStack(frames) {
stack;
}, "");
}
Loader.serializeStack = serializeStack;
exports.serializeStack = serializeStack;
function readURI(uri) {
let stream = NetUtil.newChannel(uri, 'UTF-8', null).open();
@ -190,7 +194,7 @@ function join (...paths) {
resolved = resolved.replace(/^chrome\:\/([^\/])/, 'chrome://$1');
return resolved;
}
Loader.join = join;
exports.join = join;
// Function takes set of options and returns a JS sandbox. Function may be
// passed set of options:
@ -242,7 +246,7 @@ const Sandbox = iced(function Sandbox(options) {
return sandbox;
});
Loader.Sandbox = Sandbox;
exports.Sandbox = Sandbox;
// Evaluates code from the given `uri` into given `sandbox`. If
// `options.source` is passed, then that code is evaluated instead.
@ -262,7 +266,7 @@ const evaluate = iced(function evaluate(sandbox, uri, options) {
return source ? Cu.evalInSandbox(source, sandbox, version, uri, line)
: loadSubScript(uri, sandbox, encoding);
});
Loader.evaluate = evaluate;
exports.evaluate = evaluate;
// Populates `exports` of the given CommonJS `module` object, in the context
// of the given `loader` by evaluating code associated with it.
@ -295,8 +299,7 @@ const load = iced(function load(loader, module) {
descriptors[name] = getOwnPropertyDescriptor(globals, name)
});
define(sandbox, descriptors);
}
else {
} else {
sandbox = Sandbox({
name: module.uri,
prototype: create(globals, descriptors),
@ -313,8 +316,7 @@ const load = iced(function load(loader, module) {
try {
evaluate(sandbox, module.uri);
}
catch (error) {
} catch (error) {
let { message, fileName, lineNumber } = error;
let stack = error.stack || Error().stack;
let frames = parseStack(stack).filter(isntLoaderFrame);
@ -352,19 +354,12 @@ const load = iced(function load(loader, module) {
});
}
if (loader.checkCompatibility) {
let err = incompatibility(module);
if (err) {
throw err;
}
}
if (module.exports && typeof(module.exports) === 'object')
freeze(module.exports);
return module;
});
Loader.load = load;
exports.load = load;
// Utility function to normalize module `uri`s so they have `.js` extension.
function normalizeExt (uri) {
@ -401,7 +396,7 @@ const resolve = iced(function resolve(id, base) {
return resolved;
});
Loader.resolve = resolve;
exports.resolve = resolve;
// Node-style module lookup
// Takes an id and path and attempts to load a file using node's resolving
@ -410,7 +405,7 @@ Loader.resolve = resolve;
// http://nodejs.org/api/modules.html#modules_all_together
const nodeResolve = iced(function nodeResolve(id, requirer, { rootURI }) {
// Resolve again
id = Loader.resolve(id, requirer);
id = exports.resolve(id, requirer);
// we assume that extensions are correct, i.e., a directory doesnt't have '.js'
// and a js file isn't named 'file.json.js'
@ -439,7 +434,7 @@ const nodeResolve = iced(function nodeResolve(id, requirer, { rootURI }) {
// with `resolveURI` -- if during runtime, then `resolve` will throw.
return void 0;
});
Loader.nodeResolve = nodeResolve;
exports.nodeResolve = nodeResolve;
// Attempts to load `path` and then `path.js`
// Returns `path` with valid file, or `undefined` otherwise
@ -536,7 +531,7 @@ const resolveURI = iced(function resolveURI(id, mapping) {
}
return void 0; // otherwise we raise a warning, see bug 910304
});
Loader.resolveURI = resolveURI;
exports.resolveURI = resolveURI;
// Creates version of `require` that will be exposed to the given `module`
// in the context of the given `loader`. Each module gets own limited copy
@ -650,7 +645,7 @@ const Require = iced(function Require(loader, requirer) {
// found in the paths most likely, like `sdk/tabs`, which should
// be resolved relatively if needed using traditional resolve
if (!requirement) {
requirement = isRelative(id) ? Loader.resolve(id, requirer.id) : id;
requirement = isRelative(id) ? exports.resolve(id, requirer.id) : id;
}
} else {
// Resolve `id` to its requirer if it's relative.
@ -677,7 +672,7 @@ const Require = iced(function Require(loader, requirer) {
require.main = loader.main === requirer ? requirer : undefined;
return iced(require);
});
Loader.Require = Require;
exports.Require = Require;
const main = iced(function main(loader, id) {
// If no main entry provided, and native loader is used,
@ -688,7 +683,7 @@ const main = iced(function main(loader, id) {
let module = loader.main = loader.modules[uri] = Module(id, uri);
return loader.load(loader, module).exports;
});
Loader.main = main;
exports.main = main;
// Makes module object that is made available to CommonJS modules when they
// are evaluated, along with `exports` and `require`.
@ -699,7 +694,7 @@ const Module = iced(function Module(id, uri) {
uri: { value: uri }
});
});
Loader.Module = Module;
exports.Module = Module;
// Takes `loader`, and unload `reason` string and notifies all observers that
// they should cleanup after them-self.
@ -714,7 +709,7 @@ const unload = iced(function unload(loader, reason) {
let subject = { wrappedJSObject: loader.destructor };
notifyObservers(subject, 'sdk:loader:destroy', reason);
});
Loader.unload = unload;
exports.unload = unload;
// Function makes new loader that can be used to load CommonJS modules
// described by a given `options.manifest`. Loader takes following options:
@ -729,25 +724,24 @@ Loader.unload = unload;
// module object (that has `uri` property) and `baseURI` of the loader.
// If `resolve` does not returns `uri` string exception will be thrown by
// an associated `require` call.
function Loader(options) {
const Loader = iced(function Loader(options) {
let console = new ConsoleAPI({
consoleID: options.id ? "addon/" + options.id : ""
});
let {
modules, globals, resolve, paths, rootURI, manifest, requireMap, isNative,
metadata, sharedGlobal, sharedGlobalBlacklist, checkCompatibility
metadata, sharedGlobal, sharedGlobalBlacklist
} = override({
paths: {},
modules: {},
globals: {
console: console
},
checkCompatibility: false,
resolve: options.isNative ?
// Make the returned resolve function have the same signature
(id, requirer) => Loader.nodeResolve(id, requirer, { rootURI: rootURI }) :
Loader.resolve,
(id, requirer) => exports.nodeResolve(id, requirer, { rootURI: rootURI }) :
exports.resolve,
sharedGlobalBlacklist: ["sdk/indexed-db"]
}, options);
@ -824,7 +818,6 @@ function Loader(options) {
invisibleToDebugger: { enumerable: false,
value: options.invisibleToDebugger || false },
load: { enumerable: false, value: options.load || load },
checkCompatibility: { enumerable: false, value: checkCompatibility },
// Main (entry point) module, it can be set only once, since loader
// instance can have only one main module.
main: new function() {
@ -846,8 +839,8 @@ function Loader(options) {
}
return freeze(create(null, returnObj));
};
Loader.Loader = Loader;
});
exports.Loader = Loader;
let isJSONURI = uri => uri.substr(-5) === '.json';
let isJSMURI = uri => uri.substr(-4) === '.jsm';
@ -860,7 +853,7 @@ let isRelative = id => id[0] === '.'
const generateMap = iced(function generateMap(options, callback) {
let { rootURI, resolve, paths } = override({
paths: {},
resolve: Loader.nodeResolve
resolve: exports.nodeResolve
}, options);
rootURI = addTrailingSlash(rootURI);
@ -882,7 +875,7 @@ const generateMap = iced(function generateMap(options, callback) {
}, {}, callback);
});
Loader.generateMap = generateMap;
exports.generateMap = generateMap;
// Default `main` entry to './index.js' and ensure is relative,
// since node allows 'lib/index.js' without relative `./`
@ -988,5 +981,4 @@ function isRequire (node) {
&& node.arguments[0].type === 'Literal';
}
module.exports = iced(Loader);
});

View File

@ -12,49 +12,16 @@ const make = (exports, rootURI, components) => {
}
});
// Implement require.unload(uri) that can be used to unload
// already loaded module which is convinient during development phase.
const unload = uri => {
delete loader.sandboxes[uri];
delete loader.modules[uri];
};
const builtins = new Set(Object.keys(loader.modules));
// Below we define `require` & `require.resolve` that resolve passed
// module id relative to the caller URI. This is not perfect but good
// enough for common case & there is always an option to pass absolute
// id when that
// but presumably well enough to cover
const require = (id, options={}) => {
const { reload, all } = options;
const require = id => {
const requirerURI = components.stack.caller.filename;
const requirer = Module(requirerURI, requirerURI);
const require = Require(loader, requirer);
if (reload) {
// To load JS code into modules, loader uses `mozIJSSubScriptLoader`
// which uses startup cache to avoid reading source from the same URI
// more than once. Unless we invalidate statup cache changes to a module
// won't be reflected even after reload. Therefor we must dispatch an
// nsIObserverService notification that causes cache invalidation.
// Note: This is not ideal since it destroys whole cache, but since there
// is no way to invalidate individual entries, we assume performance hit
// during development is acceptable.
components.classes["@mozilla.org/observer-service;1"].
getService(components.interfaces.nsIObserverService).
notifyObservers({}, "startupcache-invalidate", null);
if (all) {
for (let uri of Object.keys(loader.sandboxes)) {
unload(uri);
}
}
else {
unload(require.resolve(id));
}
}
return require(id);
return Require(loader, requirer)(id);
};
require.resolve = id => {

View File

@ -7,10 +7,9 @@ var EXPORTED_SYMBOLS = ["Startup"];
const { utils: Cu, interfaces: Ci, classes: Cc } = Components;
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
const { XulApp } = Cu.import("resource://gre/modules/sdk/system/XulApp.js", {});
const { defer } = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
const { XulApp } = Cu.import("resource://gre/modules/commonjs/sdk/system/xul-app.jsm", {});
const appStartupSrv = Cc["@mozilla.org/toolkit/app-startup;1"]
.getService(Ci.nsIAppStartup);

View File

@ -10,19 +10,8 @@ var { classes: Cc, interfaces: Ci } = Components;
var exports = {};
var XulApp = exports;
var appInfo;
// NOTE: below is required to avoid failing xpcshell tests,
// which do not implement nsIXULAppInfo
// See Bug 1114752 https://bugzilla.mozilla.org/show_bug.cgi?id=1114752
try {
appInfo = Cc["@mozilla.org/xre/app-info;1"]
var appInfo = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo);
}
catch (e) {
// xpcshell test case
appInfo = {};
}
var vc = Cc["@mozilla.org/xpcom/version-comparator;1"]
.getService(Ci.nsIVersionComparator);
@ -39,10 +28,13 @@ var platformVersion = exports.platformVersion = appInfo.platformVersion;
// re-branded versions of a product have different names: for instance,
// Firefox, Minefield, Iceweasel, and Shiretoko all have the same
// GUID.
// This mapping is duplicated in `app-extensions/bootstrap.js`. They should keep
// in sync, so if you change one, change the other too!
var ids = exports.ids = {
Firefox: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
Mozilla: "{86c18b42-e466-45a9-ae7a-9b95ba6f5640}",
Sunbird: "{718e30fb-e89b-41dd-9da7-e25a45638b28}",
SeaMonkey: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
Fennec: "{aa3c5121-dab2-40e2-81ca-7ea25febc110}",
Thunderbird: "{3550f703-e582-4d05-9a08-453d09bdfdc6}"
@ -191,51 +183,3 @@ function satisfiesVersion(version, versionRange) {
});
}
exports.satisfiesVersion = satisfiesVersion;
/**
* Ensure the current application satisfied the requirements specified in the
* module given. If not, an exception related to the incompatibility is
* returned; `null` otherwise.
*
* @param {Object} module
* The module to check
* @returns {Error}
*/
function incompatibility(module) {
let { metadata, id } = module;
// if metadata or engines are not specified we assume compatibility is not
// an issue.
if (!metadata || !("engines" in metadata))
return null;
let { engines } = metadata;
if (engines === null || typeof(engines) !== "object")
return new Error("Malformed engines' property in metadata");
let applications = Object.keys(engines);
let versionRange;
applications.forEach(function(name) {
if (is(name)) {
versionRange = engines[name];
// Continue iteration. We want to ensure the module doesn't
// contain a typo in the applications' name or some unknown
// application - `is` function throws an exception in that case.
}
});
if (typeof(versionRange) === "string") {
if (satisfiesVersion(versionRange))
return null;
return new Error("Unsupported Application version: The module " + id +
" currently supports only version " + versionRange + " of " +
name + ".");
}
return new Error("Unsupported Application: The module " + id +
" currently supports only " + applications.join(", ") + ".")
}
exports.incompatibility = incompatibility;

View File

@ -6,4 +6,5 @@
EXTRA_JS_MODULES.sdk.system += [
'Startup.js',
'XulApp.js',
]

View File

@ -1,36 +1,11 @@
{
"name": "addon-sdk",
"description": "Add-on development made easy.",
"keywords": [
"javascript", "engine", "addon", "extension",
"xulrunner", "firefox", "browser"
],
"license": "MPL 2.0",
"unpack": true,
"scripts": {
"test": "node ./bin/jpm-test.js",
"modules": "node ./bin/jpm-test.js --type modules",
"addons": "node ./bin/jpm-test.js --type addons",
"examples": "node ./bin/jpm-test.js --type examples"
},
"homepage": "https://github.com/mozilla/addon-sdk",
"repository": {
"type": "git",
"url": "git://github.com/mozilla/addon-sdk.git"
},
"version": "0.1.18",
"main": "./lib/index.js",
"loader": "lib/sdk/loader/cuddlefish.js",
"devDependencies": {
"async": "0.2.10",
"chai": "1.9.2",
"glob": "4.0.6",
"jpm": "0.0.21",
"lodash": "2.4.1",
"mocha": "1.21.5",
"promise": "6.0.1",
"rimraf": "2.2.8",
"unzip": "0.1.9",
"xmldom": "0.1.19"
}
"name": "addon-sdk",
"description": "Add-on development made easy.",
"keywords": [
"javascript", "engine", "addon", "extension",
"xulrunner", "firefox", "browser"
],
"loader": "lib/sdk/loader/cuddlefish.js",
"license": "MPL 2.0",
"unpack": true
}

View File

@ -417,9 +417,6 @@ class ManifestBuilder:
# test-securable-module.js, and the modules/red.js
# that it imports, both do that intentionally
continue
if reqname.endswith(".jsm"):
# ignore JSM modules
continue
if not self.abort_on_missing:
# print a warning, but tolerate missing modules
# unless cfx --abort-on-missing-module flag was set
@ -805,3 +802,4 @@ if __name__ == '__main__':
sys.exit(1)
print "requires: %s" % (",".join(sorted(requires.keys())))
print "locations: %s" % locations

View File

@ -84,7 +84,7 @@ def validate_resource_hostname(name):
"""
# See https://bugzilla.mozilla.org/show_bug.cgi?id=568131 for details.
if not name.lower() == name:
if not name.islower():
raise ValueError("""Error: the name of your package contains upper-case letters.
Package names can contain only lower-case letters, numbers, underscores, and dashes.
Current package name: %s""" % name)

View File

@ -36,23 +36,17 @@ DEFAULT_COMMON_PREFS = {
# shut up some warnings on `about:` page
'app.releaseNotesURL': 'http://localhost/app-dummy/',
'app.vendorURL': 'http://localhost/app-dummy/',
# Don't prompt about e10s
'browser.displayedE10SPrompt.1': 5
'app.vendorURL': 'http://localhost/app-dummy/'
}
DEFAULT_NO_CONNECTIONS_PREFS = {
'toolkit.telemetry.enabled': False,
'toolkit.telemetry.server': 'https://localhost/telemetry-dummy/',
'app.update.auto' : False,
'app.update.url': 'http://localhost/app-dummy/update',
# Make sure GMPInstallManager won't hit the network.
'media.gmp-gmpopenh264.autoupdate' : False,
'media.gmp-manager.cert.checkAttributes' : False,
'media.gmp-manager.cert.requireBuiltIn' : False,
'media.gmp-manager.url' : 'http://localhost/media-dummy/gmpmanager',
'media.gmp-manager.url.override': 'http://localhost/dummy-gmp-manager.xml',
'browser.newtab.url' : 'about:blank',
'browser.search.update': False,
'browser.safebrowsing.enabled' : False,
@ -60,12 +54,9 @@ DEFAULT_NO_CONNECTIONS_PREFS = {
'browser.safebrowsing.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
'browser.safebrowsing.reportURL': 'http://localhost/safebrowsing-dummy/report',
'browser.safebrowsing.malware.reportURL': 'http://localhost/safebrowsing-dummy/malwarereport',
'browser.trackingprotection.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
'browser.trackingprotection.updateURL': 'http://localhost/safebrowsing-dummy/update',
# Disable app update
'app.update.enabled' : False,
'app.update.staging.enabled': False,
# Disable about:newtab content fetch and ping
'browser.newtabpage.directory.source': 'data:application/json,{"jetpack":1}',
@ -73,31 +64,9 @@ DEFAULT_NO_CONNECTIONS_PREFS = {
# Point update checks to a nonexistent local URL for fast failures.
'extensions.update.url' : 'http://localhost/extensions-dummy/updateURL',
'extensions.update.background.url': 'http://localhost/extensions-dummy/updateBackgroundURL',
'extensions.blocklist.url' : 'http://localhost/extensions-dummy/blocklistURL',
# Make sure opening about:addons won't hit the network.
'extensions.webservice.discoverURL' : 'http://localhost/extensions-dummy/discoveryURL',
'extensions.getAddons.maxResults': 0,
# Disable webapp updates. Yes, it is supposed to be an integer.
'browser.webapps.checkForUpdates': 0,
# Location services
'geo.wifi.uri': 'http://localhost/location-dummy/locationURL',
'browser.search.geoip.url': 'http://localhost/location-dummy/locationURL',
# Tell the search service we are running in the US. This also has the
# desired side-effect of preventing our geoip lookup.
'browser.search.isUS' : True,
'browser.search.countryCode' : 'US',
'geo.wifi.uri' : 'http://localhost/extensions-dummy/geowifiURL',
'geo.wifi.scan' : False,
# We don't want to hit the real Firefox Accounts server for tests. We don't
# actually need a functioning FxA server, so just set it to something that
# resolves and accepts requests, even if they all fail.
'identity.fxaccounts.auth.uri': 'http://localhost/fxa-dummy/'
'extensions.webservice.discoverURL' : 'http://localhost/extensions-dummy/discoveryURL'
}
DEFAULT_FENNEC_PREFS = {
@ -109,7 +78,6 @@ DEFAULT_FENNEC_PREFS = {
DEFAULT_FIREFOX_PREFS = {
'browser.startup.homepage' : 'about:blank',
'startup.homepage_welcome_url' : 'about:blank',
'devtools.browsertoolbox.panel': 'jsdebugger',
'devtools.errorconsole.enabled' : True,
'devtools.chrome.enabled' : True,
@ -177,69 +145,6 @@ DEFAULT_THUNDERBIRD_PREFS = {
}
DEFAULT_TEST_PREFS = {
'browser.console.showInPanel': True,
'browser.startup.page': 0,
'browser.firstrun.show.localepicker': False,
'browser.firstrun.show.uidiscovery': False,
'browser.ui.layout.tablet': 0,
'dom.disable_open_during_load': False,
'dom.experimental_forms': True,
'dom.forms.number': True,
'dom.forms.color': True,
'dom.max_script_run_time': 0,
'hangmonitor.timeout': 0,
'dom.max_chrome_script_run_time': 0,
'dom.popup_maximum': -1,
'dom.send_after_paint_to_content': True,
'dom.successive_dialog_time_limit': 0,
'browser.shell.checkDefaultBrowser': False,
'shell.checkDefaultClient': False,
'browser.warnOnQuit': False,
'accessibility.typeaheadfind.autostart': False,
'browser.EULA.override': True,
'gfx.color_management.force_srgb': True,
'network.manage-offline-status': False,
# Disable speculative connections so they aren't reported as leaking when they're hanging around.
'network.http.speculative-parallel-limit': 0,
'test.mousescroll': True,
# Need to client auth test be w/o any dialogs
'security.default_personal_cert': 'Select Automatically',
'network.http.prompt-temp-redirect': False,
'security.warn_viewing_mixed': False,
'browser.panorama.experienced_first_run': True,
# Set a future policy version to avoid the telemetry prompt.
'toolkit.telemetry.prompted': 999,
'toolkit.telemetry.notifiedOptOut': 999,
'extensions.defaultProviders.enabled': True,
'datareporting.policy.dataSubmissionPolicyBypassNotification': True,
'layout.css.report_errors': True,
'layout.css.grid.enabled': True,
'layout.spammy_warnings.enabled': False,
'dom.mozSettings.enabled': True,
# Make sure the disk cache doesn't get auto disabled
'network.http.bypass-cachelock-threshold': 200000,
# Always use network provider for geolocation tests
# so we bypass the OSX dialog raised by the corelocation provider
'geo.provider.testing': True,
# Background thumbnails in particular cause grief, and disabling thumbnails
# in general can't hurt - we re-enable them when tests need them.
'browser.pagethumbnails.capturing_disabled': True,
# Indicate that the download panel has been shown once so that whichever
# download test runs first doesn't show the popup inconsistently.
'browser.download.panel.shown': True,
# Assume the about:newtab page's intro panels have been shown to not depend on
# which test runs first and happens to open about:newtab
'browser.newtabpage.introShown': True,
# Disable useragent updates.
'general.useragent.updates.enabled': False,
'dom.mozApps.debug': True,
'dom.apps.customization.enabled': True,
'media.eme.enabled': True,
# Don't forceably kill content processes after a timeout
'dom.ipc.tabs.shutdownTimeoutSecs': 0,
# Don't show the search first run UI by default
'browser.search.highlightCount': 0,
'general.useragent.locale': "en-US",
'intl.locale.matchOS': "en-US",
'dom.indexedDB.experimental': True
'intl.locale.matchOS': "en-US"
}

View File

@ -418,10 +418,6 @@ def run_app(harness_root_dir, manifest_rdf, harness_options,
if enable_e10s:
preferences['browser.tabs.remote.autostart'] = True
else:
preferences['browser.tabs.remote.autostart'] = False
preferences['browser.tabs.remote.autostart.1'] = False
preferences['browser.tabs.remote.autostart.2'] = False
# For now, only allow running on Mobile with --force-mobile argument
if app_type in ["fennec-on-device"] and not enable_mobile:

View File

@ -1,8 +0,0 @@
/* 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";
module.exports = require("./test-main.js");
require('sdk/test/runner').runTestsFromModule(module);

View File

@ -1,7 +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";
'use strict';
const { id } = require("sdk/self");
const { getAddonByID } = require("sdk/addon/manager");
@ -10,3 +10,5 @@ exports["test getAddonByID"] = function*(assert) {
let addon = yield getAddonByID(id);
assert.equal(addon.id, id, "getAddonByID works");
}
require('sdk/test/runner').runTestsFromModule(module);

Some files were not shown because too many files have changed in this diff Show More