зеркало из 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
|
# 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/.
|
||||||
|
|
||||||
HAS_MISC_RULE = True
|
|
||||||
|
|
||||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||||
JETPACK_PACKAGE_MANIFESTS += ['source/test/jetpack-package.ini']
|
JETPACK_PACKAGE_MANIFESTS += ['source/test/jetpack-package.ini']
|
||||||
JETPACK_ADDON_MANIFESTS += ['source/test/addons/jetpack-addon.ini']
|
JETPACK_ADDON_MANIFESTS += ['source/test/addons/jetpack-addon.ini']
|
||||||
|
@ -21,7 +19,6 @@ EXTRA_JS_MODULES.sdk += [
|
||||||
|
|
||||||
EXTRA_JS_MODULES.sdk.system += [
|
EXTRA_JS_MODULES.sdk.system += [
|
||||||
'source/modules/system/Startup.js',
|
'source/modules/system/Startup.js',
|
||||||
'source/modules/system/XulApp.js',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk":
|
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/list.js',
|
||||||
'source/lib/sdk/deprecated/memory.js',
|
'source/lib/sdk/deprecated/memory.js',
|
||||||
'source/lib/sdk/deprecated/symbiont.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-worker.js',
|
||||||
'source/lib/sdk/deprecated/traits.js',
|
'source/lib/sdk/deprecated/traits.js',
|
||||||
'source/lib/sdk/deprecated/unit-test-finder.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 += [
|
EXTRA_JS_MODULES.commonjs.sdk.panel += [
|
||||||
'source/lib/sdk/panel/events.js',
|
'source/lib/sdk/panel/events.js',
|
||||||
'source/lib/sdk/panel/utils.js',
|
'source/lib/sdk/panel/utils.js',
|
||||||
'source/lib/sdk/panel/window.js',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
EXTRA_JS_MODULES.commonjs.sdk.places += [
|
EXTRA_JS_MODULES.commonjs.sdk.places += [
|
||||||
|
@ -99,6 +96,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk":
|
||||||
]
|
]
|
||||||
|
|
||||||
EXTRA_JS_MODULES.commonjs.sdk.ui += [
|
EXTRA_JS_MODULES.commonjs.sdk.ui += [
|
||||||
|
'source/lib/sdk/ui/component.js',
|
||||||
'source/lib/sdk/ui/frame.js',
|
'source/lib/sdk/ui/frame.js',
|
||||||
'source/lib/sdk/ui/id.js',
|
'source/lib/sdk/ui/id.js',
|
||||||
'source/lib/sdk/ui/sidebar.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/dom.js',
|
||||||
'source/lib/sdk/windows/fennec.js',
|
'source/lib/sdk/windows/fennec.js',
|
||||||
'source/lib/sdk/windows/firefox.js',
|
'source/lib/sdk/windows/firefox.js',
|
||||||
'source/lib/sdk/windows/loader.js',
|
|
||||||
'source/lib/sdk/windows/observer.js',
|
'source/lib/sdk/windows/observer.js',
|
||||||
'source/lib/sdk/windows/tabs-fennec.js',
|
'source/lib/sdk/windows/tabs-fennec.js',
|
||||||
'source/lib/sdk/windows/tabs-firefox.js',
|
'source/lib/sdk/windows/tabs-firefox.js',
|
||||||
]
|
]
|
||||||
|
|
||||||
EXTRA_JS_MODULES.commonjs += [
|
EXTRA_JS_MODULES.commonjs += [
|
||||||
|
'source/lib/index.js',
|
||||||
'source/lib/test.js',
|
'source/lib/test.js',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -173,10 +171,13 @@ EXTRA_JS_MODULES.commonjs.diffpatcher.test += [
|
||||||
]
|
]
|
||||||
|
|
||||||
EXTRA_JS_MODULES.commonjs.framescript += [
|
EXTRA_JS_MODULES.commonjs.framescript += [
|
||||||
|
'source/lib/framescript/context-menu.js',
|
||||||
'source/lib/framescript/contextmenu-events.js',
|
'source/lib/framescript/contextmenu-events.js',
|
||||||
'source/lib/framescript/FrameScriptManager.jsm',
|
'source/lib/framescript/FrameScriptManager.jsm',
|
||||||
'source/lib/framescript/LoaderHelper.jsm',
|
'source/lib/framescript/LoaderHelper.jsm',
|
||||||
|
'source/lib/framescript/manager.js',
|
||||||
'source/lib/framescript/tab-events.js',
|
'source/lib/framescript/tab-events.js',
|
||||||
|
'source/lib/framescript/util.js',
|
||||||
]
|
]
|
||||||
|
|
||||||
EXTRA_JS_MODULES.commonjs.method += [
|
EXTRA_JS_MODULES.commonjs.method += [
|
||||||
|
@ -191,6 +192,7 @@ EXTRA_JS_MODULES.commonjs.sdk += [
|
||||||
'source/lib/sdk/base64.js',
|
'source/lib/sdk/base64.js',
|
||||||
'source/lib/sdk/clipboard.js',
|
'source/lib/sdk/clipboard.js',
|
||||||
'source/lib/sdk/context-menu.js',
|
'source/lib/sdk/context-menu.js',
|
||||||
|
'source/lib/sdk/context-menu@2.js',
|
||||||
'source/lib/sdk/hotkeys.js',
|
'source/lib/sdk/hotkeys.js',
|
||||||
'source/lib/sdk/indexed-db.js',
|
'source/lib/sdk/indexed-db.js',
|
||||||
'source/lib/sdk/l10n.js',
|
'source/lib/sdk/l10n.js',
|
||||||
|
@ -218,6 +220,7 @@ EXTRA_JS_MODULES.commonjs.sdk += [
|
||||||
]
|
]
|
||||||
|
|
||||||
EXTRA_JS_MODULES.commonjs.sdk.addon += [
|
EXTRA_JS_MODULES.commonjs.sdk.addon += [
|
||||||
|
'source/lib/sdk/addon/bootstrap.js',
|
||||||
'source/lib/sdk/addon/events.js',
|
'source/lib/sdk/addon/events.js',
|
||||||
'source/lib/sdk/addon/host.js',
|
'source/lib/sdk/addon/host.js',
|
||||||
'source/lib/sdk/addon/installer.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/thumbnail.js',
|
||||||
'source/lib/sdk/content/utils.js',
|
'source/lib/sdk/content/utils.js',
|
||||||
'source/lib/sdk/content/worker-child.js',
|
'source/lib/sdk/content/worker-child.js',
|
||||||
'source/lib/sdk/content/worker-parent.js',
|
|
||||||
'source/lib/sdk/content/worker.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 += [
|
EXTRA_JS_MODULES.commonjs.sdk.core += [
|
||||||
'source/lib/sdk/core/disposable.js',
|
'source/lib/sdk/core/disposable.js',
|
||||||
'source/lib/sdk/core/heritage.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/runtime.js',
|
||||||
'source/lib/sdk/system/unload.js',
|
'source/lib/sdk/system/unload.js',
|
||||||
'source/lib/sdk/system/xul-app.js',
|
'source/lib/sdk/system/xul-app.js',
|
||||||
|
'source/lib/sdk/system/xul-app.jsm',
|
||||||
]
|
]
|
||||||
|
|
||||||
EXTRA_JS_MODULES.commonjs.sdk.system.child_process += [
|
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',
|
'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 += [
|
EXTRA_JS_MODULES.commonjs.sdk.url += [
|
||||||
'source/lib/sdk/url/utils.js',
|
'source/lib/sdk/url/utils.js',
|
||||||
]
|
]
|
||||||
|
|
||||||
EXTRA_JS_MODULES.commonjs.sdk.util += [
|
EXTRA_JS_MODULES.commonjs.sdk.util += [
|
||||||
'source/lib/sdk/util/array.js',
|
'source/lib/sdk/util/array.js',
|
||||||
|
'source/lib/sdk/util/bond.js',
|
||||||
'source/lib/sdk/util/collection.js',
|
'source/lib/sdk/util/collection.js',
|
||||||
'source/lib/sdk/util/contract.js',
|
'source/lib/sdk/util/contract.js',
|
||||||
'source/lib/sdk/util/deprecate.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/list.js',
|
||||||
'source/lib/sdk/util/match-pattern.js',
|
'source/lib/sdk/util/match-pattern.js',
|
||||||
'source/lib/sdk/util/object.js',
|
'source/lib/sdk/util/object.js',
|
||||||
'source/lib/sdk/util/registry.js',
|
|
||||||
'source/lib/sdk/util/rules.js',
|
'source/lib/sdk/util/rules.js',
|
||||||
'source/lib/sdk/util/sequence.js',
|
'source/lib/sdk/util/sequence.js',
|
||||||
'source/lib/sdk/util/uuid.js',
|
'source/lib/sdk/util/uuid.js',
|
||||||
|
|
|
@ -14,5 +14,4 @@ EXTRA_JS_MODULES.sdk += [
|
||||||
|
|
||||||
EXTRA_JS_MODULES.sdk.system += [
|
EXTRA_JS_MODULES.sdk.system += [
|
||||||
'source/modules/system/Startup.js',
|
'source/modules/system/Startup.js',
|
||||||
'source/modules/system/XulApp.js',
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -8,6 +8,7 @@ doc/index.html
|
||||||
doc/modules/
|
doc/modules/
|
||||||
doc/status.md5
|
doc/status.md5
|
||||||
packages/*
|
packages/*
|
||||||
|
node_modules
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
*.pyc
|
*.pyc
|
||||||
|
@ -17,4 +18,3 @@ packages/*
|
||||||
|
|
||||||
# Windows
|
# Windows
|
||||||
*Thumbs.db
|
*Thumbs.db
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ testdocs.tgz
|
||||||
jetpack-sdk-docs.tgz
|
jetpack-sdk-docs.tgz
|
||||||
.test_tmp
|
.test_tmp
|
||||||
jetpack-sdk-docs
|
jetpack-sdk-docs
|
||||||
|
node_modules
|
||||||
|
|
||||||
# These should really be in a global .hgignore, but such a thing
|
# These should really be in a global .hgignore, but such a thing
|
||||||
# seems ridiculously confusing to set up, so we'll include some
|
# 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);
|
|
@ -4,6 +4,8 @@
|
||||||
"contributors": [],
|
"contributors": [],
|
||||||
"author": "Will Bamberg",
|
"author": "Will Bamberg",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"id": "anonid0-annotator",
|
"version": "0.1.1",
|
||||||
"description": "Add notes to Web pages"
|
"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
|
/* 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
|
* 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";
|
||||||
|
|
||||||
exports.testMain = function(test) {
|
exports.testMain = function(assert) {
|
||||||
test.pass("TODO: Write some tests.");
|
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);
|
|
@ -2,8 +2,9 @@
|
||||||
"name": "library-detector-sdk",
|
"name": "library-detector-sdk",
|
||||||
"license": "MPL 2.0",
|
"license": "MPL 2.0",
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "0.1",
|
"version": "0.1.1",
|
||||||
"title": "library-detector-sdk",
|
"title": "library-detector-sdk",
|
||||||
"id": "jid1-R4rSVNkBANnvGQ",
|
"id": "jid1-R4rSVNkBANnvGQ@jetpack",
|
||||||
"description": "a basic add-on"
|
"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
|
/* 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
|
* 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";
|
||||||
|
|
||||||
exports.testMain = function(test) {
|
exports.testMain = function(assert) {
|
||||||
test.pass("TODO: Write some tests.");
|
assert.pass("TODO: Write some tests.");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
require("sdk/test").run(exports);
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"description": "a toolbar api example",
|
"description": "a toolbar api example",
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MPL 2.0",
|
"license": "MPL 2.0",
|
||||||
"version": "0.1",
|
"version": "0.1.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"firefox": ">=27.0 <=30.0"
|
"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",
|
"description": "A Button API example",
|
||||||
"author": "jeff@canuckistani.ca (Jeff Griffiths | @canuckistani)",
|
"author": "jeff@canuckistani.ca (Jeff Griffiths | @canuckistani)",
|
||||||
"license": "MPL 2.0",
|
"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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
try {
|
||||||
|
// CFX use case..
|
||||||
var { actionButton, toggleButton, icon } = require("main");
|
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");
|
var self = require("sdk/self");
|
||||||
|
|
||||||
exports.testActionButton = function(assert) {
|
exports.testActionButton = function(assert) {
|
||||||
|
|
|
@ -16,11 +16,13 @@ const targetFor = target => {
|
||||||
return devtools.TargetFactory.forTab(target);
|
return devtools.TargetFactory.forTab(target);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getId = id => ((id.prototype && id.prototype.id) || id.id || id);
|
||||||
|
|
||||||
const getCurrentPanel = toolbox => toolbox.getCurrentPanel();
|
const getCurrentPanel = toolbox => toolbox.getCurrentPanel();
|
||||||
exports.getCurrentPanel = getCurrentPanel;
|
exports.getCurrentPanel = getCurrentPanel;
|
||||||
|
|
||||||
const openToolbox = (id, tab) => {
|
const openToolbox = (id, tab) => {
|
||||||
id = id.prototype.id || id.id || id;
|
id = getId(id);
|
||||||
return gDevTools.showToolbox(targetFor(tab), id);
|
return gDevTools.showToolbox(targetFor(tab), id);
|
||||||
};
|
};
|
||||||
exports.openToolbox = openToolbox;
|
exports.openToolbox = openToolbox;
|
||||||
|
@ -32,7 +34,7 @@ const getToolbox = tab => gDevTools.getToolbox(targetFor(tab));
|
||||||
exports.getToolbox = getToolbox;
|
exports.getToolbox = getToolbox;
|
||||||
|
|
||||||
const openToolboxPanel = (id, tab) => {
|
const openToolboxPanel = (id, tab) => {
|
||||||
id = id.prototype.id || id.id || id;
|
id = getId(id);
|
||||||
return gDevTools.showToolbox(targetFor(tab), id).then(getCurrentPanel);
|
return gDevTools.showToolbox(targetFor(tab), id).then(getCurrentPanel);
|
||||||
};
|
};
|
||||||
exports.openToolboxPanel = openToolboxPanel;
|
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){
|
!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";
|
"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": {
|
"engines": {
|
||||||
// TODO Fennec Support 789757
|
// TODO Fennec Support 789757
|
||||||
"Firefox": "*",
|
"Firefox": "*",
|
||||||
"SeaMonkey": "*"
|
"SeaMonkey": "*",
|
||||||
|
"Thunderbird": "*"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -124,26 +125,24 @@ exports.set = function(aData, aDataType) {
|
||||||
switch (flavor) {
|
switch (flavor) {
|
||||||
case "text/html":
|
case "text/html":
|
||||||
// add text/html flavor
|
// add text/html flavor
|
||||||
let (str = Cc["@mozilla.org/supports-string;1"].
|
let str = Cc["@mozilla.org/supports-string;1"].
|
||||||
createInstance(Ci.nsISupportsString))
|
createInstance(Ci.nsISupportsString);
|
||||||
{
|
|
||||||
str.data = options.data;
|
str.data = options.data;
|
||||||
xferable.addDataFlavor(flavor);
|
xferable.addDataFlavor(flavor);
|
||||||
xferable.setTransferData(flavor, str, str.data.length * 2);
|
xferable.setTransferData(flavor, str, str.data.length * 2);
|
||||||
}
|
|
||||||
|
|
||||||
// add a text/unicode flavor (html converted to plain text)
|
// add a text/unicode flavor (html converted to plain text)
|
||||||
let (str = Cc["@mozilla.org/supports-string;1"].
|
str = Cc["@mozilla.org/supports-string;1"].
|
||||||
createInstance(Ci.nsISupportsString),
|
createInstance(Ci.nsISupportsString);
|
||||||
converter = Cc["@mozilla.org/feed-textconstruct;1"].
|
let converter = Cc["@mozilla.org/feed-textconstruct;1"].
|
||||||
createInstance(Ci.nsIFeedTextConstruct))
|
createInstance(Ci.nsIFeedTextConstruct);
|
||||||
{
|
|
||||||
converter.type = "html";
|
converter.type = "html";
|
||||||
converter.text = options.data;
|
converter.text = options.data;
|
||||||
str.data = converter.plainText();
|
str.data = converter.plainText();
|
||||||
xferable.addDataFlavor("text/unicode");
|
xferable.addDataFlavor("text/unicode");
|
||||||
xferable.setTransferData("text/unicode", str, str.data.length * 2);
|
xferable.setTransferData("text/unicode", str, str.data.length * 2);
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Set images to the clipboard is not straightforward, to have an idea how
|
// 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.receive = this.receive.bind(this);
|
||||||
this.manager.addMessageListener('sdk/worker/message', this.receive);
|
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
|
// messages
|
||||||
receive({ data: { id, args }}) {
|
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"
|
"stability": "unstable"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { emit } = require('../event/core');
|
||||||
|
const { omit } = require('../util/object');
|
||||||
const { Class } = require('../core/heritage');
|
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 { method } = require('../lang/functional');
|
||||||
const { Ci, Cu, Cc } = require('chrome');
|
const { getInnerId } = require('../window/utils');
|
||||||
const unload = require('../system/unload');
|
const { EventTarget } = require('../event/target');
|
||||||
const events = require('../system/events');
|
const { when, ensure } = require('../system/unload');
|
||||||
const { getInnerId } = require("../window/utils");
|
|
||||||
const { WorkerSandbox } = require('./sandbox');
|
|
||||||
const { getTabForWindow } = require('../tabs/helpers');
|
const { getTabForWindow } = require('../tabs/helpers');
|
||||||
|
const { getTabForContentWindow, getBrowserForTab } = require('../tabs/utils');
|
||||||
const { isPrivate } = require('../private-browsing/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();
|
const workers = new WeakMap();
|
||||||
|
|
||||||
let modelFor = (worker) => workers.get(worker);
|
let modelFor = (worker) => workers.get(worker);
|
||||||
|
|
||||||
const ERR_DESTROYED =
|
const ERR_DESTROYED = "Couldn't find the worker to receive this message. " +
|
||||||
"Couldn't find the worker to receive this message. " +
|
|
||||||
"The script may not be initialized yet, or may already have been unloaded.";
|
"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 " +
|
const ERR_FROZEN = "The page is currently hidden and can no longer be used " +
|
||||||
"until it is visible again.";
|
"until it is visible again.";
|
||||||
|
|
||||||
/**
|
// a handle for communication between content script and addon code
|
||||||
* Message-passing facility for communication between code running
|
|
||||||
* in the content and add-on process.
|
|
||||||
* @see https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/content_worker
|
|
||||||
*/
|
|
||||||
const Worker = Class({
|
const Worker = Class({
|
||||||
implements: [EventTarget],
|
implements: [EventTarget],
|
||||||
initialize: function WorkerConstructor (options) {
|
initialize(options = {}) {
|
||||||
// Save model in weak map to not expose properties
|
|
||||||
let model = createModel();
|
let model = {
|
||||||
|
inited: false,
|
||||||
|
earlyEvents: [], // fired before worker was inited
|
||||||
|
frozen: true, // document is in BFcache, let it go
|
||||||
|
options,
|
||||||
|
};
|
||||||
workers.set(this, model);
|
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.receive = this.receive.bind(this);
|
||||||
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);
|
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
|
this.port = EventTarget();
|
||||||
// to send events during worker initialization.
|
this.port.emit = this.send.bind(this, 'event');
|
||||||
this.port = createPort(this);
|
this.postMessage = this.send.bind(this, 'message');
|
||||||
|
|
||||||
model.documentUnload = documentUnload.bind(this);
|
|
||||||
model.pageShow = pageShow.bind(this);
|
|
||||||
model.pageHide = pageHide.bind(this);
|
|
||||||
|
|
||||||
if ('window' in options)
|
if ('window' in options)
|
||||||
attach(this, options.window);
|
attach(this, options.window);
|
||||||
},
|
},
|
||||||
|
// messages
|
||||||
/**
|
receive({ data: { id, args }}) {
|
||||||
* Sends a message to the worker's global scope. Method takes single
|
let model = modelFor(this);
|
||||||
* argument, which represents data to be sent to the worker. The data may
|
if (id !== model.id || !model.childWorker)
|
||||||
* be any primitive type value or `JSON`. Call of this method asynchronously
|
return;
|
||||||
* emits `message` event with data value in the global scope of this
|
if (args[0] === 'event')
|
||||||
* symbiont.
|
emit(this.port, ...args.slice(1))
|
||||||
*
|
else
|
||||||
* `message` event listeners can be set either by calling
|
emit(this, ...args);
|
||||||
* `self.on` with a first argument string `"message"` or by
|
},
|
||||||
* implementing `onMessage` function in the global scope of this worker.
|
send(...args) {
|
||||||
* @param {Number|String|JSON} data
|
|
||||||
*/
|
|
||||||
postMessage: function (...data) {
|
|
||||||
let model = modelFor(this);
|
let model = modelFor(this);
|
||||||
let args = ['message'].concat(data);
|
|
||||||
if (!model.inited) {
|
if (!model.inited) {
|
||||||
model.earlyEvents.push(args);
|
model.earlyEvents.push(args);
|
||||||
return;
|
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) {
|
||||||
|
//
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
// properties
|
||||||
get url() {
|
get url() {
|
||||||
let model = modelFor(this);
|
let { window } = modelFor(this);
|
||||||
// model.window will be null after detach
|
return window && window.document.location.href;
|
||||||
return model.window ? model.window.document.location.href : null;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
get contentURL() {
|
get contentURL() {
|
||||||
let model = modelFor(this);
|
let { window } = modelFor(this);
|
||||||
return model.window ? model.window.document.URL : null;
|
return window && window.document.URL;
|
||||||
},
|
},
|
||||||
|
|
||||||
get tab() {
|
get tab() {
|
||||||
let model = modelFor(this);
|
let { window } = modelFor(this);
|
||||||
// model.window will be null after detach
|
return window && getTabForWindow(window);
|
||||||
if (model.window)
|
|
||||||
return getTabForWindow(model.window);
|
|
||||||
return null;
|
|
||||||
},
|
},
|
||||||
|
toString: () => '[object Worker]',
|
||||||
// Implemented to provide some of the previous features of exposing sandbox
|
// methods
|
||||||
// so that Worker can be extended
|
|
||||||
getSandbox: function () {
|
|
||||||
return modelFor(this).contentWorker;
|
|
||||||
},
|
|
||||||
|
|
||||||
toString: function () { return '[object Worker]'; },
|
|
||||||
attach: method(attach),
|
attach: method(attach),
|
||||||
detach: method(detach),
|
detach: method(detach),
|
||||||
destroy: method(destroy)
|
destroy: method(destroy),
|
||||||
});
|
})
|
||||||
exports.Worker = Worker;
|
exports.Worker = Worker;
|
||||||
|
|
||||||
attach.define(Worker, function(worker, window) {
|
attach.define(Worker, function(worker, window) {
|
||||||
let model = modelFor(worker);
|
let model = modelFor(worker);
|
||||||
|
|
||||||
model.window = window;
|
model.window = window;
|
||||||
// Track document unload to destroy this worker.
|
model.options.window = getInnerId(window);
|
||||||
// We can't watch for unload event on page's window object as it
|
model.options.currentReadyState = window.document.readyState;
|
||||||
// prevents bfcache from working:
|
model.id = model.options.id = String(uuid());
|
||||||
// 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:
|
let tab = getTabForContentWindow(window);
|
||||||
model.contentWorker = WorkerSandbox(worker, model.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
|
model.manager.addMessageListener('sdk/worker/event', worker.receive);
|
||||||
// while the document is frozen in bfcache:
|
model.manager.addMessageListener('sdk/worker/attach', attach);
|
||||||
model.window.addEventListener("pageshow", model.pageShow, true);
|
|
||||||
model.window.addEventListener("pagehide", model.pageHide, true);
|
model.manager.sendAsyncMessage('sdk/worker/create', {
|
||||||
|
options: model.options,
|
||||||
|
addon: ADDON
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
// Mainly enable worker.port.emit to send event to the content worker
|
|
||||||
model.inited = true;
|
model.inited = true;
|
||||||
model.frozen = false;
|
model.frozen = false;
|
||||||
|
|
||||||
// Fire off `attach` event
|
model.earlyEvents.forEach(args => worker.send(...args));
|
||||||
emit(worker, 'attach', window);
|
emit(worker, 'attach', window);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Process all events and messages that were fired before the
|
// unload and release the child worker, release window reference
|
||||||
// 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) {
|
detach.define(Worker, function(worker, reason) {
|
||||||
let model = modelFor(worker);
|
let model = modelFor(worker);
|
||||||
|
worker.send('detach', reason);
|
||||||
|
if (!model.childWorker)
|
||||||
|
return;
|
||||||
|
|
||||||
// maybe unloaded before content side is created
|
model.childWorker = null;
|
||||||
if (model.contentWorker) {
|
model.earlyEvents = [];
|
||||||
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;
|
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');
|
emit(worker, 'detach');
|
||||||
}
|
model.manager.removeMessageListener('sdk/worker/event', this.receive);
|
||||||
model.inited = false;
|
})
|
||||||
});
|
|
||||||
|
|
||||||
isPrivate.define(Worker, ({ tab }) => isPrivate(tab));
|
isPrivate.define(Worker, ({ tab }) => isPrivate(tab));
|
||||||
|
|
||||||
/**
|
// unlod worker, release references
|
||||||
* Tells content worker to unload itself and
|
|
||||||
* removes all the references from itself.
|
|
||||||
*/
|
|
||||||
destroy.define(Worker, function(worker, reason) {
|
destroy.define(Worker, function(worker, reason) {
|
||||||
detach(worker, reason);
|
detach(worker, reason);
|
||||||
modelFor(worker).inited = true;
|
modelFor(worker).inited = true;
|
||||||
// Specifying no type or listener removes all listeners
|
})
|
||||||
// from target
|
|
||||||
off(worker);
|
|
||||||
off(worker.port);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
// unload Loaders used for creating WorkerChild instances in each process
|
||||||
* Events fired by workers
|
when(() => ppmm.broadcastAsyncMessage('sdk/loader/unload', { data: ADDON }));
|
||||||
*/
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ const { WindowTracker, browserWindowIterator } = require("./deprecated/window-ut
|
||||||
const { isBrowser, getInnerId } = require("./window/utils");
|
const { isBrowser, getInnerId } = require("./window/utils");
|
||||||
const { Ci, Cc, Cu } = require("chrome");
|
const { Ci, Cc, Cu } = require("chrome");
|
||||||
const { MatchPattern } = require("./util/match-pattern");
|
const { MatchPattern } = require("./util/match-pattern");
|
||||||
const { Worker } = require("./content/worker");
|
|
||||||
const { EventTarget } = require("./event/target");
|
const { EventTarget } = require("./event/target");
|
||||||
const { emit } = require('./event/core');
|
const { emit } = require('./event/core');
|
||||||
const { when } = require('./system/unload');
|
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 => {
|
const setupDisposable = disposable => {
|
||||||
subscribe(disposable, addonUnloadTopic, isWeak(disposable));
|
subscribe(disposable, addonUnloadTopic, isWeak(disposable));
|
||||||
};
|
};
|
||||||
|
exports.setupDisposable = setupDisposable;
|
||||||
|
|
||||||
// Tears down disposable instance.
|
// Tears down disposable instance.
|
||||||
const disposeDisposable = disposable => {
|
const disposeDisposable = disposable => {
|
||||||
unsubscribe(disposable, addonUnloadTopic);
|
unsubscribe(disposable, addonUnloadTopic);
|
||||||
};
|
};
|
||||||
|
exports.disposeDisposable = disposeDisposable;
|
||||||
|
|
||||||
// Base type that takes care of disposing it's instances on add-on unload.
|
// 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.
|
// 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
|
// increase shutdown time. Although specefic components may choose
|
||||||
// to implement shutdown handler that does something better.
|
// to implement shutdown handler that does something better.
|
||||||
shutdown.define(Disposable, disposable => {});
|
shutdown.define(Disposable, disposable => {});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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
|
* 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";
|
"use strict";
|
||||||
|
|
||||||
module.metadata = {
|
module.metadata = {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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
|
* 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";
|
"use strict";
|
||||||
|
|
||||||
module.metadata = {
|
module.metadata = {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const { Trait } = require("../light-traits");
|
const { Class } = require("../../core/heritage");
|
||||||
const { removeListener, on } = require("../../dom/events");
|
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
|
* `supportedEventsTypes` and function for handling all those events as
|
||||||
* `handleEvent` property.
|
* `handleEvent` property.
|
||||||
*/
|
*/
|
||||||
exports.DOMEventAssembler = Trait({
|
exports.DOMEventAssembler = Class({
|
||||||
/**
|
/**
|
||||||
* Function that is supposed to handle all the supported events (that are
|
* Function that is supposed to handle all the supported events (that are
|
||||||
* present in the `supportedEventsTypes`) from all the observed
|
* present in the `supportedEventsTypes`) from all the observed
|
||||||
|
@ -23,12 +23,16 @@ exports.DOMEventAssembler = Trait({
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* Event being dispatched.
|
* Event being dispatched.
|
||||||
*/
|
*/
|
||||||
handleEvent: Trait.required,
|
handleEvent() {
|
||||||
|
throw new TypeError("Instance of DOMEventAssembler must implement `handleEvent` method");
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Array of supported event names.
|
* Array of supported event names.
|
||||||
* @type {String[]}
|
* @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
|
* Adds `eventTarget` to the list of observed `eventTarget`s. Listeners for
|
||||||
* supported events will be registered on the given `eventTarget`.
|
* 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 memory = require("./memory");
|
||||||
const timer = require("../timers");
|
const timer = require("../timers");
|
||||||
const cfxArgs = require("../test/options");
|
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 { windows, isBrowser, getMostRecentBrowserWindow } = require("../window/utils");
|
||||||
const { defer, all, Debugging: PromiseDebugging, resolve } = require("../core/promise");
|
const { defer, all, Debugging: PromiseDebugging, resolve } = require("../core/promise");
|
||||||
const { getInnerId } = require("../window/utils");
|
const { getInnerId } = require("../window/utils");
|
||||||
|
@ -35,10 +35,16 @@ const findAndRunTests = function findAndRunTests(options) {
|
||||||
exports.findAndRunTests = findAndRunTests;
|
exports.findAndRunTests = findAndRunTests;
|
||||||
|
|
||||||
let runnerWindows = new WeakMap();
|
let runnerWindows = new WeakMap();
|
||||||
|
let runnerTabs = new WeakMap();
|
||||||
|
|
||||||
const TestRunner = function TestRunner(options) {
|
const TestRunner = function TestRunner(options) {
|
||||||
options = 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.fs = options.fs;
|
||||||
this.console = options.console || console;
|
this.console = options.console || console;
|
||||||
memory.track(this);
|
memory.track(this);
|
||||||
|
@ -318,15 +324,26 @@ TestRunner.prototype = {
|
||||||
return all(winPromises).then(() => {
|
return all(winPromises).then(() => {
|
||||||
let browserWins = wins.filter(isBrowser);
|
let browserWins = wins.filter(isBrowser);
|
||||||
let tabs = browserWins.reduce((tabs, window) => tabs.concat(getTabs(window)), []);
|
let tabs = browserWins.reduce((tabs, window) => tabs.concat(getTabs(window)), []);
|
||||||
|
let newTabID = getTabId(getSelectedTab(wins[0]));
|
||||||
if (wins.length != 1 || getInnerId(wins[0]) !== runnerWindows.get(this))
|
let oldTabID = runnerTabs.get(this);
|
||||||
this.fail("Should not be any unexpected windows open");
|
|
||||||
|
|
||||||
let hasMoreTabsOpen = browserWins.length && tabs.length != 1;
|
let hasMoreTabsOpen = browserWins.length && tabs.length != 1;
|
||||||
if (hasMoreTabsOpen)
|
let failure = false;
|
||||||
this.fail("Should not be any unexpected tabs open");
|
|
||||||
|
|
||||||
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:");
|
console.log("Windows open:");
|
||||||
for (let win of wins) {
|
for (let win of wins) {
|
||||||
if (isBrowser(win)) {
|
if (isBrowser(win)) {
|
||||||
|
@ -356,7 +373,7 @@ TestRunner.prototype = {
|
||||||
timer.setTimeout(_ => onDone(this));
|
timer.setTimeout(_ => onDone(this));
|
||||||
}
|
}
|
||||||
}).
|
}).
|
||||||
catch(e => console.exception(e));
|
catch(console.exception);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Set of assertion functions to wait for an assertion to become true
|
// Set of assertion functions to wait for an assertion to become true
|
||||||
|
|
|
@ -192,8 +192,10 @@ const InputStream = Class({
|
||||||
},
|
},
|
||||||
resume: function resume() {
|
resume: function resume() {
|
||||||
this.paused = false;
|
this.paused = false;
|
||||||
|
if (nsIInputStreamPump(this).isPending()) {
|
||||||
nsIInputStreamPump(this).resume();
|
nsIInputStreamPump(this).resume();
|
||||||
emit(this, "resume");
|
emit(this, "resume");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
close: function close() {
|
close: function close() {
|
||||||
this.readable = false;
|
this.readable = false;
|
||||||
|
|
|
@ -8,8 +8,9 @@ module.metadata = {
|
||||||
"stability": "unstable"
|
"stability": "unstable"
|
||||||
};
|
};
|
||||||
|
|
||||||
const { Trait } = require("../deprecated/light-traits");
|
const { Class } = require("../core/heritage");
|
||||||
const { EventEmitterTrait: EventEmitter } = require("../deprecated/events");
|
const { EventTarget } = require("../event/target");
|
||||||
|
const { emit } = require("../event/core");
|
||||||
const { DOMEventAssembler } = require("../deprecated/events/assembler");
|
const { DOMEventAssembler } = require("../deprecated/events/assembler");
|
||||||
const { browserWindowIterator } = require('../deprecated/window-utils');
|
const { browserWindowIterator } = require('../deprecated/window-utils');
|
||||||
const { isBrowser } = require('../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
|
// Event emitter objects used to register listeners and emit events on them
|
||||||
// when they occur.
|
// when they occur.
|
||||||
const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
|
const Observer = Class({
|
||||||
/**
|
implements: [DOMEventAssembler, EventTarget],
|
||||||
* Method is implemented by `EventEmitter` and is used just for emitting
|
initialize() {
|
||||||
* events on registered listeners.
|
// Adding each opened window to a list of observed windows.
|
||||||
*/
|
windowObserver.on("open", window => {
|
||||||
_emit: Trait.required,
|
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.
|
* Events that are supported and emitted by the module.
|
||||||
*/
|
*/
|
||||||
|
@ -34,24 +49,9 @@ const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* Keyboard event being emitted.
|
* Keyboard event being emitted.
|
||||||
*/
|
*/
|
||||||
handleEvent: function handleEvent(event) {
|
handleEvent(event) {
|
||||||
this._emit(event.type, event, event.target.ownerDocument.defaultView);
|
emit(this, event.type, event, event.target.ownerDocument.defaultView);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Adding each opened window to a list of observed windows.
|
exports.observer = new Observer();
|
||||||
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;
|
|
||||||
|
|
|
@ -50,7 +50,8 @@ function getPreferedLocales(caseSensitve) {
|
||||||
addLocale(browserUiLocale);
|
addLocale(browserUiLocale);
|
||||||
|
|
||||||
// Third priority is the list of locales used for web content
|
// 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) {
|
if (contentLocales) {
|
||||||
// This list is a string of locales seperated by commas.
|
// This list is a string of locales seperated by commas.
|
||||||
// There is spaces after commas, so strip each item
|
// There is spaces after commas, so strip each item
|
||||||
|
|
|
@ -107,6 +107,18 @@ function isObject(value) {
|
||||||
}
|
}
|
||||||
exports.isObject = isObject;
|
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.
|
* Returns true if `value` is an Array.
|
||||||
* @examples
|
* @examples
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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
|
* 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';
|
"use strict";
|
||||||
|
|
||||||
module.metadata = {
|
module.metadata = {
|
||||||
"stability": "unstable"
|
"stability": "unstable"
|
||||||
|
@ -14,7 +14,6 @@ module.metadata = {
|
||||||
require('chrome') // Otherwise CFX will complain about Components
|
require('chrome') // Otherwise CFX will complain about Components
|
||||||
require('toolkit/loader') // Otherwise CFX will stip out loader.js
|
require('toolkit/loader') // Otherwise CFX will stip out loader.js
|
||||||
require('sdk/addon/runner') // Otherwise CFX will stip out addon/runner.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;
|
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",
|
const loaderURI = module.uri.replace("sdk/loader/cuddlefish.js",
|
||||||
"toolkit/loader.js");
|
"toolkit/loader.js");
|
||||||
const xulappURI = module.uri.replace("loader/cuddlefish.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
|
// We need to keep a reference to the sandbox in order to unload it in
|
||||||
// bootstrap.js
|
// bootstrap.js
|
||||||
|
|
||||||
const loaderSandbox = loadSandbox(loaderURI);
|
const loaderSandbox = loadSandbox(loaderURI);
|
||||||
const loaderModule = loaderSandbox.exports;
|
const loaderModule = loaderSandbox.exports;
|
||||||
|
|
||||||
const xulappSandbox = loadSandbox(xulappURI);
|
const { incompatibility } = Cu.import(xulappURI, {}).XulApp;
|
||||||
const xulappModule = xulappSandbox.exports;
|
|
||||||
|
|
||||||
const { override, load } = loaderModule;
|
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) {
|
function CuddlefishLoader(options) {
|
||||||
let { manifest } = options;
|
let { manifest } = options;
|
||||||
|
|
||||||
|
@ -90,8 +41,7 @@ function CuddlefishLoader(options) {
|
||||||
// cache to avoid subsequent loads via `require`.
|
// cache to avoid subsequent loads via `require`.
|
||||||
modules: override({
|
modules: override({
|
||||||
'toolkit/loader': loaderModule,
|
'toolkit/loader': loaderModule,
|
||||||
'sdk/loader/cuddlefish': exports,
|
'sdk/loader/cuddlefish': exports
|
||||||
'sdk/system/xul-app': xulappModule
|
|
||||||
}, options.modules),
|
}, options.modules),
|
||||||
resolve: function resolve(id, requirer) {
|
resolve: function resolve(id, requirer) {
|
||||||
let entry = requirer && requirer in manifest && manifest[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 { Class } = require('./core/heritage');
|
||||||
const { Disposable } = require('./core/disposable');
|
const { Disposable } = require('./core/disposable');
|
||||||
const { WeakReference } = require('./core/reference');
|
const { WeakReference } = require('./core/reference');
|
||||||
const { Worker } = require('./content/worker-parent');
|
const { Worker } = require('./content/worker');
|
||||||
const { EventTarget } = require('./event/target');
|
const { EventTarget } = require('./event/target');
|
||||||
const { on, emit, once, setListeners } = require('./event/core');
|
const { on, emit, once, setListeners } = require('./event/core');
|
||||||
const { on: domOn, removeListener: domOff } = require('./dom/events');
|
const { on: domOn, removeListener: domOff } = require('./dom/events');
|
||||||
|
@ -189,6 +189,10 @@ function applyOnExistingDocuments (mod) {
|
||||||
getTabs().forEach(tab => {
|
getTabs().forEach(tab => {
|
||||||
// Fake a newly created document
|
// Fake a newly created document
|
||||||
let window = getTabContentWindow(tab);
|
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);
|
let uri = getTabURI(tab);
|
||||||
if (has(mod.attachTo, "top") && modMatchesURI(mod, uri))
|
if (has(mod.attachTo, "top") && modMatchesURI(mod, uri))
|
||||||
onContent(mod, window);
|
onContent(mod, window);
|
||||||
|
@ -258,6 +262,20 @@ function onContent (mod, window) {
|
||||||
return;
|
return;
|
||||||
domOff(window, eventName, onReady, true);
|
domOff(window, eventName, onReady, true);
|
||||||
createWorker(mod, window);
|
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);
|
}, 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");
|
let { deprecateUsage } = require("../util/deprecate");
|
||||||
|
|
||||||
deprecateUsage("Module 'sdk/page-mod/match-pattern' is deprecated use 'sdk/util/match-pattern' instead");
|
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 { merge } = require('./util/object');
|
||||||
const { data } = require('./self');
|
const { data } = require('./self');
|
||||||
|
|
||||||
const views = WeakMap();
|
const views = new WeakMap();
|
||||||
const workers = WeakMap();
|
const workers = new WeakMap();
|
||||||
const pages = WeakMap();
|
const pages = new WeakMap();
|
||||||
|
|
||||||
const readyEventNames = [
|
const readyEventNames = [
|
||||||
'DOMContentLoaded',
|
'DOMContentLoaded',
|
||||||
|
|
|
@ -15,12 +15,10 @@ module.metadata = {
|
||||||
|
|
||||||
const { Ci } = require("chrome");
|
const { Ci } = require("chrome");
|
||||||
const { setTimeout } = require('./timers');
|
const { setTimeout } = require('./timers');
|
||||||
const { isPrivateBrowsingSupported } = require('./self');
|
|
||||||
const { isWindowPBSupported } = require('./private-browsing/utils');
|
|
||||||
const { Class } = require("./core/heritage");
|
const { Class } = require("./core/heritage");
|
||||||
const { merge } = require("./util/object");
|
const { merge } = require("./util/object");
|
||||||
const { WorkerHost } = require("./content/utils");
|
const { WorkerHost } = require("./content/utils");
|
||||||
const { Worker } = require("./content/worker");
|
const { Worker } = require("./deprecated/sync-worker");
|
||||||
const { Disposable } = require("./core/disposable");
|
const { Disposable } = require("./core/disposable");
|
||||||
const { WeakReference } = require('./core/reference');
|
const { WeakReference } = require('./core/reference');
|
||||||
const { contract: loaderContract } = require("./content/loader");
|
const { contract: loaderContract } = require("./content/loader");
|
||||||
|
|
|
@ -286,8 +286,7 @@ function make(document) {
|
||||||
events.emit(type, { subject: panel });
|
events.emit(type, { subject: panel });
|
||||||
}
|
}
|
||||||
|
|
||||||
function onContentChange({subject, type}) {
|
function onContentChange({subject: document, type}) {
|
||||||
let document = subject;
|
|
||||||
if (document === getContentDocument(panel) && document.defaultView)
|
if (document === getContentDocument(panel) && document.defaultView)
|
||||||
events.emit(type, { subject: panel });
|
events.emit(type, { subject: panel });
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = {
|
module.metadata = {
|
||||||
"stability": "unstable",
|
"stability": "unstable",
|
||||||
"engines": {
|
"engines": {
|
||||||
"Firefox": "*"
|
"Firefox": "*",
|
||||||
|
"SeaMonkey": "*"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
module.metadata = {
|
module.metadata = {
|
||||||
'stability': 'experimental',
|
'stability': 'experimental',
|
||||||
'engines': {
|
'engines': {
|
||||||
'Firefox': '*'
|
'Firefox': '*',
|
||||||
|
"SeaMonkey": '*'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
module.metadata = {
|
module.metadata = {
|
||||||
"stability": "unstable",
|
"stability": "unstable",
|
||||||
"engines": {
|
"engines": {
|
||||||
"Firefox": "*"
|
"Firefox": "*",
|
||||||
|
"SeaMonkey": "*"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
module.metadata = {
|
module.metadata = {
|
||||||
"stability": "unstable",
|
"stability": "unstable",
|
||||||
"engines": {
|
"engines": {
|
||||||
"Firefox": "*"
|
"Firefox": "*",
|
||||||
|
"SeaMonkey": "*"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
module.metadata = {
|
module.metadata = {
|
||||||
"stability": "experimental",
|
"stability": "experimental",
|
||||||
"engines": {
|
"engines": {
|
||||||
"Firefox": "*"
|
"Firefox": "*",
|
||||||
|
"SeaMonkey": "*"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
module.metadata = {
|
module.metadata = {
|
||||||
"stability": "experimental",
|
"stability": "experimental",
|
||||||
"engines": {
|
"engines": {
|
||||||
"Firefox": "*"
|
"Firefox": "*",
|
||||||
|
"SeaMonkey": "*"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
module.metadata = {
|
module.metadata = {
|
||||||
"stability": "experimental",
|
"stability": "experimental",
|
||||||
"engines": {
|
"engines": {
|
||||||
"Firefox": "*"
|
"Firefox": "*",
|
||||||
|
"SeaMonkey": "*"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
module.metadata = {
|
module.metadata = {
|
||||||
"stability": "experimental",
|
"stability": "experimental",
|
||||||
"engines": {
|
"engines": {
|
||||||
"Firefox": "*"
|
"Firefox": "*",
|
||||||
|
"SeaMonkey": "*"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -123,6 +123,7 @@ function injectOptions({ preferences, preferencesBranch, document, parent, id })
|
||||||
setting.setAttribute('pref', 'extensions.' + preferencesBranch + '.' + name);
|
setting.setAttribute('pref', 'extensions.' + preferencesBranch + '.' + name);
|
||||||
setting.setAttribute('type', type);
|
setting.setAttribute('type', type);
|
||||||
setting.setAttribute('title', title);
|
setting.setAttribute('title', title);
|
||||||
|
if (description)
|
||||||
setting.setAttribute('desc', description);
|
setting.setAttribute('desc', description);
|
||||||
|
|
||||||
if (type === 'file' || type === 'directory') {
|
if (type === 'file' || type === 'directory') {
|
||||||
|
|
|
@ -21,106 +21,70 @@ const prefService = Cc["@mozilla.org/preferences-service;1"].
|
||||||
const prefSvc = prefService.getBranch(null);
|
const prefSvc = prefService.getBranch(null);
|
||||||
const defaultBranch = prefService.getDefaultBranch(null);
|
const defaultBranch = prefService.getDefaultBranch(null);
|
||||||
|
|
||||||
function Branch(branchName) {
|
const { Preferences } = require("resource://gre/modules/Preferences.jsm");
|
||||||
function getPrefKeys() {
|
const prefs = new Preferences({});
|
||||||
return keys(branchName).map(function(key) {
|
|
||||||
return key.replace(branchName, "");
|
const branchKeys = branchName =>
|
||||||
|
keys(branchName).map($ => $.replace(branchName, ""));
|
||||||
|
|
||||||
|
const Branch = function(branchName) {
|
||||||
|
return new Proxy(Branch.prototype, {
|
||||||
|
getOwnPropertyDescriptor(target, name, receiver) {
|
||||||
|
return {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
writable: false,
|
||||||
|
value: this.get(target, name, receiver)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
|
||||||
return {
|
|
||||||
value: get(branchName + name)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
enumerate: getPrefKeys,
|
|
||||||
keys: getPrefKeys
|
|
||||||
}, Branch.prototype);
|
|
||||||
}
|
|
||||||
|
|
||||||
function get(name, defaultValue) {
|
function get(name, defaultValue) {
|
||||||
switch (prefSvc.getPrefType(name)) {
|
return prefs.get(name, defaultValue);
|
||||||
case Ci.nsIPrefBranch.PREF_STRING:
|
|
||||||
return prefSvc.getComplexValue(name, Ci.nsISupportsString).data;
|
|
||||||
|
|
||||||
case Ci.nsIPrefBranch.PREF_INT:
|
|
||||||
return prefSvc.getIntPref(name);
|
|
||||||
|
|
||||||
case Ci.nsIPrefBranch.PREF_BOOL:
|
|
||||||
return prefSvc.getBoolPref(name);
|
|
||||||
|
|
||||||
case Ci.nsIPrefBranch.PREF_INVALID:
|
|
||||||
return defaultValue;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// This should never happen.
|
|
||||||
throw new Error("Error getting pref " + name +
|
|
||||||
"; its value's type is " +
|
|
||||||
prefSvc.getPrefType(name) +
|
|
||||||
", which I don't know " +
|
|
||||||
"how to handle.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
exports.get = get;
|
exports.get = get;
|
||||||
|
|
||||||
|
|
||||||
function set(name, value) {
|
function set(name, value) {
|
||||||
var prefType;
|
var prefType;
|
||||||
if (typeof value != "undefined" && value != null)
|
if (typeof value != "undefined" && value != null)
|
||||||
prefType = value.constructor.name;
|
prefType = value.constructor.name;
|
||||||
|
|
||||||
switch (prefType) {
|
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":
|
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)
|
if (value % 1 != 0)
|
||||||
throw new Error("cannot store non-integer number: " + value);
|
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;
|
exports.set = set;
|
||||||
|
|
||||||
function has(name) {
|
const has = prefs.has.bind(prefs)
|
||||||
return (prefSvc.getPrefType(name) != Ci.nsIPrefBranch.PREF_INVALID);
|
|
||||||
}
|
|
||||||
exports.has = has;
|
exports.has = has;
|
||||||
|
|
||||||
function keys(root) {
|
function keys(root) {
|
||||||
|
@ -128,9 +92,7 @@ function keys(root) {
|
||||||
}
|
}
|
||||||
exports.keys = keys;
|
exports.keys = keys;
|
||||||
|
|
||||||
function isSet(name) {
|
const isSet = prefs.isSet.bind(prefs);
|
||||||
return (has(name) && prefSvc.prefHasUserValue(name));
|
|
||||||
}
|
|
||||||
exports.isSet = isSet;
|
exports.isSet = isSet;
|
||||||
|
|
||||||
function reset(name) {
|
function reset(name) {
|
||||||
|
|
|
@ -8,20 +8,17 @@ module.metadata = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const { openTab, getBrowserForTab, getTabId } = require("sdk/tabs/utils");
|
const { openTab, getBrowserForTab, getTabId } = require("sdk/tabs/utils");
|
||||||
const { defer, all } = require("sdk/core/promise");
|
|
||||||
const { on, off } = require("sdk/system/events");
|
const { on, off } = require("sdk/system/events");
|
||||||
const { setTimeout } = require("sdk/timers");
|
|
||||||
const { getMostRecentBrowserWindow } = require('../window/utils');
|
const { getMostRecentBrowserWindow } = require('../window/utils');
|
||||||
|
|
||||||
const open = function open({ id }) {
|
// Opens about:addons in a new tab, then displays the inline
|
||||||
let showing = defer();
|
// preferences of the provided add-on
|
||||||
let loaded = defer();
|
const open = ({ id }) => new Promise((resolve, reject) => {
|
||||||
let result = { id: id };
|
// opening the about:addons page in a new tab
|
||||||
let tab = openTab(getMostRecentBrowserWindow(), "about:addons", {
|
let tab = openTab(getMostRecentBrowserWindow(), "about:addons");
|
||||||
inBackground: true
|
|
||||||
});
|
|
||||||
let browser = getBrowserForTab(tab);
|
let browser = getBrowserForTab(tab);
|
||||||
|
|
||||||
|
// waiting for the about:addons page to load
|
||||||
browser.addEventListener("load", function onPageLoad() {
|
browser.addEventListener("load", function onPageLoad() {
|
||||||
browser.removeEventListener("load", onPageLoad, true);
|
browser.removeEventListener("load", onPageLoad, true);
|
||||||
let window = browser.contentWindow;
|
let window = browser.contentWindow;
|
||||||
|
@ -30,21 +27,16 @@ const open = function open({ id }) {
|
||||||
on("addon-options-displayed", function onPrefDisplayed({ subject: doc, data }) {
|
on("addon-options-displayed", function onPrefDisplayed({ subject: doc, data }) {
|
||||||
if (data === id) {
|
if (data === id) {
|
||||||
off("addon-options-displayed", onPrefDisplayed);
|
off("addon-options-displayed", onPrefDisplayed);
|
||||||
result.tabId = getTabId(tab);
|
resolve({
|
||||||
result.document = doc;
|
id: id,
|
||||||
loaded.resolve();
|
tabId: getTabId(tab),
|
||||||
|
"document": doc
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
// display the add-on inline preferences page
|
// display the add-on inline preferences page
|
||||||
window.gViewController.commands.cmd_showItemDetails.doCommand({ id: id }, true);
|
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);
|
}, true);
|
||||||
|
});
|
||||||
return all([ showing.promise, loaded.promise ]).then(_ => result);
|
|
||||||
}
|
|
||||||
exports.open = open;
|
exports.open = open;
|
||||||
|
|
|
@ -21,7 +21,7 @@ const name = readPref("name") || options.name;
|
||||||
const version = readPref("version") || options.version;
|
const version = readPref("version") || options.version;
|
||||||
const loadReason = readPref("load.reason") || options.loadReason;
|
const loadReason = readPref("load.reason") || options.loadReason;
|
||||||
const rootURI = readPref("rootURI") || options.rootURI || "";
|
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 addonDataURI = baseURI + "data/";
|
||||||
const metadata = options.metadata || {};
|
const metadata = options.metadata || {};
|
||||||
const permissions = metadata.permissions || {};
|
const permissions = metadata.permissions || {};
|
||||||
|
@ -30,7 +30,10 @@ const isPacked = rootURI && rootURI.indexOf("jar:") === 0;
|
||||||
const uri = (path="") =>
|
const uri = (path="") =>
|
||||||
path.contains(":") ? path : addonDataURI + path.replace(/^\.\//, "");
|
path.contains(":") ? path : addonDataURI + path.replace(/^\.\//, "");
|
||||||
|
|
||||||
let { preferencesBranch } = options;
|
let preferencesBranch = ("preferences-branch" in metadata)
|
||||||
|
? metadata["preferences-branch"]
|
||||||
|
: options.preferencesBranch
|
||||||
|
|
||||||
if (/[^\w{@}.-]/.test(preferencesBranch)) {
|
if (/[^\w{@}.-]/.test(preferencesBranch)) {
|
||||||
preferencesBranch = id;
|
preferencesBranch = id;
|
||||||
console.warn("Ignoring preferences-branch (not a valid branch name)");
|
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 { setTimeout, clearTimeout } = require('../timers');
|
||||||
let isWindows = platform.indexOf('win') === 0;
|
let isWindows = platform.indexOf('win') === 0;
|
||||||
|
|
||||||
let processes = WeakMap();
|
let processes = new WeakMap();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,7 +7,6 @@ module.metadata = {
|
||||||
"stability": "experimental"
|
"stability": "experimental"
|
||||||
};
|
};
|
||||||
|
|
||||||
var { Cu } = require("chrome");
|
const { XulApp } = require("./xul-app.jsm");
|
||||||
var { XulApp } = Cu.import("resource://gre/modules/sdk/system/XulApp.js", {});
|
|
||||||
|
|
||||||
Object.keys(XulApp).forEach(k => exports[k] = XulApp[k]);
|
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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["XulApp"];
|
this.EXPORTED_SYMBOLS = [ "XulApp" ];
|
||||||
|
|
||||||
var { classes: Cc, interfaces: Ci } = Components;
|
var { classes: Cc, interfaces: Ci } = Components;
|
||||||
|
|
||||||
var exports = {};
|
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);
|
.getService(Ci.nsIXULAppInfo);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
// xpcshell test case
|
||||||
|
appInfo = {};
|
||||||
|
}
|
||||||
var vc = Cc["@mozilla.org/xpcom/version-comparator;1"]
|
var vc = Cc["@mozilla.org/xpcom/version-comparator;1"]
|
||||||
.getService(Ci.nsIVersionComparator);
|
.getService(Ci.nsIVersionComparator);
|
||||||
|
|
||||||
|
@ -28,13 +39,10 @@ var platformVersion = exports.platformVersion = appInfo.platformVersion;
|
||||||
// re-branded versions of a product have different names: for instance,
|
// re-branded versions of a product have different names: for instance,
|
||||||
// Firefox, Minefield, Iceweasel, and Shiretoko all have the same
|
// Firefox, Minefield, Iceweasel, and Shiretoko all have the same
|
||||||
// GUID.
|
// 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 = {
|
var ids = exports.ids = {
|
||||||
Firefox: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
|
Firefox: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
|
||||||
Mozilla: "{86c18b42-e466-45a9-ae7a-9b95ba6f5640}",
|
Mozilla: "{86c18b42-e466-45a9-ae7a-9b95ba6f5640}",
|
||||||
Sunbird: "{718e30fb-e89b-41dd-9da7-e25a45638b28}",
|
|
||||||
SeaMonkey: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
|
SeaMonkey: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
|
||||||
Fennec: "{aa3c5121-dab2-40e2-81ca-7ea25febc110}",
|
Fennec: "{aa3c5121-dab2-40e2-81ca-7ea25febc110}",
|
||||||
Thunderbird: "{3550f703-e582-4d05-9a08-453d09bdfdc6}"
|
Thunderbird: "{3550f703-e582-4d05-9a08-453d09bdfdc6}"
|
||||||
|
@ -183,3 +191,51 @@ function satisfiesVersion(version, versionRange) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
exports.satisfiesVersion = satisfiesVersion;
|
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"
|
"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 { DOMEventAssembler } = require("../deprecated/events/assembler");
|
||||||
const { Trait } = require("../deprecated/light-traits");
|
const { Class } = require("../core/heritage");
|
||||||
const { getActiveTab, getTabs, getTabContainer } = require("./utils");
|
const { getActiveTab, getTabs } = require("./utils");
|
||||||
const { browserWindowIterator } = require("../deprecated/window-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 { observer: windowObserver } = require("../windows/observer");
|
||||||
|
|
||||||
const EVENTS = {
|
const EVENTS = {
|
||||||
|
@ -24,15 +25,69 @@ const EVENTS = {
|
||||||
"TabUnpinned": "unpinned"
|
"TabUnpinned": "unpinned"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectedTab = Symbol("observer/state/selectedTab");
|
||||||
|
|
||||||
// Event emitter objects used to register listeners and emit events on them
|
// Event emitter objects used to register listeners and emit events on them
|
||||||
// when they occur.
|
// when they occur.
|
||||||
const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
|
const Observer = Class({
|
||||||
/**
|
implements: [EventTarget, DOMEventAssembler],
|
||||||
* Method is implemented by `EventEmitter` and is used just for emitting
|
initialize() {
|
||||||
* events on registered listeners.
|
this[selectedTab] = null;
|
||||||
*/
|
// Currently Gecko does not dispatch any event on the previously selected
|
||||||
_emit: Trait.required,
|
// 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.
|
* Events that are supported and emitted by the module.
|
||||||
*/
|
*/
|
||||||
|
@ -45,54 +100,8 @@ const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
|
||||||
* Keyboard event being emitted.
|
* Keyboard event being emitted.
|
||||||
*/
|
*/
|
||||||
handleEvent: function handleEvent(event) {
|
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
|
exports.observer = new Observer();
|
||||||
// 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;
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* 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';
|
'use strict';
|
||||||
|
|
||||||
const ContentWorker = require('../content/worker-parent').Worker;
|
const ContentWorker = require('../content/worker').Worker;
|
||||||
|
|
||||||
function Worker(options, window) {
|
function Worker(options, window) {
|
||||||
options.window = window;
|
options.window = window;
|
||||||
|
|
|
@ -8,10 +8,10 @@ module.metadata = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const { Cu } = require("chrome");
|
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 { defer } = require("sdk/core/promise");
|
||||||
const BaseAssert = require("sdk/test/assert").Assert;
|
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");
|
const { extend } = require("sdk/util/object");
|
||||||
|
|
||||||
exports.Assert = BaseAssert;
|
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
|
// If test function is a generator use a task JS to allow yield-ing
|
||||||
// style test runs.
|
// style test runs.
|
||||||
if (test.isGenerator && test.isGenerator()) {
|
if (isGenerator(test)) {
|
||||||
options.waitUntilDone();
|
options.waitUntilDone();
|
||||||
Task.spawn(test.bind(null, assert)).
|
Task.spawn(test.bind(null, assert)).
|
||||||
then(null, assert.fail).
|
catch(assert.fail).
|
||||||
then(assert.end);
|
then(assert.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +60,6 @@ function defineTestSuite(target, suite, prefix) {
|
||||||
// it means that test is async and second argument is a callback
|
// it means that test is async and second argument is a callback
|
||||||
// to notify that test is finished.
|
// to notify that test is finished.
|
||||||
else if (1 < test.length) {
|
else if (1 < test.length) {
|
||||||
|
|
||||||
// Letting test runner know that test is executed async and
|
// Letting test runner know that test is executed async and
|
||||||
// creating a callback function that CommonJS tests will call
|
// creating a callback function that CommonJS tests will call
|
||||||
// once it's done.
|
// once it's done.
|
||||||
|
|
|
@ -78,9 +78,9 @@ Assert.prototype = {
|
||||||
if ('operator' in e) {
|
if ('operator' in e) {
|
||||||
message += [
|
message += [
|
||||||
" -",
|
" -",
|
||||||
source(e.expected),
|
source(e.actual),
|
||||||
e.operator,
|
e.operator,
|
||||||
source(e.actual)
|
source(e.expected)
|
||||||
].join(" ");
|
].join(" ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,6 +89,7 @@ Assert.prototype = {
|
||||||
},
|
},
|
||||||
pass: function pass(message) {
|
pass: function pass(message) {
|
||||||
this._log.pass(message);
|
this._log.pass(message);
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
error: function error(e) {
|
error: function error(e) {
|
||||||
this._log.exception(e);
|
this._log.exception(e);
|
||||||
|
@ -101,10 +102,11 @@ Assert.prototype = {
|
||||||
message: message,
|
message: message,
|
||||||
operator: "=="
|
operator: "=="
|
||||||
});
|
});
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
this.pass(message);
|
this.pass(message);
|
||||||
}
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -115,15 +117,16 @@ Assert.prototype = {
|
||||||
equal: function equal(actual, expected, message) {
|
equal: function equal(actual, expected, message) {
|
||||||
if (actual == expected) {
|
if (actual == expected) {
|
||||||
this.pass(message);
|
this.pass(message);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
this.fail({
|
this.fail({
|
||||||
actual: actual,
|
actual: actual,
|
||||||
expected: expected,
|
expected: expected,
|
||||||
message: message,
|
message: message,
|
||||||
operator: "=="
|
operator: "=="
|
||||||
});
|
});
|
||||||
}
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -135,15 +138,16 @@ Assert.prototype = {
|
||||||
notEqual: function notEqual(actual, expected, message) {
|
notEqual: function notEqual(actual, expected, message) {
|
||||||
if (actual != expected) {
|
if (actual != expected) {
|
||||||
this.pass(message);
|
this.pass(message);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
this.fail({
|
this.fail({
|
||||||
actual: actual,
|
actual: actual,
|
||||||
expected: expected,
|
expected: expected,
|
||||||
message: message,
|
message: message,
|
||||||
operator: "!=",
|
operator: "!=",
|
||||||
});
|
});
|
||||||
}
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -154,15 +158,16 @@ Assert.prototype = {
|
||||||
deepEqual: function deepEqual(actual, expected, message) {
|
deepEqual: function deepEqual(actual, expected, message) {
|
||||||
if (isDeepEqual(actual, expected)) {
|
if (isDeepEqual(actual, expected)) {
|
||||||
this.pass(message);
|
this.pass(message);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
this.fail({
|
this.fail({
|
||||||
actual: actual,
|
actual: actual,
|
||||||
expected: expected,
|
expected: expected,
|
||||||
message: message,
|
message: message,
|
||||||
operator: "deepEqual"
|
operator: "deepEqual"
|
||||||
});
|
});
|
||||||
}
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -174,15 +179,16 @@ Assert.prototype = {
|
||||||
notDeepEqual: function notDeepEqual(actual, expected, message) {
|
notDeepEqual: function notDeepEqual(actual, expected, message) {
|
||||||
if (!isDeepEqual(actual, expected)) {
|
if (!isDeepEqual(actual, expected)) {
|
||||||
this.pass(message);
|
this.pass(message);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
this.fail({
|
this.fail({
|
||||||
actual: actual,
|
actual: actual,
|
||||||
expected: expected,
|
expected: expected,
|
||||||
message: message,
|
message: message,
|
||||||
operator: "notDeepEqual"
|
operator: "notDeepEqual"
|
||||||
});
|
});
|
||||||
}
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -194,15 +200,16 @@ Assert.prototype = {
|
||||||
strictEqual: function strictEqual(actual, expected, message) {
|
strictEqual: function strictEqual(actual, expected, message) {
|
||||||
if (actual === expected) {
|
if (actual === expected) {
|
||||||
this.pass(message);
|
this.pass(message);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
this.fail({
|
this.fail({
|
||||||
actual: actual,
|
actual: actual,
|
||||||
expected: expected,
|
expected: expected,
|
||||||
message: message,
|
message: message,
|
||||||
operator: "==="
|
operator: "==="
|
||||||
});
|
});
|
||||||
}
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -214,15 +221,16 @@ Assert.prototype = {
|
||||||
notStrictEqual: function notStrictEqual(actual, expected, message) {
|
notStrictEqual: function notStrictEqual(actual, expected, message) {
|
||||||
if (actual !== expected) {
|
if (actual !== expected) {
|
||||||
this.pass(message);
|
this.pass(message);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
this.fail({
|
this.fail({
|
||||||
actual: actual,
|
actual: actual,
|
||||||
expected: expected,
|
expected: expected,
|
||||||
message: message,
|
message: message,
|
||||||
operator: "!=="
|
operator: "!=="
|
||||||
})
|
});
|
||||||
}
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -275,35 +283,36 @@ Assert.prototype = {
|
||||||
if (threw && (isUndefined(Error) ||
|
if (threw && (isUndefined(Error) ||
|
||||||
// If passed `Error` is RegExp using it's test method to
|
// If passed `Error` is RegExp using it's test method to
|
||||||
// assert thrown exception message.
|
// 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
|
// If passed `Error` is a constructor function testing if
|
||||||
// thrown exception is an instance of it.
|
// thrown exception is an instance of it.
|
||||||
(isFunction(Error) && instanceOf(exception, Error))))
|
(isFunction(Error) && instanceOf(exception, Error))))
|
||||||
{
|
{
|
||||||
this.pass(message);
|
this.pass(message);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise we report assertion failure.
|
// Otherwise we report assertion failure.
|
||||||
else {
|
|
||||||
let failure = {
|
let failure = {
|
||||||
message: message,
|
message: message,
|
||||||
operator: "throws"
|
operator: "matches"
|
||||||
};
|
};
|
||||||
|
|
||||||
if (exception)
|
if (exception) {
|
||||||
failure.actual = exception;
|
failure.actual = exception.message || exception.toString();
|
||||||
|
}
|
||||||
|
|
||||||
if (Error)
|
if (Error) {
|
||||||
failure.expected = Error;
|
failure.expected = Error.toString();
|
||||||
|
}
|
||||||
|
|
||||||
this.fail(failure);
|
this.fail(failure);
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
exports.Assert = Assert;
|
exports.Assert = Assert;
|
||||||
|
|
||||||
function isDeepEqual(actual, expected) {
|
function isDeepEqual(actual, expected) {
|
||||||
|
|
||||||
// 7.1. All identical values are equivalent, as determined by ===.
|
// 7.1. All identical values are equivalent, as determined by ===.
|
||||||
if (actual === expected) {
|
if (actual === expected) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -58,11 +58,7 @@ var stopOnError;
|
||||||
var findAndRunTests;
|
var findAndRunTests;
|
||||||
|
|
||||||
// Combined information from all test runs.
|
// Combined information from all test runs.
|
||||||
var results = {
|
var results;
|
||||||
passed: 0,
|
|
||||||
failed: 0,
|
|
||||||
testRuns: []
|
|
||||||
};
|
|
||||||
|
|
||||||
// A list of the compartments and windows loaded after startup
|
// A list of the compartments and windows loaded after startup
|
||||||
var startLeaks;
|
var startLeaks;
|
||||||
|
@ -438,7 +434,8 @@ var POINTLESS_ERRORS = [
|
||||||
'file: "chrome://browser/content/',
|
'file: "chrome://browser/content/',
|
||||||
'file: "chrome://global/content/',
|
'file: "chrome://global/content/',
|
||||||
'[JavaScript Warning: "The character encoding of a framed document was ' +
|
'[JavaScript Warning: "The character encoding of a framed document was ' +
|
||||||
'not declared.'
|
'not declared.',
|
||||||
|
'file: "chrome://browser/skin/'
|
||||||
];
|
];
|
||||||
|
|
||||||
var consoleListener = {
|
var consoleListener = {
|
||||||
|
@ -590,6 +587,12 @@ var runTests = exports.runTests = function runTests(options) {
|
||||||
print = options.print;
|
print = options.print;
|
||||||
findAndRunTests = options.findAndRunTests;
|
findAndRunTests = options.findAndRunTests;
|
||||||
|
|
||||||
|
results = {
|
||||||
|
passed: 0,
|
||||||
|
failed: 0,
|
||||||
|
testRuns: []
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
consoleListener.register();
|
consoleListener.register();
|
||||||
print("Running tests on " + system.name + " " + system.version +
|
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';
|
'use strict';
|
||||||
|
|
||||||
const { Cu } = require("chrome");
|
const { Cu } = require("chrome");
|
||||||
|
|
|
@ -12,6 +12,9 @@ const { setInterval, clearInterval } = require('../timers');
|
||||||
const { getTabs, closeTab } = require("../tabs/utils");
|
const { getTabs, closeTab } = require("../tabs/utils");
|
||||||
const { windows: getWindows } = require("../window/utils");
|
const { windows: getWindows } = require("../window/utils");
|
||||||
const { close: closeWindow } = require("../window/helpers");
|
const { close: closeWindow } = require("../window/helpers");
|
||||||
|
const { isGenerator } = require("../lang/type");
|
||||||
|
|
||||||
|
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||||
|
|
||||||
function getTestNames (exports)
|
function getTestNames (exports)
|
||||||
Object.keys(exports).filter(name => /^test/.test(name))
|
Object.keys(exports).filter(name => /^test/.test(name))
|
||||||
|
@ -29,16 +32,37 @@ function isHelperAsync (fn) fn.length > 2
|
||||||
function before (exports, beforeFn) {
|
function before (exports, beforeFn) {
|
||||||
getTestNames(exports).map(name => {
|
getTestNames(exports).map(name => {
|
||||||
let testFn = exports[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);
|
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);
|
testFn(assert);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (isTestAsync(testFn) && !isHelperAsync(beforeFn)) {
|
else if (!isTestAsync(testFn) && !isHelperAsync(beforeFn)) {
|
||||||
exports[name] = function (assert, done) {
|
exports[name] = function (assert) {
|
||||||
beforeFn(name, assert);
|
beforeFn(name, assert);
|
||||||
testFn(assert, done);
|
testFn(assert);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (!isTestAsync(testFn) && isHelperAsync(beforeFn)) {
|
else if (!isTestAsync(testFn) && isHelperAsync(beforeFn)) {
|
||||||
|
@ -48,7 +72,21 @@ function before (exports, beforeFn) {
|
||||||
done();
|
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) {
|
exports[name] = function (assert, done) {
|
||||||
beforeFn(name, assert, () => {
|
beforeFn(name, assert, () => {
|
||||||
testFn(assert, done);
|
testFn(assert, done);
|
||||||
|
@ -69,30 +107,62 @@ exports.before = before;
|
||||||
function after (exports, afterFn) {
|
function after (exports, afterFn) {
|
||||||
getTestNames(exports).map(name => {
|
getTestNames(exports).map(name => {
|
||||||
let testFn = exports[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) {
|
exports[name] = function (assert) {
|
||||||
testFn(assert);
|
testFn(assert);
|
||||||
afterFn(name, 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)) {
|
else if (!isTestAsync(testFn) && isHelperAsync(afterFn)) {
|
||||||
exports[name] = function (assert, done) {
|
exports[name] = function (assert, done) {
|
||||||
testFn(assert);
|
testFn(assert);
|
||||||
afterFn(name, assert, done);
|
afterFn(name, assert, done);
|
||||||
};
|
};
|
||||||
} else if (isTestAsync(testFn) && isHelperAsync(afterFn)) {
|
}
|
||||||
exports[name] = function (assert, done) {
|
// ASYNC TESTS
|
||||||
testFn(assert, () => {
|
else if (isTestAsync(testFn) && isGenerator(afterFn)) {
|
||||||
afterFn(name, assert, done);
|
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 = {
|
module.metadata = {
|
||||||
'stability': 'experimental',
|
'stability': 'experimental',
|
||||||
'engines': {
|
'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 { ns } = require('../core/namespace');
|
||||||
const { remove: removeFromArray } = require('../util/array');
|
const { remove: removeFromArray } = require('../util/array');
|
||||||
const { show, hide, toggle } = require('./sidebar/actions');
|
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 { contract: sidebarContract } = require('./sidebar/contract');
|
||||||
const { create, dispose, updateTitle, updateURL, isSidebarShowing, showSidebar, hideSidebar } = require('./sidebar/view');
|
const { create, dispose, updateTitle, updateURL, isSidebarShowing, showSidebar, hideSidebar } = require('./sidebar/view');
|
||||||
const { defer } = require('../core/promise');
|
const { defer } = require('../core/promise');
|
||||||
|
|
|
@ -8,7 +8,9 @@
|
||||||
module.metadata = {
|
module.metadata = {
|
||||||
'stability': 'experimental',
|
'stability': 'experimental',
|
||||||
'engines': {
|
'engines': {
|
||||||
'Firefox': '*'
|
'Firefox': '*',
|
||||||
|
'SeaMonkey': '*',
|
||||||
|
'Thunderbird': '*'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,9 @@
|
||||||
module.metadata = {
|
module.metadata = {
|
||||||
'stability': 'experimental',
|
'stability': 'experimental',
|
||||||
'engines': {
|
'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
|
/* 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
|
* 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";
|
"use strict";
|
||||||
|
|
||||||
module.metadata = {
|
module.metadata = {
|
||||||
|
@ -22,14 +21,15 @@ module.metadata = {
|
||||||
// - `_` used for argument(s) or variable(s) who's values are ignored.
|
// - `_` used for argument(s) or variable(s) who's values are ignored.
|
||||||
|
|
||||||
const { complement, flip, identity } = require("../lang/functional");
|
const { complement, flip, identity } = require("../lang/functional");
|
||||||
const { isArray, isArguments, isMap, isSet,
|
const { isArray, isArguments, isMap, isSet, isGenerator,
|
||||||
isString, isBoolean, isNumber } = require("../lang/type");
|
isString, isBoolean, isNumber } = require("../lang/type");
|
||||||
|
|
||||||
const Sequence = function Sequence(iterator) {
|
const Sequence = function Sequence(iterator) {
|
||||||
if (iterator.isGenerator && iterator.isGenerator())
|
if (!isGenerator(iterator)) {
|
||||||
this[Symbol.iterator] = iterator;
|
|
||||||
else
|
|
||||||
throw TypeError("Expected generator argument");
|
throw TypeError("Expected generator argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
this[Symbol.iterator] = iterator;
|
||||||
};
|
};
|
||||||
exports.Sequence = Sequence;
|
exports.Sequence = Sequence;
|
||||||
|
|
||||||
|
@ -61,9 +61,6 @@ const seq = polymorphic({
|
||||||
});
|
});
|
||||||
exports.seq = seq;
|
exports.seq = seq;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Function to cast seq to string.
|
// Function to cast seq to string.
|
||||||
const string = (...etc) => "".concat(...etc);
|
const string = (...etc) => "".concat(...etc);
|
||||||
exports.string = string;
|
exports.string = string;
|
||||||
|
@ -111,6 +108,27 @@ const pairs = polymorphic({
|
||||||
});
|
});
|
||||||
exports.pairs = pairs;
|
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({
|
const keys = polymorphic({
|
||||||
null: empty,
|
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 XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
|
||||||
|
|
||||||
|
const prefs = require("../preferences/service");
|
||||||
const BROWSER = 'navigator:browser',
|
const BROWSER = 'navigator:browser',
|
||||||
URI_BROWSER = 'chrome://browser/content/browser.xul',
|
URI_BROWSER = prefs.get('browser.chromeURL', null),
|
||||||
NAME = '_blank',
|
NAME = '_blank',
|
||||||
FEATURES = 'chrome,all,dialog=no,non-private';
|
FEATURES = 'chrome,all,dialog=no,non-private';
|
||||||
|
|
||||||
|
@ -186,6 +187,9 @@ function open(uri, options) {
|
||||||
uri = uri || URI_BROWSER;
|
uri = uri || URI_BROWSER;
|
||||||
options = options || {};
|
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)
|
if (['chrome', 'resource', 'data'].indexOf(io.newURI(uri, null, null).scheme) < 0)
|
||||||
throw new Error('only chrome, resource and data uris are allowed');
|
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'),
|
{ EventEmitter } = require('../deprecated/events'),
|
||||||
{ WindowTabs, WindowTabTracker } = require('./tabs-firefox'),
|
{ WindowTabs, WindowTabTracker } = require('./tabs-firefox'),
|
||||||
{ WindowDom } = require('./dom'),
|
{ WindowDom } = require('./dom'),
|
||||||
{ WindowLoader } = require('./loader'),
|
|
||||||
{ isBrowser, getWindowDocShell, isFocused,
|
{ isBrowser, getWindowDocShell, isFocused,
|
||||||
windows: windowIterator, isWindowPrivate } = require('../window/utils'),
|
windows: windowIterator, isWindowPrivate } = require('../window/utils'),
|
||||||
{ Options } = require('../tabs/common'),
|
{ Options } = require('../tabs/common'),
|
||||||
|
@ -23,7 +22,10 @@ const { windowNS } = require('../window/namespace');
|
||||||
const { isPrivateBrowsingSupported } = require('../self');
|
const { isPrivateBrowsingSupported } = require('../self');
|
||||||
const { ignoreWindow, isPrivate } = require('sdk/private-browsing/utils');
|
const { ignoreWindow, isPrivate } = require('sdk/private-browsing/utils');
|
||||||
const { viewFor } = require('../view/core');
|
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
|
* Window trait composes safe wrappers for browser window that are E10S
|
||||||
* compatible.
|
* compatible.
|
||||||
|
@ -33,12 +35,96 @@ const BrowserWindowTrait = Trait.compose(
|
||||||
WindowDom.resolve({ close: '_close' }),
|
WindowDom.resolve({ close: '_close' }),
|
||||||
WindowTabs,
|
WindowTabs,
|
||||||
WindowTabTracker,
|
WindowTabTracker,
|
||||||
WindowLoader,
|
|
||||||
/* WindowSidebars, */
|
/* WindowSidebars, */
|
||||||
Trait.compose({
|
Trait.compose({
|
||||||
_emit: Trait.required,
|
_emit: Trait.required,
|
||||||
_close: 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.
|
* Constructor returns wrapper of the specified chrome window.
|
||||||
* @param {nsIWindow} 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"
|
"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 { WindowTracker, windowIterator } = require("../deprecated/window-utils");
|
||||||
const { DOMEventAssembler } = require("../deprecated/events/assembler");
|
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
|
// Event emitter objects used to register listeners and emit events on them
|
||||||
// when they occur.
|
// when they occur.
|
||||||
const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
|
const Observer = Class({
|
||||||
/**
|
initialize() {
|
||||||
* Method is implemented by `EventEmitter` and is used just for emitting
|
// Using `WindowTracker` to track window events.
|
||||||
* events on registered listeners.
|
WindowTracker({
|
||||||
*/
|
onTrack: chromeWindow => {
|
||||||
_emit: Trait.required,
|
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.
|
* Events that are supported and emitted by the module.
|
||||||
*/
|
*/
|
||||||
|
@ -31,21 +41,9 @@ const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* Keyboard event being emitted.
|
* Keyboard event being emitted.
|
||||||
*/
|
*/
|
||||||
handleEvent: function handleEvent(event) {
|
handleEvent(event) {
|
||||||
this._emit(event.type, event.target, event);
|
emit(this, event.type, event.target, event);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Using `WindowTracker` to track window events.
|
exports.observer = new Observer();
|
||||||
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;
|
|
||||||
|
|
|
@ -158,7 +158,7 @@ function onTabSelect(event) {
|
||||||
emit(tab, 'activate', tab);
|
emit(tab, 'activate', tab);
|
||||||
emit(gTabs, 'activate', tab);
|
emit(gTabs, 'activate', tab);
|
||||||
|
|
||||||
for (let of in gTabs) {
|
for (let t of gTabs) {
|
||||||
if (t === tab) continue;
|
if (t === tab) continue;
|
||||||
emit(t, 'deactivate', t);
|
emit(t, 'deactivate', t);
|
||||||
emit(gTabs, '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
|
* 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/. */
|
||||||
|
|
||||||
;(function(id, factory) { // Module boilerplate :(
|
;((factory) => { // Module boilerplate :(
|
||||||
if (typeof(define) === 'function') { // RequireJS
|
if (typeof(require) === 'function') { // CommonJS
|
||||||
define(factory);
|
require("chrome").Cu.import(module.uri, exports);
|
||||||
} 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 });
|
|
||||||
}
|
}
|
||||||
}).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';
|
'use strict';
|
||||||
|
|
||||||
|
@ -38,11 +30,16 @@ const { loadSubScript } = Cc['@mozilla.org/moz/jssubscript-loader;1'].
|
||||||
getService(Ci.mozIJSSubScriptLoader);
|
getService(Ci.mozIJSSubScriptLoader);
|
||||||
const { notifyObservers } = Cc['@mozilla.org/observer-service;1'].
|
const { notifyObservers } = Cc['@mozilla.org/observer-service;1'].
|
||||||
getService(Ci.nsIObserverService);
|
getService(Ci.nsIObserverService);
|
||||||
|
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
|
||||||
const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.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");
|
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.
|
// Define some shortcuts.
|
||||||
const bind = Function.call.bind(Function.bind);
|
const bind = Function.call.bind(Function.bind);
|
||||||
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
||||||
|
@ -90,7 +87,7 @@ const descriptor = iced(function descriptor(object) {
|
||||||
});
|
});
|
||||||
return value;
|
return value;
|
||||||
});
|
});
|
||||||
exports.descriptor = descriptor;
|
Loader.descriptor = descriptor;
|
||||||
|
|
||||||
// Freeze important built-ins so they can't be used by untrusted code as a
|
// Freeze important built-ins so they can't be used by untrusted code as a
|
||||||
// message passing channel.
|
// message passing channel.
|
||||||
|
@ -127,15 +124,15 @@ const override = iced(function override(target, source) {
|
||||||
});
|
});
|
||||||
return define({}, properties);
|
return define({}, properties);
|
||||||
});
|
});
|
||||||
exports.override = override;
|
Loader.override = override;
|
||||||
|
|
||||||
function sourceURI(uri) { return String(uri).split(" -> ").pop(); }
|
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 isntLoaderFrame(frame) { return frame.fileName !== module.uri }
|
||||||
|
|
||||||
function parseURI(uri) { return String(uri).split(" -> ").pop(); }
|
function parseURI(uri) { return String(uri).split(" -> ").pop(); }
|
||||||
exports.parseURI = parseURI;
|
Loader.parseURI = parseURI;
|
||||||
|
|
||||||
function parseStack(stack) {
|
function parseStack(stack) {
|
||||||
let lines = String(stack).split("\n");
|
let lines = String(stack).split("\n");
|
||||||
|
@ -158,7 +155,7 @@ function parseStack(stack) {
|
||||||
return frames;
|
return frames;
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
exports.parseStack = parseStack;
|
Loader.parseStack = parseStack;
|
||||||
|
|
||||||
function serializeStack(frames) {
|
function serializeStack(frames) {
|
||||||
return frames.reduce(function(stack, frame) {
|
return frames.reduce(function(stack, frame) {
|
||||||
|
@ -169,7 +166,7 @@ function serializeStack(frames) {
|
||||||
stack;
|
stack;
|
||||||
}, "");
|
}, "");
|
||||||
}
|
}
|
||||||
exports.serializeStack = serializeStack;
|
Loader.serializeStack = serializeStack;
|
||||||
|
|
||||||
function readURI(uri) {
|
function readURI(uri) {
|
||||||
let stream = NetUtil.newChannel2(uri,
|
let stream = NetUtil.newChannel2(uri,
|
||||||
|
@ -201,7 +198,7 @@ function join (...paths) {
|
||||||
resolved = resolved.replace(/^chrome\:\/([^\/])/, 'chrome://$1');
|
resolved = resolved.replace(/^chrome\:\/([^\/])/, 'chrome://$1');
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
exports.join = join;
|
Loader.join = join;
|
||||||
|
|
||||||
// Function takes set of options and returns a JS sandbox. Function may be
|
// Function takes set of options and returns a JS sandbox. Function may be
|
||||||
// passed set of options:
|
// passed set of options:
|
||||||
|
@ -253,7 +250,7 @@ const Sandbox = iced(function Sandbox(options) {
|
||||||
|
|
||||||
return sandbox;
|
return sandbox;
|
||||||
});
|
});
|
||||||
exports.Sandbox = Sandbox;
|
Loader.Sandbox = Sandbox;
|
||||||
|
|
||||||
// Evaluates code from the given `uri` into given `sandbox`. If
|
// Evaluates code from the given `uri` into given `sandbox`. If
|
||||||
// `options.source` is passed, then that code is evaluated instead.
|
// `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)
|
return source ? Cu.evalInSandbox(source, sandbox, version, uri, line)
|
||||||
: loadSubScript(uri, sandbox, encoding);
|
: loadSubScript(uri, sandbox, encoding);
|
||||||
});
|
});
|
||||||
exports.evaluate = evaluate;
|
Loader.evaluate = evaluate;
|
||||||
|
|
||||||
// Populates `exports` of the given CommonJS `module` object, in the context
|
// Populates `exports` of the given CommonJS `module` object, in the context
|
||||||
// of the given `loader` by evaluating code associated with it.
|
// 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)
|
descriptors[name] = getOwnPropertyDescriptor(globals, name)
|
||||||
});
|
});
|
||||||
define(sandbox, descriptors);
|
define(sandbox, descriptors);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
sandbox = Sandbox({
|
sandbox = Sandbox({
|
||||||
name: module.uri,
|
name: module.uri,
|
||||||
prototype: create(globals, descriptors),
|
prototype: create(globals, descriptors),
|
||||||
|
@ -323,7 +321,8 @@ const load = iced(function load(loader, module) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
evaluate(sandbox, module.uri);
|
evaluate(sandbox, module.uri);
|
||||||
} catch (error) {
|
}
|
||||||
|
catch (error) {
|
||||||
let { message, fileName, lineNumber } = error;
|
let { message, fileName, lineNumber } = error;
|
||||||
let stack = error.stack || Error().stack;
|
let stack = error.stack || Error().stack;
|
||||||
let frames = parseStack(stack).filter(isntLoaderFrame);
|
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')
|
if (module.exports && typeof(module.exports) === 'object')
|
||||||
freeze(module.exports);
|
freeze(module.exports);
|
||||||
|
|
||||||
return module;
|
return module;
|
||||||
});
|
});
|
||||||
exports.load = load;
|
Loader.load = load;
|
||||||
|
|
||||||
// Utility function to normalize module `uri`s so they have `.js` extension.
|
// Utility function to normalize module `uri`s so they have `.js` extension.
|
||||||
function normalizeExt (uri) {
|
function normalizeExt (uri) {
|
||||||
|
@ -403,7 +409,7 @@ const resolve = iced(function resolve(id, base) {
|
||||||
|
|
||||||
return resolved;
|
return resolved;
|
||||||
});
|
});
|
||||||
exports.resolve = resolve;
|
Loader.resolve = resolve;
|
||||||
|
|
||||||
// Node-style module lookup
|
// Node-style module lookup
|
||||||
// Takes an id and path and attempts to load a file using node's resolving
|
// 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
|
// http://nodejs.org/api/modules.html#modules_all_together
|
||||||
const nodeResolve = iced(function nodeResolve(id, requirer, { rootURI }) {
|
const nodeResolve = iced(function nodeResolve(id, requirer, { rootURI }) {
|
||||||
// Resolve again
|
// 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'
|
// we assume that extensions are correct, i.e., a directory doesnt't have '.js'
|
||||||
// and a js file isn't named 'file.json.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.
|
// with `resolveURI` -- if during runtime, then `resolve` will throw.
|
||||||
return void 0;
|
return void 0;
|
||||||
});
|
});
|
||||||
exports.nodeResolve = nodeResolve;
|
Loader.nodeResolve = nodeResolve;
|
||||||
|
|
||||||
// Attempts to load `path` and then `path.js`
|
// Attempts to load `path` and then `path.js`
|
||||||
// Returns `path` with valid file, or `undefined` otherwise
|
// 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
|
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`
|
// 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
|
// 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
|
// found in the paths most likely, like `sdk/tabs`, which should
|
||||||
// be resolved relatively if needed using traditional resolve
|
// be resolved relatively if needed using traditional resolve
|
||||||
if (!requirement) {
|
if (!requirement) {
|
||||||
requirement = isRelative(id) ? exports.resolve(id, requirer.id) : id;
|
requirement = isRelative(id) ? Loader.resolve(id, requirer.id) : id;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Resolve `id` to its requirer if it's relative.
|
// 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;
|
require.main = loader.main === requirer ? requirer : undefined;
|
||||||
return iced(require);
|
return iced(require);
|
||||||
});
|
});
|
||||||
exports.Require = Require;
|
Loader.Require = Require;
|
||||||
|
|
||||||
const main = iced(function main(loader, id) {
|
const main = iced(function main(loader, id) {
|
||||||
// If no main entry provided, and native loader is used,
|
// 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);
|
let module = loader.main = loader.modules[uri] = Module(id, uri);
|
||||||
return loader.load(loader, module).exports;
|
return loader.load(loader, module).exports;
|
||||||
});
|
});
|
||||||
exports.main = main;
|
Loader.main = main;
|
||||||
|
|
||||||
// Makes module object that is made available to CommonJS modules when they
|
// Makes module object that is made available to CommonJS modules when they
|
||||||
// are evaluated, along with `exports` and `require`.
|
// are evaluated, along with `exports` and `require`.
|
||||||
|
@ -701,7 +707,7 @@ const Module = iced(function Module(id, uri) {
|
||||||
uri: { value: uri }
|
uri: { value: uri }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
exports.Module = Module;
|
Loader.Module = Module;
|
||||||
|
|
||||||
// Takes `loader`, and unload `reason` string and notifies all observers that
|
// Takes `loader`, and unload `reason` string and notifies all observers that
|
||||||
// they should cleanup after them-self.
|
// they should cleanup after them-self.
|
||||||
|
@ -716,7 +722,7 @@ const unload = iced(function unload(loader, reason) {
|
||||||
let subject = { wrappedJSObject: loader.destructor };
|
let subject = { wrappedJSObject: loader.destructor };
|
||||||
notifyObservers(subject, 'sdk:loader:destroy', reason);
|
notifyObservers(subject, 'sdk:loader:destroy', reason);
|
||||||
});
|
});
|
||||||
exports.unload = unload;
|
Loader.unload = unload;
|
||||||
|
|
||||||
// Function makes new loader that can be used to load CommonJS modules
|
// Function makes new loader that can be used to load CommonJS modules
|
||||||
// described by a given `options.manifest`. Loader takes following options:
|
// 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.
|
// module object (that has `uri` property) and `baseURI` of the loader.
|
||||||
// If `resolve` does not returns `uri` string exception will be thrown by
|
// If `resolve` does not returns `uri` string exception will be thrown by
|
||||||
// an associated `require` call.
|
// an associated `require` call.
|
||||||
const Loader = iced(function Loader(options) {
|
function Loader(options) {
|
||||||
let console = new ConsoleAPI({
|
|
||||||
consoleID: options.id ? "addon/" + options.id : ""
|
|
||||||
});
|
|
||||||
|
|
||||||
let {
|
let {
|
||||||
modules, globals, resolve, paths, rootURI, manifest, requireMap, isNative,
|
modules, globals, resolve, paths, rootURI, manifest, requireMap, isNative,
|
||||||
metadata, sharedGlobal, sharedGlobalBlacklist
|
metadata, sharedGlobal, sharedGlobalBlacklist, checkCompatibility
|
||||||
} = override({
|
} = override({
|
||||||
paths: {},
|
paths: {},
|
||||||
modules: {},
|
modules: {},
|
||||||
globals: {
|
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 ?
|
resolve: options.isNative ?
|
||||||
// Make the returned resolve function have the same signature
|
// Make the returned resolve function have the same signature
|
||||||
(id, requirer) => exports.nodeResolve(id, requirer, { rootURI: rootURI }) :
|
(id, requirer) => Loader.nodeResolve(id, requirer, { rootURI: rootURI }) :
|
||||||
exports.resolve,
|
Loader.resolve,
|
||||||
sharedGlobalBlacklist: ["sdk/indexed-db"]
|
sharedGlobalBlacklist: ["sdk/indexed-db"]
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
|
@ -825,6 +836,7 @@ const Loader = iced(function Loader(options) {
|
||||||
invisibleToDebugger: { enumerable: false,
|
invisibleToDebugger: { enumerable: false,
|
||||||
value: options.invisibleToDebugger || false },
|
value: options.invisibleToDebugger || false },
|
||||||
load: { enumerable: false, value: options.load || load },
|
load: { enumerable: false, value: options.load || load },
|
||||||
|
checkCompatibility: { enumerable: false, value: checkCompatibility },
|
||||||
// Main (entry point) module, it can be set only once, since loader
|
// Main (entry point) module, it can be set only once, since loader
|
||||||
// instance can have only one main module.
|
// instance can have only one main module.
|
||||||
main: new function() {
|
main: new function() {
|
||||||
|
@ -846,8 +858,8 @@ const Loader = iced(function Loader(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return freeze(create(null, returnObj));
|
return freeze(create(null, returnObj));
|
||||||
});
|
};
|
||||||
exports.Loader = Loader;
|
Loader.Loader = Loader;
|
||||||
|
|
||||||
let isJSONURI = uri => uri.substr(-5) === '.json';
|
let isJSONURI = uri => uri.substr(-5) === '.json';
|
||||||
let isJSMURI = uri => uri.substr(-4) === '.jsm';
|
let isJSMURI = uri => uri.substr(-4) === '.jsm';
|
||||||
|
@ -860,7 +872,7 @@ let isRelative = id => id[0] === '.'
|
||||||
const generateMap = iced(function generateMap(options, callback) {
|
const generateMap = iced(function generateMap(options, callback) {
|
||||||
let { rootURI, resolve, paths } = override({
|
let { rootURI, resolve, paths } = override({
|
||||||
paths: {},
|
paths: {},
|
||||||
resolve: exports.nodeResolve
|
resolve: Loader.nodeResolve
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
rootURI = addTrailingSlash(rootURI);
|
rootURI = addTrailingSlash(rootURI);
|
||||||
|
@ -882,7 +894,7 @@ const generateMap = iced(function generateMap(options, callback) {
|
||||||
}, {}, callback);
|
}, {}, callback);
|
||||||
|
|
||||||
});
|
});
|
||||||
exports.generateMap = generateMap;
|
Loader.generateMap = generateMap;
|
||||||
|
|
||||||
// Default `main` entry to './index.js' and ensure is relative,
|
// Default `main` entry to './index.js' and ensure is relative,
|
||||||
// since node allows 'lib/index.js' without relative `./`
|
// since node allows 'lib/index.js' without relative `./`
|
||||||
|
@ -950,6 +962,8 @@ function findModuleIncludes (uri, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function walk (src, 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);
|
let nodes = Reflect.parse(src);
|
||||||
traverse(nodes, callback);
|
traverse(nodes, callback);
|
||||||
}
|
}
|
||||||
|
@ -988,4 +1002,5 @@ function isRequire (node) {
|
||||||
&& node.arguments[0].type === 'Literal';
|
&& 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 make = (exports, rootURI, components) => {
|
||||||
const { Loader: { Loader, Require, Module, main } } =
|
const { Loader: { Loader, Require, Module, main } } =
|
||||||
components.utils.import(rootURI + "toolkit/loader.js", {});
|
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
|
// Below we define `require` & `require.resolve` that resolve passed
|
||||||
// module id relative to the caller URI. This is not perfect but good
|
// 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
|
// enough for common case & there is always an option to pass absolute
|
||||||
// id when that
|
// id when that
|
||||||
// but presumably well enough to cover
|
// but presumably well enough to cover
|
||||||
|
|
||||||
const require = id => {
|
const require = (id, options={}) => {
|
||||||
|
const { reload, all } = options;
|
||||||
const requirerURI = components.stack.caller.filename;
|
const requirerURI = components.stack.caller.filename;
|
||||||
const requirer = Module(requirerURI, requirerURI);
|
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 => {
|
require.resolve = id => {
|
||||||
|
|
|
@ -7,9 +7,10 @@ var EXPORTED_SYMBOLS = ["Startup"];
|
||||||
|
|
||||||
const { utils: Cu, interfaces: Ci, classes: Cc } = Components;
|
const { utils: Cu, interfaces: Ci, classes: Cc } = Components;
|
||||||
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
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 { 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"]
|
const appStartupSrv = Cc["@mozilla.org/toolkit/app-startup;1"]
|
||||||
.getService(Ci.nsIAppStartup);
|
.getService(Ci.nsIAppStartup);
|
||||||
|
|
||||||
|
|
|
@ -6,5 +6,4 @@
|
||||||
|
|
||||||
EXTRA_JS_MODULES.sdk.system += [
|
EXTRA_JS_MODULES.sdk.system += [
|
||||||
'Startup.js',
|
'Startup.js',
|
||||||
'XulApp.js',
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,7 +5,32 @@
|
||||||
"javascript", "engine", "addon", "extension",
|
"javascript", "engine", "addon", "extension",
|
||||||
"xulrunner", "firefox", "browser"
|
"xulrunner", "firefox", "browser"
|
||||||
],
|
],
|
||||||
"loader": "lib/sdk/loader/cuddlefish.js",
|
|
||||||
"license": "MPL 2.0",
|
"license": "MPL 2.0",
|
||||||
"unpack": true
|
"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,
|
jid=jid,
|
||||||
update_url=options.update_url,
|
update_url=options.update_url,
|
||||||
bootstrap=True,
|
bootstrap=True,
|
||||||
enable_mobile=options.enable_mobile,
|
enable_mobile=options.enable_mobile)
|
||||||
harness_options=harness_options)
|
|
||||||
|
|
||||||
if command == "xpi" and options.update_link:
|
if command == "xpi" and options.update_link:
|
||||||
if not options.update_link.startswith("https"):
|
if not options.update_link.startswith("https"):
|
||||||
|
|
|
@ -417,6 +417,9 @@ class ManifestBuilder:
|
||||||
# test-securable-module.js, and the modules/red.js
|
# test-securable-module.js, and the modules/red.js
|
||||||
# that it imports, both do that intentionally
|
# that it imports, both do that intentionally
|
||||||
continue
|
continue
|
||||||
|
if reqname.endswith(".jsm"):
|
||||||
|
# ignore JSM modules
|
||||||
|
continue
|
||||||
if not self.abort_on_missing:
|
if not self.abort_on_missing:
|
||||||
# print a warning, but tolerate missing modules
|
# print a warning, but tolerate missing modules
|
||||||
# unless cfx --abort-on-missing-module flag was set
|
# unless cfx --abort-on-missing-module flag was set
|
||||||
|
@ -802,4 +805,3 @@ if __name__ == '__main__':
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
print "requires: %s" % (",".join(sorted(requires.keys())))
|
print "requires: %s" % (",".join(sorted(requires.keys())))
|
||||||
print "locations: %s" % locations
|
print "locations: %s" % locations
|
||||||
|
|
||||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче