зеркало из https://github.com/mozilla/gecko-dev.git
merge fx-team to mozilla-central a=merge
This commit is contained in:
Коммит
d34a78bdd7
|
@ -9,8 +9,6 @@
|
|||
# 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']
|
||||
|
@ -21,7 +19,6 @@ EXTRA_JS_MODULES.sdk += [
|
|||
|
||||
EXTRA_JS_MODULES.sdk.system += [
|
||||
'source/modules/system/Startup.js',
|
||||
'source/modules/system/XulApp.js',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk":
|
||||
|
@ -39,6 +36,7 @@ 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',
|
||||
|
@ -54,7 +52,6 @@ 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 += [
|
||||
|
@ -99,6 +96,7 @@ 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',
|
||||
|
@ -133,13 +131,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',
|
||||
]
|
||||
|
||||
|
@ -173,10 +171,13 @@ 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 += [
|
||||
|
@ -191,6 +192,7 @@ 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',
|
||||
|
@ -218,6 +220,7 @@ 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',
|
||||
|
@ -246,10 +249,15 @@ 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',
|
||||
|
@ -395,6 +403,7 @@ 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 += [
|
||||
|
@ -426,12 +435,17 @@ 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',
|
||||
|
@ -439,7 +453,6 @@ 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',
|
||||
|
|
|
@ -14,5 +14,4 @@ EXTRA_JS_MODULES.sdk += [
|
|||
|
||||
EXTRA_JS_MODULES.sdk.system += [
|
||||
'source/modules/system/Startup.js',
|
||||
'source/modules/system/XulApp.js',
|
||||
]
|
||||
|
|
|
@ -8,6 +8,7 @@ doc/index.html
|
|||
doc/modules/
|
||||
doc/status.md5
|
||||
packages/*
|
||||
node_modules
|
||||
|
||||
# Python
|
||||
*.pyc
|
||||
|
@ -17,4 +18,3 @@ packages/*
|
|||
|
||||
# Windows
|
||||
*Thumbs.db
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ 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
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
local.json
|
||||
mapping.json
|
||||
CONTRIBUTING.md
|
||||
@addon-sdk.xpi
|
||||
.*
|
||||
app-extension/
|
||||
bin/
|
||||
modules/
|
||||
node_modules/
|
||||
examples/
|
||||
|
||||
# Python
|
||||
python-lib/
|
||||
*.pyc
|
||||
|
||||
# Windows
|
||||
*Thumbs.db
|
|
@ -0,0 +1,23 @@
|
|||
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
|
|
@ -1,41 +0,0 @@
|
|||
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
|
|
@ -0,0 +1,31 @@
|
|||
# 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)
|
|
@ -0,0 +1,28 @@
|
|||
/* 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);
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
/* 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());
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"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());
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/* 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);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,70 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"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;
|
|
@ -0,0 +1,13 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
// 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);
|
|
@ -0,0 +1,10 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
exports.testMain = function(assert) {
|
||||
assert.pass("TODO: Write some tests.");
|
||||
};
|
||||
|
||||
require("sdk/test").run(exports);
|
|
@ -1,9 +1,11 @@
|
|||
{
|
||||
"license": "MPL 2.0",
|
||||
"name": "annotator",
|
||||
"contributors": [],
|
||||
"author": "Will Bamberg",
|
||||
"keywords": [],
|
||||
"id": "anonid0-annotator",
|
||||
"description": "Add notes to Web pages"
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
exports.testMain = function(test) {
|
||||
test.pass("TODO: Write some tests.");
|
||||
exports.testMain = function(assert) {
|
||||
assert.pass("TODO: Write some tests.");
|
||||
};
|
||||
|
||||
require("sdk/test").run(exports);
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
exports.testMain = function(assert) {
|
||||
assert.pass("TODO: Write some tests.");
|
||||
};
|
||||
|
||||
require("sdk/test").run(exports);
|
|
@ -1,9 +1,10 @@
|
|||
{
|
||||
"name": "library-detector-sdk",
|
||||
"license": "MPL 2.0",
|
||||
"author": "",
|
||||
"version": "0.1",
|
||||
"title": "library-detector-sdk",
|
||||
"id": "jid1-R4rSVNkBANnvGQ",
|
||||
"description": "a basic add-on"
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
exports.testMain = function(test) {
|
||||
test.pass("TODO: Write some tests.");
|
||||
exports.testMain = function(assert) {
|
||||
assert.pass("TODO: Write some tests.");
|
||||
};
|
||||
|
||||
require("sdk/test").run(exports);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"description": "a toolbar api example",
|
||||
"author": "",
|
||||
"license": "MPL 2.0",
|
||||
"version": "0.1",
|
||||
"version": "0.1.1",
|
||||
"engines": {
|
||||
"firefox": ">=27.0 <=30.0"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
exports.testMain = function(assert) {
|
||||
assert.pass("TODO: Write some tests.");
|
||||
};
|
||||
|
||||
require("sdk/test").run(exports);
|
|
@ -5,5 +5,6 @@
|
|||
"description": "A Button API example",
|
||||
"author": "jeff@canuckistani.ca (Jeff Griffiths | @canuckistani)",
|
||||
"license": "MPL 2.0",
|
||||
"version": "0.1"
|
||||
"version": "0.1.1",
|
||||
"main": "./lib/main.js"
|
||||
}
|
||||
|
|
|
@ -3,7 +3,15 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var { actionButton, toggleButton, icon } = require("main");
|
||||
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 self = require("sdk/self");
|
||||
|
||||
exports.testActionButton = function(assert) {
|
||||
|
|
|
@ -16,11 +16,13 @@ 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 = id.prototype.id || id.id || id;
|
||||
id = getId(id);
|
||||
return gDevTools.showToolbox(targetFor(tab), id);
|
||||
};
|
||||
exports.openToolbox = openToolbox;
|
||||
|
@ -32,7 +34,7 @@ const getToolbox = tab => gDevTools.getToolbox(targetFor(tab));
|
|||
exports.getToolbox = getToolbox;
|
||||
|
||||
const openToolboxPanel = (id, tab) => {
|
||||
id = id.prototype.id || id.id || id;
|
||||
id = getId(id);
|
||||
return gDevTools.showToolbox(targetFor(tab), id).then(getCurrentPanel);
|
||||
};
|
||||
exports.openToolboxPanel = openToolboxPanel;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.volcan=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
|
||||
"use strict";
|
||||
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
/* 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 { 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;
|
|
@ -0,0 +1,26 @@
|
|||
/* 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;
|
|
@ -0,0 +1,25 @@
|
|||
/* 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;
|
|
@ -0,0 +1,3 @@
|
|||
/* 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/. */
|
|
@ -0,0 +1,160 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { 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;
|
|
@ -9,7 +9,8 @@ module.metadata = {
|
|||
"engines": {
|
||||
// TODO Fennec Support 789757
|
||||
"Firefox": "*",
|
||||
"SeaMonkey": "*"
|
||||
"SeaMonkey": "*",
|
||||
"Thunderbird": "*"
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -124,26 +125,24 @@ 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)
|
||||
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);
|
||||
}
|
||||
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);
|
||||
break;
|
||||
|
||||
// Set images to the clipboard is not straightforward, to have an idea how
|
||||
|
|
|
@ -37,7 +37,17 @@ const WorkerChild = Class({
|
|||
this.receive = this.receive.bind(this);
|
||||
this.manager.addMessageListener('sdk/worker/message', this.receive);
|
||||
|
||||
this.sandbox = WorkerSandbox(this, getByInnerId(this.window));
|
||||
let window = getByInnerId(this.window);
|
||||
this.sandbox = WorkerSandbox(this, window);
|
||||
|
||||
if (options.currentReadyState != "complete" &&
|
||||
window.document.readyState == "complete") {
|
||||
// If we attempted to attach the worker before the document was loaded but
|
||||
// it has now completed loading then the parent should reasonably expect
|
||||
// to see a pageshow event.
|
||||
this.sandbox.emitSync("pageshow");
|
||||
this.send("pageshow");
|
||||
}
|
||||
},
|
||||
// messages
|
||||
receive({ data: { id, args }}) {
|
||||
|
|
|
@ -1,184 +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 { 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 }));
|
|
@ -7,280 +7,179 @@ module.metadata = {
|
|||
"stability": "unstable"
|
||||
};
|
||||
|
||||
const { emit } = require('../event/core');
|
||||
const { omit } = require('../util/object');
|
||||
const { Class } = require('../core/heritage');
|
||||
const { EventTarget } = require('../event/target');
|
||||
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 { 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']);
|
||||
|
||||
// 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.";
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
// a handle for communication between content script and addon code
|
||||
const Worker = Class({
|
||||
implements: [EventTarget],
|
||||
initialize: function WorkerConstructor (options) {
|
||||
// Save model in weak map to not expose properties
|
||||
let model = createModel();
|
||||
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);
|
||||
|
||||
options = options || {};
|
||||
ensure(this, 'destroy');
|
||||
this.on('detach', this.detach);
|
||||
EventTarget.prototype.initialize.call(this, 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;
|
||||
this.receive = this.receive.bind(this);
|
||||
|
||||
setListeners(this, options);
|
||||
model.observe = ({ subject }) => {
|
||||
let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
|
||||
if (model.window && getInnerId(model.window) === id)
|
||||
this.detach();
|
||||
}
|
||||
|
||||
unload.ensure(this, "destroy");
|
||||
observe('inner-window-destroyed', model.observe);
|
||||
|
||||
// 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);
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
// 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);
|
||||
let args = ['message'].concat(data);
|
||||
if (!model.inited) {
|
||||
model.earlyEvents.push(args);
|
||||
return;
|
||||
}
|
||||
processMessage.apply(null, [this].concat(args));
|
||||
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) {
|
||||
//
|
||||
}
|
||||
},
|
||||
|
||||
get url () {
|
||||
let model = modelFor(this);
|
||||
// model.window will be null after detach
|
||||
return model.window ? model.window.document.location.href : null;
|
||||
// properties
|
||||
get url() {
|
||||
let { window } = modelFor(this);
|
||||
return window && window.document.location.href;
|
||||
},
|
||||
|
||||
get contentURL () {
|
||||
let model = modelFor(this);
|
||||
return model.window ? model.window.document.URL : null;
|
||||
get contentURL() {
|
||||
let { window } = modelFor(this);
|
||||
return window && window.document.URL;
|
||||
},
|
||||
|
||||
get tab () {
|
||||
let model = modelFor(this);
|
||||
// model.window will be null after detach
|
||||
if (model.window)
|
||||
return getTabForWindow(model.window);
|
||||
return null;
|
||||
get tab() {
|
||||
let { window } = modelFor(this);
|
||||
return window && getTabForWindow(window);
|
||||
},
|
||||
|
||||
// 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]'; },
|
||||
toString: () => '[object Worker]',
|
||||
// methods
|
||||
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;
|
||||
// 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);
|
||||
model.options.window = getInnerId(window);
|
||||
model.options.currentReadyState = window.document.readyState;
|
||||
model.id = model.options.id = String(uuid());
|
||||
|
||||
// will set model.contentWorker pointing to the private API:
|
||||
model.contentWorker = WorkerSandbox(worker, model.window);
|
||||
let tab = getTabForContentWindow(window);
|
||||
if (tab) {
|
||||
model.manager = getBrowserForTab(tab).messageManager;
|
||||
} else {
|
||||
model.manager = getFrameElement(window.top).frameLoader.messageManager;
|
||||
}
|
||||
|
||||
// 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.addMessageListener('sdk/worker/event', worker.receive);
|
||||
model.manager.addMessageListener('sdk/worker/attach', attach);
|
||||
|
||||
// Mainly enable worker.port.emit to send event to the content worker
|
||||
model.inited = true;
|
||||
model.frozen = false;
|
||||
model.manager.sendAsyncMessage('sdk/worker/create', {
|
||||
options: model.options,
|
||||
addon: ADDON
|
||||
});
|
||||
|
||||
// Fire off `attach` event
|
||||
emit(worker, 'attach', window);
|
||||
function attach({ data }) {
|
||||
if (data.id !== model.id)
|
||||
return;
|
||||
model.manager.removeMessageListener('sdk/worker/attach', attach);
|
||||
model.childWorker = true;
|
||||
|
||||
// Process all events and messages that were fired before the
|
||||
// worker was initialized.
|
||||
model.earlyEvents.forEach(args => processMessage.apply(null, [worker].concat(args)));
|
||||
});
|
||||
worker.on('pageshow', () => model.frozen = false);
|
||||
worker.on('pagehide', () => model.frozen = true);
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
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;
|
||||
|
||||
// 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.childWorker = null;
|
||||
model.earlyEvents = [];
|
||||
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;
|
||||
});
|
||||
emit(worker, 'detach');
|
||||
model.manager.removeMessageListener('sdk/worker/event', this.receive);
|
||||
})
|
||||
|
||||
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) {
|
||||
// unlod worker, release references
|
||||
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));
|
||||
}
|
||||
// unload Loaders used for creating WorkerChild instances in each process
|
||||
when(() => ppmm.broadcastAsyncMessage('sdk/loader/unload', { data: ADDON }));
|
||||
|
|
|
@ -21,7 +21,6 @@ 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');
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const { 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;
|
|
@ -0,0 +1,384 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"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);
|
|
@ -0,0 +1,112 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
const { 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;
|
|
@ -0,0 +1,32 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
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);
|
|
@ -52,11 +52,13 @@ 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.
|
||||
|
@ -129,4 +131,3 @@ uninstall.define(Disposable, dispose);
|
|||
// increase shutdown time. Although specefic components may choose
|
||||
// to implement shutdown handler that does something better.
|
||||
shutdown.define(Disposable, disposable => {});
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { Trait } = require("../light-traits");
|
||||
const { Class } = require("../../core/heritage");
|
||||
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 = Trait({
|
||||
exports.DOMEventAssembler = Class({
|
||||
/**
|
||||
* Function that is supposed to handle all the supported events (that are
|
||||
* present in the `supportedEventsTypes`) from all the observed
|
||||
|
@ -23,12 +23,16 @@ exports.DOMEventAssembler = Trait({
|
|||
* @param {Event} event
|
||||
* Event being dispatched.
|
||||
*/
|
||||
handleEvent: Trait.required,
|
||||
handleEvent() {
|
||||
throw new TypeError("Instance of DOMEventAssembler must implement `handleEvent` method");
|
||||
},
|
||||
/**
|
||||
* Array of supported event names.
|
||||
* @type {String[]}
|
||||
*/
|
||||
supportedEventsTypes: Trait.required,
|
||||
get supportedEventsTypes() {
|
||||
throw new TypeError("Instance of DOMEventAssembler must implement `handleEvent` field");
|
||||
},
|
||||
/**
|
||||
* Adds `eventTarget` to the list of observed `eventTarget`s. Listeners for
|
||||
* supported events will be registered on the given `eventTarget`.
|
||||
|
|
|
@ -0,0 +1,297 @@
|
|||
/* 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));
|
||||
}
|
|
@ -10,7 +10,7 @@ module.metadata = {
|
|||
const memory = require("./memory");
|
||||
const timer = require("../timers");
|
||||
const cfxArgs = require("../test/options");
|
||||
const { getTabs, closeTab, getURI } = require("../tabs/utils");
|
||||
const { getTabs, closeTab, getURI, getTabId, getSelectedTab } = 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,10 +35,16 @@ const findAndRunTests = function findAndRunTests(options) {
|
|||
exports.findAndRunTests = findAndRunTests;
|
||||
|
||||
let runnerWindows = new WeakMap();
|
||||
let runnerTabs = new WeakMap();
|
||||
|
||||
const TestRunner = function TestRunner(options) {
|
||||
options = options || {};
|
||||
runnerWindows.set(this, getInnerId(getMostRecentBrowserWindow()));
|
||||
|
||||
// remember the id's for the open window and tab
|
||||
let window = getMostRecentBrowserWindow();
|
||||
runnerWindows.set(this, getInnerId(window));
|
||||
runnerTabs.set(this, getTabId(getSelectedTab(window)));
|
||||
|
||||
this.fs = options.fs;
|
||||
this.console = options.console || console;
|
||||
memory.track(this);
|
||||
|
@ -318,15 +324,26 @@ TestRunner.prototype = {
|
|||
return all(winPromises).then(() => {
|
||||
let browserWins = wins.filter(isBrowser);
|
||||
let tabs = browserWins.reduce((tabs, window) => tabs.concat(getTabs(window)), []);
|
||||
|
||||
if (wins.length != 1 || getInnerId(wins[0]) !== runnerWindows.get(this))
|
||||
this.fail("Should not be any unexpected windows open");
|
||||
|
||||
let newTabID = getTabId(getSelectedTab(wins[0]));
|
||||
let oldTabID = runnerTabs.get(this);
|
||||
let hasMoreTabsOpen = browserWins.length && tabs.length != 1;
|
||||
if (hasMoreTabsOpen)
|
||||
this.fail("Should not be any unexpected tabs open");
|
||||
let failure = false;
|
||||
|
||||
if (hasMoreTabsOpen || wins.length != 1) {
|
||||
if (wins.length != 1 || getInnerId(wins[0]) !== runnerWindows.get(this)) {
|
||||
failure = true;
|
||||
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) {
|
||||
console.log("Windows open:");
|
||||
for (let win of wins) {
|
||||
if (isBrowser(win)) {
|
||||
|
@ -356,7 +373,7 @@ TestRunner.prototype = {
|
|||
timer.setTimeout(_ => onDone(this));
|
||||
}
|
||||
}).
|
||||
catch(e => console.exception(e));
|
||||
catch(console.exception);
|
||||
},
|
||||
|
||||
// Set of assertion functions to wait for an assertion to become true
|
||||
|
|
|
@ -192,8 +192,10 @@ const InputStream = Class({
|
|||
},
|
||||
resume: function resume() {
|
||||
this.paused = false;
|
||||
nsIInputStreamPump(this).resume();
|
||||
emit(this, "resume");
|
||||
if (nsIInputStreamPump(this).isPending()) {
|
||||
nsIInputStreamPump(this).resume();
|
||||
emit(this, "resume");
|
||||
}
|
||||
},
|
||||
close: function close() {
|
||||
this.readable = false;
|
||||
|
|
|
@ -8,8 +8,9 @@ module.metadata = {
|
|||
"stability": "unstable"
|
||||
};
|
||||
|
||||
const { Trait } = require("../deprecated/light-traits");
|
||||
const { EventEmitterTrait: EventEmitter } = require("../deprecated/events");
|
||||
const { Class } = require("../core/heritage");
|
||||
const { EventTarget } = require("../event/target");
|
||||
const { emit } = require("../event/core");
|
||||
const { DOMEventAssembler } = require("../deprecated/events/assembler");
|
||||
const { browserWindowIterator } = require('../deprecated/window-utils');
|
||||
const { isBrowser } = require('../window/utils');
|
||||
|
@ -17,12 +18,26 @@ const { observer: windowObserver } = require("../windows/observer");
|
|||
|
||||
// Event emitter objects used to register listeners and emit events on them
|
||||
// when they occur.
|
||||
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,
|
||||
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);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Events that are supported and emitted by the module.
|
||||
*/
|
||||
|
@ -34,24 +49,9 @@ const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
|
|||
* @param {Event} event
|
||||
* Keyboard event being emitted.
|
||||
*/
|
||||
handleEvent: function handleEvent(event) {
|
||||
this._emit(event.type, event, event.target.ownerDocument.defaultView);
|
||||
handleEvent(event) {
|
||||
emit(this, event.type, event, event.target.ownerDocument.defaultView);
|
||||
}
|
||||
});
|
||||
|
||||
// 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;
|
||||
exports.observer = new Observer();
|
||||
|
|
|
@ -50,7 +50,8 @@ function getPreferedLocales(caseSensitve) {
|
|||
addLocale(browserUiLocale);
|
||||
|
||||
// Third priority is the list of locales used for web content
|
||||
let contentLocales = prefs.get(PREF_ACCEPT_LANGUAGES, "");
|
||||
let contentLocales = prefs.getLocalized(PREF_ACCEPT_LANGUAGES, "") ||
|
||||
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
|
||||
|
|
|
@ -107,6 +107,18 @@ 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
|
||||
|
|
|
@ -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,7 +14,6 @@ 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;
|
||||
|
@ -23,65 +22,17 @@ 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.js");
|
||||
"system/xul-app.jsm");
|
||||
// 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 xulappSandbox = loadSandbox(xulappURI);
|
||||
const xulappModule = xulappSandbox.exports;
|
||||
const { incompatibility } = Cu.import(xulappURI, {}).XulApp;
|
||||
|
||||
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;
|
||||
|
||||
|
@ -90,8 +41,7 @@ function CuddlefishLoader(options) {
|
|||
// cache to avoid subsequent loads via `require`.
|
||||
modules: override({
|
||||
'toolkit/loader': loaderModule,
|
||||
'sdk/loader/cuddlefish': exports,
|
||||
'sdk/system/xul-app': xulappModule
|
||||
'sdk/loader/cuddlefish': exports
|
||||
}, options.modules),
|
||||
resolve: function resolve(id, requirer) {
|
||||
let entry = requirer && requirer in manifest && manifest[requirer];
|
||||
|
|
|
@ -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-parent');
|
||||
const { Worker } = require('./content/worker');
|
||||
const { EventTarget } = require('./event/target');
|
||||
const { on, emit, once, setListeners } = require('./event/core');
|
||||
const { on: domOn, removeListener: domOff } = require('./dom/events');
|
||||
|
@ -189,6 +189,10 @@ 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);
|
||||
|
@ -216,7 +220,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());
|
||||
|
@ -258,6 +262,20 @@ function onContent (mod, window) {
|
|||
return;
|
||||
domOff(window, eventName, onReady, true);
|
||||
createWorker(mod, window);
|
||||
|
||||
// Attaching is asynchronous so if the document is already loaded we will
|
||||
// miss the pageshow event so send a synthetic one.
|
||||
if (window.document.readyState == "complete") {
|
||||
mod.on('attach', worker => {
|
||||
try {
|
||||
worker.send('pageshow');
|
||||
emit(worker, 'pageshow');
|
||||
}
|
||||
catch (e) {
|
||||
// This can fail if an earlier attach listener destroyed the worker
|
||||
}
|
||||
});
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
let { deprecateUsage } = require("../util/deprecate");
|
||||
|
||||
deprecateUsage("Module 'sdk/page-mod/match-pattern' is deprecated use 'sdk/util/match-pattern' instead");
|
||||
|
|
|
@ -28,9 +28,9 @@ const { Rules } = require('./util/rules');
|
|||
const { merge } = require('./util/object');
|
||||
const { data } = require('./self');
|
||||
|
||||
const views = WeakMap();
|
||||
const workers = WeakMap();
|
||||
const pages = WeakMap();
|
||||
const views = new WeakMap();
|
||||
const workers = new WeakMap();
|
||||
const pages = new 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);
|
||||
|
|
|
@ -15,12 +15,10 @@ 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("./content/worker");
|
||||
const { Worker } = require("./deprecated/sync-worker");
|
||||
const { Disposable } = require("./core/disposable");
|
||||
const { WeakReference } = require('./core/reference');
|
||||
const { contract: loaderContract } = require("./content/loader");
|
||||
|
@ -155,7 +153,7 @@ const Panel = Class({
|
|||
|
||||
// Load panel content.
|
||||
domPanel.setURL(view, model.contentURL);
|
||||
|
||||
|
||||
// Allow context menu
|
||||
domPanel.allowContextMenu(view, model.contextMenu);
|
||||
|
||||
|
@ -195,7 +193,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) {
|
||||
|
@ -203,7 +201,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);
|
||||
|
|
|
@ -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,8 +286,7 @@ function make(document) {
|
|||
events.emit(type, { subject: panel });
|
||||
}
|
||||
|
||||
function onContentChange({subject, type}) {
|
||||
let document = subject;
|
||||
function onContentChange({subject: document, type}) {
|
||||
if (document === getContentDocument(panel) && document.defaultView)
|
||||
events.emit(type, { subject: panel });
|
||||
}
|
||||
|
@ -411,9 +410,9 @@ function setURL(panel, url) {
|
|||
exports.setURL = setURL;
|
||||
|
||||
function allowContextMenu(panel, allow) {
|
||||
if(allow) {
|
||||
if (allow) {
|
||||
panel.setAttribute("context", "contentAreaContextMenu");
|
||||
}
|
||||
}
|
||||
else {
|
||||
panel.removeAttribute("context");
|
||||
}
|
||||
|
|
|
@ -1,64 +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';
|
||||
|
||||
// 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;
|
|
@ -7,7 +7,8 @@
|
|||
module.metadata = {
|
||||
"stability": "unstable",
|
||||
"engines": {
|
||||
"Firefox": "*"
|
||||
"Firefox": "*",
|
||||
"SeaMonkey": "*"
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
module.metadata = {
|
||||
'stability': 'experimental',
|
||||
'engines': {
|
||||
'Firefox': '*'
|
||||
'Firefox': '*',
|
||||
"SeaMonkey": '*'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
module.metadata = {
|
||||
"stability": "unstable",
|
||||
"engines": {
|
||||
"Firefox": "*"
|
||||
"Firefox": "*",
|
||||
"SeaMonkey": "*"
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
module.metadata = {
|
||||
"stability": "unstable",
|
||||
"engines": {
|
||||
"Firefox": "*"
|
||||
"Firefox": "*",
|
||||
"SeaMonkey": "*"
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
module.metadata = {
|
||||
"stability": "experimental",
|
||||
"engines": {
|
||||
"Firefox": "*"
|
||||
"Firefox": "*",
|
||||
"SeaMonkey": "*"
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
module.metadata = {
|
||||
"stability": "experimental",
|
||||
"engines": {
|
||||
"Firefox": "*"
|
||||
"Firefox": "*",
|
||||
"SeaMonkey": "*"
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
module.metadata = {
|
||||
"stability": "experimental",
|
||||
"engines": {
|
||||
"Firefox": "*"
|
||||
"Firefox": "*",
|
||||
"SeaMonkey": "*"
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
module.metadata = {
|
||||
"stability": "experimental",
|
||||
"engines": {
|
||||
"Firefox": "*"
|
||||
"Firefox": "*",
|
||||
"SeaMonkey": "*"
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -123,7 +123,8 @@ function injectOptions({ preferences, preferencesBranch, document, parent, id })
|
|||
setting.setAttribute('pref', 'extensions.' + preferencesBranch + '.' + name);
|
||||
setting.setAttribute('type', type);
|
||||
setting.setAttribute('title', title);
|
||||
setting.setAttribute('desc', description);
|
||||
if (description)
|
||||
setting.setAttribute('desc', description);
|
||||
|
||||
if (type === 'file' || type === 'directory') {
|
||||
setting.setAttribute('fullpath', 'true');
|
||||
|
|
|
@ -21,106 +21,70 @@ const prefService = Cc["@mozilla.org/preferences-service;1"].
|
|||
const prefSvc = prefService.getBranch(null);
|
||||
const defaultBranch = prefService.getDefaultBranch(null);
|
||||
|
||||
function Branch(branchName) {
|
||||
function getPrefKeys() {
|
||||
return keys(branchName).map(function(key) {
|
||||
return key.replace(branchName, "");
|
||||
});
|
||||
}
|
||||
const { Preferences } = require("resource://gre/modules/Preferences.jsm");
|
||||
const prefs = new Preferences({});
|
||||
|
||||
return Proxy.create({
|
||||
get: function(receiver, pref) {
|
||||
return get(branchName + pref);
|
||||
},
|
||||
set: function(receiver, pref, val) {
|
||||
set(branchName + pref, val);
|
||||
},
|
||||
delete: function(pref) {
|
||||
reset(branchName + pref);
|
||||
return true;
|
||||
},
|
||||
has: function hasPrefKey(pref) {
|
||||
return has(branchName + pref)
|
||||
},
|
||||
getPropertyDescriptor: function(name) {
|
||||
const branchKeys = branchName =>
|
||||
keys(branchName).map($ => $.replace(branchName, ""));
|
||||
|
||||
const Branch = function(branchName) {
|
||||
return new Proxy(Branch.prototype, {
|
||||
getOwnPropertyDescriptor(target, name, receiver) {
|
||||
return {
|
||||
value: get(branchName + name)
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
value: this.get(target, name, receiver)
|
||||
};
|
||||
},
|
||||
enumerate: getPrefKeys,
|
||||
keys: getPrefKeys
|
||||
}, Branch.prototype);
|
||||
enumerate(target) {
|
||||
return branchKeys(branchName)[Symbol.iterator]();
|
||||
},
|
||||
ownKeys(target) {
|
||||
return branchKeys(branchName);
|
||||
},
|
||||
get(target, name, receiver) {
|
||||
return get(`${branchName}${name}`);
|
||||
},
|
||||
set(target, name, value, receiver) {
|
||||
set(`${branchName}${name}`, value);
|
||||
},
|
||||
has(target, name) {
|
||||
return this.hasOwn(target, name);
|
||||
},
|
||||
hasOwn(target, name) {
|
||||
return has(`${branchName}${name}`);
|
||||
},
|
||||
deleteProperty(target, name) {
|
||||
reset(`${branchName}${name}`);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function 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.");
|
||||
}
|
||||
return prefs.get(name, defaultValue);
|
||||
}
|
||||
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;
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
prefs.set(name, value);
|
||||
}
|
||||
exports.set = set;
|
||||
|
||||
function has(name) {
|
||||
return (prefSvc.getPrefType(name) != Ci.nsIPrefBranch.PREF_INVALID);
|
||||
}
|
||||
const has = prefs.has.bind(prefs)
|
||||
exports.has = has;
|
||||
|
||||
function keys(root) {
|
||||
|
@ -128,9 +92,7 @@ function keys(root) {
|
|||
}
|
||||
exports.keys = keys;
|
||||
|
||||
function isSet(name) {
|
||||
return (has(name) && prefSvc.prefHasUserValue(name));
|
||||
}
|
||||
const isSet = prefs.isSet.bind(prefs);
|
||||
exports.isSet = isSet;
|
||||
|
||||
function reset(name) {
|
||||
|
|
|
@ -8,20 +8,17 @@ 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');
|
||||
|
||||
const open = function open({ id }) {
|
||||
let showing = defer();
|
||||
let loaded = defer();
|
||||
let result = { id: id };
|
||||
let tab = openTab(getMostRecentBrowserWindow(), "about:addons", {
|
||||
inBackground: true
|
||||
});
|
||||
// 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");
|
||||
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;
|
||||
|
@ -30,21 +27,16 @@ const open = function open({ id }) {
|
|||
on("addon-options-displayed", function onPrefDisplayed({ subject: doc, data }) {
|
||||
if (data === id) {
|
||||
off("addon-options-displayed", onPrefDisplayed);
|
||||
result.tabId = getTabId(tab);
|
||||
result.document = doc;
|
||||
loaded.resolve();
|
||||
resolve({
|
||||
id: id,
|
||||
tabId: getTabId(tab),
|
||||
"document": doc
|
||||
});
|
||||
}
|
||||
}, 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;
|
||||
|
|
|
@ -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,7 +30,10 @@ const isPacked = rootURI && rootURI.indexOf("jar:") === 0;
|
|||
const uri = (path="") =>
|
||||
path.contains(":") ? path : addonDataURI + path.replace(/^\.\//, "");
|
||||
|
||||
let { preferencesBranch } = options;
|
||||
let preferencesBranch = ("preferences-branch" in metadata)
|
||||
? metadata["preferences-branch"]
|
||||
: options.preferencesBranch
|
||||
|
||||
if (/[^\w{@}.-]/.test(preferencesBranch)) {
|
||||
preferencesBranch = id;
|
||||
console.warn("Ignoring preferences-branch (not a valid branch name)");
|
||||
|
|
|
@ -20,7 +20,7 @@ let { merge } = require('../util/object');
|
|||
let { setTimeout, clearTimeout } = require('../timers');
|
||||
let isWindows = platform.indexOf('win') === 0;
|
||||
|
||||
let processes = WeakMap();
|
||||
let processes = new WeakMap();
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,7 +7,6 @@ module.metadata = {
|
|||
"stability": "experimental"
|
||||
};
|
||||
|
||||
var { Cu } = require("chrome");
|
||||
var { XulApp } = Cu.import("resource://gre/modules/sdk/system/XulApp.js", {});
|
||||
const { XulApp } = require("./xul-app.jsm");
|
||||
|
||||
Object.keys(XulApp).forEach(k => exports[k] = XulApp[k]);
|
||||
|
|
|
@ -3,15 +3,26 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["XulApp"];
|
||||
this.EXPORTED_SYMBOLS = [ "XulApp" ];
|
||||
|
||||
var { classes: Cc, interfaces: Ci } = Components;
|
||||
|
||||
var exports = {};
|
||||
var XulApp = exports;
|
||||
this.XulApp = exports;
|
||||
|
||||
var appInfo = Cc["@mozilla.org/xre/app-info;1"]
|
||||
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"]
|
||||
.getService(Ci.nsIXULAppInfo);
|
||||
}
|
||||
catch (e) {
|
||||
// xpcshell test case
|
||||
appInfo = {};
|
||||
}
|
||||
var vc = Cc["@mozilla.org/xpcom/version-comparator;1"]
|
||||
.getService(Ci.nsIVersionComparator);
|
||||
|
||||
|
@ -28,13 +39,10 @@ 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}"
|
||||
|
@ -183,3 +191,51 @@ 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;
|
|
@ -7,12 +7,13 @@ module.metadata = {
|
|||
"stability": "unstable"
|
||||
};
|
||||
|
||||
const { EventEmitterTrait: EventEmitter } = require("../deprecated/events");
|
||||
const { EventTarget } = require("../event/target");
|
||||
const { emit } = require("../event/core");
|
||||
const { DOMEventAssembler } = require("../deprecated/events/assembler");
|
||||
const { Trait } = require("../deprecated/light-traits");
|
||||
const { getActiveTab, getTabs, getTabContainer } = require("./utils");
|
||||
const { Class } = require("../core/heritage");
|
||||
const { getActiveTab, getTabs } = require("./utils");
|
||||
const { browserWindowIterator } = require("../deprecated/window-utils");
|
||||
const { isBrowser } = require('../window/utils');
|
||||
const { isBrowser, windows, getMostRecentBrowserWindow } = require("../window/utils");
|
||||
const { observer: windowObserver } = require("../windows/observer");
|
||||
|
||||
const EVENTS = {
|
||||
|
@ -24,15 +25,69 @@ 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 = Trait.compose(DOMEventAssembler, EventEmitter).create({
|
||||
/**
|
||||
* Method is implemented by `EventEmitter` and is used just for emitting
|
||||
* events on registered listeners.
|
||||
*/
|
||||
_emit: Trait.required,
|
||||
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);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Events that are supported and emitted by the module.
|
||||
*/
|
||||
|
@ -45,54 +100,8 @@ const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
|
|||
* Keyboard event being emitted.
|
||||
*/
|
||||
handleEvent: function handleEvent(event) {
|
||||
this._emit(EVENTS[event.type], event.target, event);
|
||||
emit(this, EVENTS[event.type], event.target, event);
|
||||
}
|
||||
});
|
||||
|
||||
// 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;
|
||||
exports.observer = new Observer();
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
'use strict';
|
||||
|
||||
const ContentWorker = require('../content/worker-parent').Worker;
|
||||
const ContentWorker = require('../content/worker').Worker;
|
||||
|
||||
function Worker(options, window) {
|
||||
options.window = window;
|
||||
|
|
|
@ -8,10 +8,10 @@ module.metadata = {
|
|||
};
|
||||
|
||||
const { Cu } = require("chrome");
|
||||
const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
const { Task } = require("resource://gre/modules/Task.jsm", {});
|
||||
const { defer } = require("sdk/core/promise");
|
||||
const BaseAssert = require("sdk/test/assert").Assert;
|
||||
const { isFunction, isObject } = require("sdk/lang/type");
|
||||
const { isFunction, isObject, isGenerator } = 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 (test.isGenerator && test.isGenerator()) {
|
||||
if (isGenerator(test)) {
|
||||
options.waitUntilDone();
|
||||
Task.spawn(test.bind(null, assert)).
|
||||
then(null, assert.fail).
|
||||
catch(assert.fail).
|
||||
then(assert.end);
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,6 @@ 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.
|
||||
|
|
|
@ -78,9 +78,9 @@ Assert.prototype = {
|
|||
if ('operator' in e) {
|
||||
message += [
|
||||
" -",
|
||||
source(e.expected),
|
||||
source(e.actual),
|
||||
e.operator,
|
||||
source(e.actual)
|
||||
source(e.expected)
|
||||
].join(" ");
|
||||
}
|
||||
}
|
||||
|
@ -89,6 +89,7 @@ Assert.prototype = {
|
|||
},
|
||||
pass: function pass(message) {
|
||||
this._log.pass(message);
|
||||
return true;
|
||||
},
|
||||
error: function error(e) {
|
||||
this._log.exception(e);
|
||||
|
@ -101,10 +102,11 @@ Assert.prototype = {
|
|||
message: message,
|
||||
operator: "=="
|
||||
});
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
this.pass(message);
|
||||
}
|
||||
|
||||
this.pass(message);
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -115,15 +117,16 @@ Assert.prototype = {
|
|||
equal: function equal(actual, expected, message) {
|
||||
if (actual == expected) {
|
||||
this.pass(message);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
this.fail({
|
||||
actual: actual,
|
||||
expected: expected,
|
||||
message: message,
|
||||
operator: "=="
|
||||
});
|
||||
}
|
||||
|
||||
this.fail({
|
||||
actual: actual,
|
||||
expected: expected,
|
||||
message: message,
|
||||
operator: "=="
|
||||
});
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -135,15 +138,16 @@ Assert.prototype = {
|
|||
notEqual: function notEqual(actual, expected, message) {
|
||||
if (actual != expected) {
|
||||
this.pass(message);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
this.fail({
|
||||
actual: actual,
|
||||
expected: expected,
|
||||
message: message,
|
||||
operator: "!=",
|
||||
});
|
||||
}
|
||||
|
||||
this.fail({
|
||||
actual: actual,
|
||||
expected: expected,
|
||||
message: message,
|
||||
operator: "!=",
|
||||
});
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -154,15 +158,16 @@ Assert.prototype = {
|
|||
deepEqual: function deepEqual(actual, expected, message) {
|
||||
if (isDeepEqual(actual, expected)) {
|
||||
this.pass(message);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
this.fail({
|
||||
actual: actual,
|
||||
expected: expected,
|
||||
message: message,
|
||||
operator: "deepEqual"
|
||||
});
|
||||
}
|
||||
|
||||
this.fail({
|
||||
actual: actual,
|
||||
expected: expected,
|
||||
message: message,
|
||||
operator: "deepEqual"
|
||||
});
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -174,15 +179,16 @@ Assert.prototype = {
|
|||
notDeepEqual: function notDeepEqual(actual, expected, message) {
|
||||
if (!isDeepEqual(actual, expected)) {
|
||||
this.pass(message);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
this.fail({
|
||||
actual: actual,
|
||||
expected: expected,
|
||||
message: message,
|
||||
operator: "notDeepEqual"
|
||||
});
|
||||
}
|
||||
|
||||
this.fail({
|
||||
actual: actual,
|
||||
expected: expected,
|
||||
message: message,
|
||||
operator: "notDeepEqual"
|
||||
});
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -194,15 +200,16 @@ Assert.prototype = {
|
|||
strictEqual: function strictEqual(actual, expected, message) {
|
||||
if (actual === expected) {
|
||||
this.pass(message);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
this.fail({
|
||||
actual: actual,
|
||||
expected: expected,
|
||||
message: message,
|
||||
operator: "==="
|
||||
});
|
||||
}
|
||||
|
||||
this.fail({
|
||||
actual: actual,
|
||||
expected: expected,
|
||||
message: message,
|
||||
operator: "==="
|
||||
});
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -214,15 +221,16 @@ Assert.prototype = {
|
|||
notStrictEqual: function notStrictEqual(actual, expected, message) {
|
||||
if (actual !== expected) {
|
||||
this.pass(message);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
this.fail({
|
||||
actual: actual,
|
||||
expected: expected,
|
||||
message: message,
|
||||
operator: "!=="
|
||||
})
|
||||
}
|
||||
|
||||
this.fail({
|
||||
actual: actual,
|
||||
expected: expected,
|
||||
message: message,
|
||||
operator: "!=="
|
||||
});
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -275,35 +283,36 @@ 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)) ||
|
||||
(isRegExp(Error) && (Error.test(exception.message) || Error.test(exception.toString()))) ||
|
||||
// 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.
|
||||
else {
|
||||
let failure = {
|
||||
message: message,
|
||||
operator: "throws"
|
||||
};
|
||||
let failure = {
|
||||
message: message,
|
||||
operator: "matches"
|
||||
};
|
||||
|
||||
if (exception)
|
||||
failure.actual = exception;
|
||||
|
||||
if (Error)
|
||||
failure.expected = Error;
|
||||
|
||||
this.fail(failure);
|
||||
if (exception) {
|
||||
failure.actual = exception.message || exception.toString();
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -58,11 +58,7 @@ var stopOnError;
|
|||
var findAndRunTests;
|
||||
|
||||
// Combined information from all test runs.
|
||||
var results = {
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
testRuns: []
|
||||
};
|
||||
var results;
|
||||
|
||||
// A list of the compartments and windows loaded after startup
|
||||
var startLeaks;
|
||||
|
@ -438,7 +434,8 @@ var POINTLESS_ERRORS = [
|
|||
'file: "chrome://browser/content/',
|
||||
'file: "chrome://global/content/',
|
||||
'[JavaScript Warning: "The character encoding of a framed document was ' +
|
||||
'not declared.'
|
||||
'not declared.',
|
||||
'file: "chrome://browser/skin/'
|
||||
];
|
||||
|
||||
var consoleListener = {
|
||||
|
@ -590,6 +587,12 @@ var runTests = exports.runTests = function runTests(options) {
|
|||
print = options.print;
|
||||
findAndRunTests = options.findAndRunTests;
|
||||
|
||||
results = {
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
testRuns: []
|
||||
};
|
||||
|
||||
try {
|
||||
consoleListener.register();
|
||||
print("Running tests on " + system.name + " " + system.version +
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
'use strict';
|
||||
|
||||
const { Cu } = require("chrome");
|
||||
|
|
|
@ -12,6 +12,9 @@ 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))
|
||||
|
@ -29,16 +32,37 @@ function isHelperAsync (fn) fn.length > 2
|
|||
function before (exports, beforeFn) {
|
||||
getTestNames(exports).map(name => {
|
||||
let testFn = exports[name];
|
||||
if (!isTestAsync(testFn) && !isHelperAsync(beforeFn)) {
|
||||
exports[name] = function (assert) {
|
||||
|
||||
// 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)) {
|
||||
exports[name] = function (assert, done) {
|
||||
else if (!isTestAsync(testFn) && !isHelperAsync(beforeFn)) {
|
||||
exports[name] = function (assert) {
|
||||
beforeFn(name, assert);
|
||||
testFn(assert, done);
|
||||
testFn(assert);
|
||||
};
|
||||
}
|
||||
else if (!isTestAsync(testFn) && isHelperAsync(beforeFn)) {
|
||||
|
@ -48,7 +72,21 @@ function before (exports, beforeFn) {
|
|||
done();
|
||||
});
|
||||
};
|
||||
} else if (isTestAsync(testFn) && isHelperAsync(beforeFn)) {
|
||||
}
|
||||
// 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)) {
|
||||
exports[name] = function (assert, done) {
|
||||
beforeFn(name, assert, () => {
|
||||
testFn(assert, done);
|
||||
|
@ -69,30 +107,62 @@ exports.before = before;
|
|||
function after (exports, afterFn) {
|
||||
getTestNames(exports).map(name => {
|
||||
let testFn = exports[name];
|
||||
if (!isTestAsync(testFn) && !isHelperAsync(afterFn)) {
|
||||
|
||||
// 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)) {
|
||||
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);
|
||||
};
|
||||
} 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));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
module.metadata = {
|
||||
'stability': 'experimental',
|
||||
'engines': {
|
||||
'Firefox': '*'
|
||||
'Firefox': '*',
|
||||
'SeaMonkey': '*',
|
||||
'Thunderbird': '*'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
/* 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";
|
||||
|
||||
// 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;
|
|
@ -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('../content/worker');
|
||||
const { Worker } = require('../deprecated/sync-worker');
|
||||
const { contract: sidebarContract } = require('./sidebar/contract');
|
||||
const { create, dispose, updateTitle, updateURL, isSidebarShowing, showSidebar, hideSidebar } = require('./sidebar/view');
|
||||
const { defer } = require('../core/promise');
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
module.metadata = {
|
||||
'stability': 'experimental',
|
||||
'engines': {
|
||||
'Firefox': '*'
|
||||
'Firefox': '*',
|
||||
'SeaMonkey': '*',
|
||||
'Thunderbird': '*'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
module.metadata = {
|
||||
'stability': 'experimental',
|
||||
'engines': {
|
||||
'Firefox': '*'
|
||||
'Firefox': '*',
|
||||
'SeaMonkey': '*',
|
||||
'Thunderbird': '*'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
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;
|
|
@ -0,0 +1,36 @@
|
|||
/* 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;
|
|
@ -1,59 +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 { 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;
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
|
@ -22,14 +21,15 @@ 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,
|
||||
const { isArray, isArguments, isMap, isSet, isGenerator,
|
||||
isString, isBoolean, isNumber } = require("../lang/type");
|
||||
|
||||
const Sequence = function Sequence(iterator) {
|
||||
if (iterator.isGenerator && iterator.isGenerator())
|
||||
this[Symbol.iterator] = iterator;
|
||||
else
|
||||
if (!isGenerator(iterator)) {
|
||||
throw TypeError("Expected generator argument");
|
||||
}
|
||||
|
||||
this[Symbol.iterator] = iterator;
|
||||
};
|
||||
exports.Sequence = Sequence;
|
||||
|
||||
|
@ -61,9 +61,6 @@ const seq = polymorphic({
|
|||
});
|
||||
exports.seq = seq;
|
||||
|
||||
|
||||
|
||||
|
||||
// Function to cast seq to string.
|
||||
const string = (...etc) => "".concat(...etc);
|
||||
exports.string = string;
|
||||
|
@ -111,6 +108,27 @@ 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,
|
||||
|
|
|
@ -25,8 +25,9 @@ const FM = Cc["@mozilla.org/focus-manager;1"].
|
|||
|
||||
const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
|
||||
|
||||
const prefs = require("../preferences/service");
|
||||
const BROWSER = 'navigator:browser',
|
||||
URI_BROWSER = 'chrome://browser/content/browser.xul',
|
||||
URI_BROWSER = prefs.get('browser.chromeURL', null),
|
||||
NAME = '_blank',
|
||||
FEATURES = 'chrome,all,dialog=no,non-private';
|
||||
|
||||
|
@ -186,6 +187,9 @@ function open(uri, options) {
|
|||
uri = uri || URI_BROWSER;
|
||||
options = options || {};
|
||||
|
||||
if (!uri)
|
||||
throw new Error('browser.chromeURL is undefined, please provide an explicit uri');
|
||||
|
||||
if (['chrome', 'resource', 'data'].indexOf(io.newURI(uri, null, null).scheme) < 0)
|
||||
throw new Error('only chrome, resource and data uris are allowed');
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ 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'),
|
||||
|
@ -23,7 +22,10 @@ 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.
|
||||
|
@ -33,12 +35,96 @@ const BrowserWindowTrait = Trait.compose(
|
|||
WindowDom.resolve({ close: '_close' }),
|
||||
WindowTabs,
|
||||
WindowTabTracker,
|
||||
WindowLoader,
|
||||
/* WindowSidebars, */
|
||||
Trait.compose({
|
||||
_emit: Trait.required,
|
||||
_close: Trait.required,
|
||||
_load: 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("|")
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Constructor returns wrapper of the specified chrome window.
|
||||
* @param {nsIWindow} window
|
||||
|
|
|
@ -1,128 +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'),
|
||||
{ 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;
|
|
@ -7,19 +7,29 @@ module.metadata = {
|
|||
"stability": "unstable"
|
||||
};
|
||||
|
||||
const { EventEmitterTrait: EventEmitter } = require("../deprecated/events");
|
||||
const { EventTarget } = require("../event/target");
|
||||
const { emit } = require("../event/core");
|
||||
const { WindowTracker, windowIterator } = require("../deprecated/window-utils");
|
||||
const { DOMEventAssembler } = require("../deprecated/events/assembler");
|
||||
const { Trait } = require("../deprecated/light-traits");
|
||||
const { Class } = require("../core/heritage");
|
||||
|
||||
// Event emitter objects used to register listeners and emit events on them
|
||||
// when they occur.
|
||||
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,
|
||||
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],
|
||||
/**
|
||||
* Events that are supported and emitted by the module.
|
||||
*/
|
||||
|
@ -31,21 +41,9 @@ const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
|
|||
* @param {Event} event
|
||||
* Keyboard event being emitted.
|
||||
*/
|
||||
handleEvent: function handleEvent(event) {
|
||||
this._emit(event.type, event.target, event);
|
||||
handleEvent(event) {
|
||||
emit(this, event.type, event.target, event);
|
||||
}
|
||||
});
|
||||
|
||||
// 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;
|
||||
exports.observer = new Observer();
|
||||
|
|
|
@ -158,7 +158,7 @@ function onTabSelect(event) {
|
|||
emit(tab, 'activate', tab);
|
||||
emit(gTabs, 'activate', tab);
|
||||
|
||||
for (let of in gTabs) {
|
||||
for (let t of gTabs) {
|
||||
if (t === tab) continue;
|
||||
emit(t, 'deactivate', t);
|
||||
emit(gTabs, 'deactivate', t);
|
||||
|
|
|
@ -2,28 +2,20 @@
|
|||
* 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(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 });
|
||||
;((factory) => { // Module boilerplate :(
|
||||
if (typeof(require) === 'function') { // CommonJS
|
||||
require("chrome").Cu.import(module.uri, exports);
|
||||
}
|
||||
}).call(this, 'loader', function Loader(require, exports, module) {
|
||||
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 => {
|
||||
|
||||
'use strict';
|
||||
|
||||
|
@ -38,11 +30,16 @@ const { loadSubScript } = Cc['@mozilla.org/moz/jssubscript-loader;1'].
|
|||
getService(Ci.mozIJSSubScriptLoader);
|
||||
const { notifyObservers } = Cc['@mozilla.org/observer-service;1'].
|
||||
getService(Ci.nsIObserverService);
|
||||
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
|
||||
const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
|
||||
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");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "XulApp", () => {
|
||||
let xulappURI = module.uri.replace("toolkit/loader.js",
|
||||
"sdk/system/xul-app.jsm");
|
||||
return Cu.import(xulappURI, {});
|
||||
});
|
||||
|
||||
// Define some shortcuts.
|
||||
const bind = Function.call.bind(Function.bind);
|
||||
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
||||
|
@ -90,7 +87,7 @@ const descriptor = iced(function descriptor(object) {
|
|||
});
|
||||
return value;
|
||||
});
|
||||
exports.descriptor = descriptor;
|
||||
Loader.descriptor = descriptor;
|
||||
|
||||
// Freeze important built-ins so they can't be used by untrusted code as a
|
||||
// message passing channel.
|
||||
|
@ -127,15 +124,15 @@ const override = iced(function override(target, source) {
|
|||
});
|
||||
return define({}, properties);
|
||||
});
|
||||
exports.override = override;
|
||||
Loader.override = override;
|
||||
|
||||
function sourceURI(uri) { return String(uri).split(" -> ").pop(); }
|
||||
exports.sourceURI = iced(sourceURI);
|
||||
Loader.sourceURI = iced(sourceURI);
|
||||
|
||||
function isntLoaderFrame(frame) { return frame.fileName !== module.uri }
|
||||
|
||||
function parseURI(uri) { return String(uri).split(" -> ").pop(); }
|
||||
exports.parseURI = parseURI;
|
||||
Loader.parseURI = parseURI;
|
||||
|
||||
function parseStack(stack) {
|
||||
let lines = String(stack).split("\n");
|
||||
|
@ -158,7 +155,7 @@ function parseStack(stack) {
|
|||
return frames;
|
||||
}, []);
|
||||
}
|
||||
exports.parseStack = parseStack;
|
||||
Loader.parseStack = parseStack;
|
||||
|
||||
function serializeStack(frames) {
|
||||
return frames.reduce(function(stack, frame) {
|
||||
|
@ -169,7 +166,7 @@ function serializeStack(frames) {
|
|||
stack;
|
||||
}, "");
|
||||
}
|
||||
exports.serializeStack = serializeStack;
|
||||
Loader.serializeStack = serializeStack;
|
||||
|
||||
function readURI(uri) {
|
||||
let stream = NetUtil.newChannel2(uri,
|
||||
|
@ -201,7 +198,7 @@ function join (...paths) {
|
|||
resolved = resolved.replace(/^chrome\:\/([^\/])/, 'chrome://$1');
|
||||
return resolved;
|
||||
}
|
||||
exports.join = join;
|
||||
Loader.join = join;
|
||||
|
||||
// Function takes set of options and returns a JS sandbox. Function may be
|
||||
// passed set of options:
|
||||
|
@ -253,7 +250,7 @@ const Sandbox = iced(function Sandbox(options) {
|
|||
|
||||
return sandbox;
|
||||
});
|
||||
exports.Sandbox = Sandbox;
|
||||
Loader.Sandbox = Sandbox;
|
||||
|
||||
// Evaluates code from the given `uri` into given `sandbox`. If
|
||||
// `options.source` is passed, then that code is evaluated instead.
|
||||
|
@ -273,7 +270,7 @@ const evaluate = iced(function evaluate(sandbox, uri, options) {
|
|||
return source ? Cu.evalInSandbox(source, sandbox, version, uri, line)
|
||||
: loadSubScript(uri, sandbox, encoding);
|
||||
});
|
||||
exports.evaluate = evaluate;
|
||||
Loader.evaluate = evaluate;
|
||||
|
||||
// Populates `exports` of the given CommonJS `module` object, in the context
|
||||
// of the given `loader` by evaluating code associated with it.
|
||||
|
@ -306,7 +303,8 @@ 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),
|
||||
|
@ -323,7 +321,8 @@ 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);
|
||||
|
@ -361,12 +360,19 @@ const load = iced(function load(loader, module) {
|
|||
});
|
||||
}
|
||||
|
||||
if (loader.checkCompatibility) {
|
||||
let err = XulApp.incompatibility(module);
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
if (module.exports && typeof(module.exports) === 'object')
|
||||
freeze(module.exports);
|
||||
|
||||
return module;
|
||||
});
|
||||
exports.load = load;
|
||||
Loader.load = load;
|
||||
|
||||
// Utility function to normalize module `uri`s so they have `.js` extension.
|
||||
function normalizeExt (uri) {
|
||||
|
@ -403,7 +409,7 @@ const resolve = iced(function resolve(id, base) {
|
|||
|
||||
return resolved;
|
||||
});
|
||||
exports.resolve = resolve;
|
||||
Loader.resolve = resolve;
|
||||
|
||||
// Node-style module lookup
|
||||
// Takes an id and path and attempts to load a file using node's resolving
|
||||
|
@ -412,7 +418,7 @@ exports.resolve = resolve;
|
|||
// http://nodejs.org/api/modules.html#modules_all_together
|
||||
const nodeResolve = iced(function nodeResolve(id, requirer, { rootURI }) {
|
||||
// Resolve again
|
||||
id = exports.resolve(id, requirer);
|
||||
id = Loader.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'
|
||||
|
@ -441,7 +447,7 @@ const nodeResolve = iced(function nodeResolve(id, requirer, { rootURI }) {
|
|||
// with `resolveURI` -- if during runtime, then `resolve` will throw.
|
||||
return void 0;
|
||||
});
|
||||
exports.nodeResolve = nodeResolve;
|
||||
Loader.nodeResolve = nodeResolve;
|
||||
|
||||
// Attempts to load `path` and then `path.js`
|
||||
// Returns `path` with valid file, or `undefined` otherwise
|
||||
|
@ -538,7 +544,7 @@ const resolveURI = iced(function resolveURI(id, mapping) {
|
|||
}
|
||||
return void 0; // otherwise we raise a warning, see bug 910304
|
||||
});
|
||||
exports.resolveURI = resolveURI;
|
||||
Loader.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
|
||||
|
@ -652,7 +658,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) ? exports.resolve(id, requirer.id) : id;
|
||||
requirement = isRelative(id) ? Loader.resolve(id, requirer.id) : id;
|
||||
}
|
||||
} else {
|
||||
// Resolve `id` to its requirer if it's relative.
|
||||
|
@ -679,7 +685,7 @@ const Require = iced(function Require(loader, requirer) {
|
|||
require.main = loader.main === requirer ? requirer : undefined;
|
||||
return iced(require);
|
||||
});
|
||||
exports.Require = Require;
|
||||
Loader.Require = Require;
|
||||
|
||||
const main = iced(function main(loader, id) {
|
||||
// If no main entry provided, and native loader is used,
|
||||
|
@ -690,7 +696,7 @@ const main = iced(function main(loader, id) {
|
|||
let module = loader.main = loader.modules[uri] = Module(id, uri);
|
||||
return loader.load(loader, module).exports;
|
||||
});
|
||||
exports.main = main;
|
||||
Loader.main = main;
|
||||
|
||||
// Makes module object that is made available to CommonJS modules when they
|
||||
// are evaluated, along with `exports` and `require`.
|
||||
|
@ -701,7 +707,7 @@ const Module = iced(function Module(id, uri) {
|
|||
uri: { value: uri }
|
||||
});
|
||||
});
|
||||
exports.Module = Module;
|
||||
Loader.Module = Module;
|
||||
|
||||
// Takes `loader`, and unload `reason` string and notifies all observers that
|
||||
// they should cleanup after them-self.
|
||||
|
@ -716,7 +722,7 @@ const unload = iced(function unload(loader, reason) {
|
|||
let subject = { wrappedJSObject: loader.destructor };
|
||||
notifyObservers(subject, 'sdk:loader:destroy', reason);
|
||||
});
|
||||
exports.unload = unload;
|
||||
Loader.unload = unload;
|
||||
|
||||
// Function makes new loader that can be used to load CommonJS modules
|
||||
// described by a given `options.manifest`. Loader takes following options:
|
||||
|
@ -731,24 +737,29 @@ exports.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.
|
||||
const Loader = iced(function Loader(options) {
|
||||
let console = new ConsoleAPI({
|
||||
consoleID: options.id ? "addon/" + options.id : ""
|
||||
});
|
||||
|
||||
function Loader(options) {
|
||||
let {
|
||||
modules, globals, resolve, paths, rootURI, manifest, requireMap, isNative,
|
||||
metadata, sharedGlobal, sharedGlobalBlacklist
|
||||
metadata, sharedGlobal, sharedGlobalBlacklist, checkCompatibility
|
||||
} = override({
|
||||
paths: {},
|
||||
modules: {},
|
||||
globals: {
|
||||
console: console
|
||||
get console() {
|
||||
// Import Console.jsm from here to prevent loading it until someone uses it
|
||||
let { ConsoleAPI } = Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||
let console = new ConsoleAPI({
|
||||
consoleID: options.id ? "addon/" + options.id : ""
|
||||
});
|
||||
Object.defineProperty(this, "console", { value: console });
|
||||
return this.console;
|
||||
}
|
||||
},
|
||||
checkCompatibility: false,
|
||||
resolve: options.isNative ?
|
||||
// Make the returned resolve function have the same signature
|
||||
(id, requirer) => exports.nodeResolve(id, requirer, { rootURI: rootURI }) :
|
||||
exports.resolve,
|
||||
(id, requirer) => Loader.nodeResolve(id, requirer, { rootURI: rootURI }) :
|
||||
Loader.resolve,
|
||||
sharedGlobalBlacklist: ["sdk/indexed-db"]
|
||||
}, options);
|
||||
|
||||
|
@ -825,6 +836,7 @@ const Loader = iced(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 +858,8 @@ const Loader = iced(function Loader(options) {
|
|||
}
|
||||
|
||||
return freeze(create(null, returnObj));
|
||||
});
|
||||
exports.Loader = Loader;
|
||||
};
|
||||
Loader.Loader = Loader;
|
||||
|
||||
let isJSONURI = uri => uri.substr(-5) === '.json';
|
||||
let isJSMURI = uri => uri.substr(-4) === '.jsm';
|
||||
|
@ -860,7 +872,7 @@ let isRelative = id => id[0] === '.'
|
|||
const generateMap = iced(function generateMap(options, callback) {
|
||||
let { rootURI, resolve, paths } = override({
|
||||
paths: {},
|
||||
resolve: exports.nodeResolve
|
||||
resolve: Loader.nodeResolve
|
||||
}, options);
|
||||
|
||||
rootURI = addTrailingSlash(rootURI);
|
||||
|
@ -882,7 +894,7 @@ const generateMap = iced(function generateMap(options, callback) {
|
|||
}, {}, callback);
|
||||
|
||||
});
|
||||
exports.generateMap = generateMap;
|
||||
Loader.generateMap = generateMap;
|
||||
|
||||
// Default `main` entry to './index.js' and ensure is relative,
|
||||
// since node allows 'lib/index.js' without relative `./`
|
||||
|
@ -950,6 +962,8 @@ function findModuleIncludes (uri, callback) {
|
|||
}
|
||||
|
||||
function walk (src, callback) {
|
||||
// Import Reflect.jsm from here to prevent loading it until someone uses it
|
||||
let { Reflect } = Cu.import("resource://gre/modules/reflect.jsm", {});
|
||||
let nodes = Reflect.parse(src);
|
||||
traverse(nodes, callback);
|
||||
}
|
||||
|
@ -988,4 +1002,5 @@ function isRequire (node) {
|
|||
&& node.arguments[0].type === 'Literal';
|
||||
}
|
||||
|
||||
module.exports = iced(Loader);
|
||||
});
|
||||
|
|
|
@ -1,3 +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/. */
|
||||
|
||||
const make = (exports, rootURI, components) => {
|
||||
const { Loader: { Loader, Require, Module, main } } =
|
||||
components.utils.import(rootURI + "toolkit/loader.js", {});
|
||||
|
@ -12,16 +16,49 @@ 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 => {
|
||||
const require = (id, options={}) => {
|
||||
const { reload, all } = options;
|
||||
const requirerURI = components.stack.caller.filename;
|
||||
const requirer = Module(requirerURI, requirerURI);
|
||||
return Require(loader, requirer)(id);
|
||||
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);
|
||||
};
|
||||
|
||||
require.resolve = id => {
|
||||
|
|
|
@ -7,9 +7,10 @@ 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);
|
||||
|
||||
|
|
|
@ -6,5 +6,4 @@
|
|||
|
||||
EXTRA_JS_MODULES.sdk.system += [
|
||||
'Startup.js',
|
||||
'XulApp.js',
|
||||
]
|
||||
|
|
|
@ -1,11 +1,36 @@
|
|||
{
|
||||
"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
|
||||
"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.23",
|
||||
"lodash": "2.4.1",
|
||||
"mocha": "1.21.5",
|
||||
"promise": "6.0.1",
|
||||
"rimraf": "2.2.8",
|
||||
"unzip": "0.1.9",
|
||||
"xmldom": "0.1.19"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -861,8 +861,7 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None,
|
|||
jid=jid,
|
||||
update_url=options.update_url,
|
||||
bootstrap=True,
|
||||
enable_mobile=options.enable_mobile,
|
||||
harness_options=harness_options)
|
||||
enable_mobile=options.enable_mobile)
|
||||
|
||||
if command == "xpi" and options.update_link:
|
||||
if not options.update_link.startswith("https"):
|
||||
|
|
|
@ -417,6 +417,9 @@ 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
|
||||
|
@ -802,4 +805,3 @@ if __name__ == '__main__':
|
|||
sys.exit(1)
|
||||
print "requires: %s" % (",".join(sorted(requires.keys())))
|
||||
print "locations: %s" % locations
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче