Bug 1114752 - Uplift Add-on SDK to Firefox a=me

--HG--
rename : addon-sdk/source/test/fixtures/test-page-worker.html => addon-sdk/source/test/addons/e10s-content/data/test-page-worker.html
rename : addon-sdk/source/test/fixtures/test-page-worker.js => addon-sdk/source/test/addons/e10s-content/data/test-page-worker.js
rename : addon-sdk/source/test/addons/places/favicon-helpers.js => addon-sdk/source/test/addons/places/lib/favicon-helpers.js
rename : addon-sdk/source/test/addons/places/httpd.js => addon-sdk/source/test/addons/places/lib/httpd.js
rename : addon-sdk/source/test/addons/places/places-helper.js => addon-sdk/source/test/addons/places/lib/places-helper.js
rename : addon-sdk/source/test/addons/places/tests/test-places-utils.js => addon-sdk/source/test/addons/places/lib/test-places-utils.js
rename : addon-sdk/source/test/fixtures/test-page-worker.html => addon-sdk/source/test/fixtures/addon-sdk/data/test-page-worker.html
rename : addon-sdk/source/test/fixtures/test-page-worker.js => addon-sdk/source/test/fixtures/addon-sdk/data/test-page-worker.js
This commit is contained in:
Erik Vold 2015-02-03 09:51:16 -08:00
parent 92399a3f06
commit eba25816af
349 changed files with 16843 additions and 4402 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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);
});

View File

@ -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());
}

View File

@ -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());
}

View File

@ -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);
});
});

View File

@ -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;

13
addon-sdk/source/bootstrap.js vendored Normal file
View File

@ -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);

View File

@ -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);

View File

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

View File

@ -1,7 +1,10 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
exports.testMain = function(test) {
test.pass("TODO: Write some tests.");
exports.testMain = function(assert) {
assert.pass("TODO: Write some tests.");
};
require("sdk/test").run(exports);

View File

@ -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);

View File

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

View File

@ -1,7 +1,10 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
exports.testMain = function(test) {
test.pass("TODO: Write some tests.");
exports.testMain = function(assert) {
assert.pass("TODO: Write some tests.");
};
require("sdk/test").run(exports);

View File

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

View File

@ -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);

View File

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

View File

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

View File

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

View File

@ -1,3 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.volcan=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
"use strict";

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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/. */

View File

@ -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;

View File

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

View File

@ -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 }));

View File

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

View File

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

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

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

View File

@ -1,7 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {

View File

@ -1,7 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {

View File

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

View File

@ -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));
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,8 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
let { deprecateUsage } = require("../util/deprecate");
deprecateUsage("Module 'sdk/page-mod/match-pattern' is deprecated use 'sdk/util/match-pattern' instead");

View File

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

View File

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

View File

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

View File

@ -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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -58,11 +58,7 @@ var stopOnError;
var findAndRunTests;
// Combined information from all test runs.
var results = {
passed: 0,
failed: 0,
testRuns: []
};
var results;
// A list of the compartments and windows loaded after startup
var startLeaks;
@ -438,7 +434,8 @@ var POINTLESS_ERRORS = [
'file: "chrome://browser/content/',
'file: "chrome://global/content/',
'[JavaScript Warning: "The character encoding of a framed document was ' +
'not declared.'
'not declared.',
'file: "chrome://browser/skin/'
];
var consoleListener = {
@ -590,6 +587,12 @@ var runTests = exports.runTests = function runTests(options) {
print = options.print;
findAndRunTests = options.findAndRunTests;
results = {
passed: 0,
failed: 0,
testRuns: []
};
try {
consoleListener.register();
print("Running tests on " + system.name + " " + system.version +

View File

@ -1,3 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const { Cu } = require("chrome");

View File

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

View File

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

View File

@ -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;

View File

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

View File

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

View File

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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

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

View File

@ -25,8 +25,9 @@ const FM = Cc["@mozilla.org/focus-manager;1"].
const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
const prefs = require("../preferences/service");
const BROWSER = 'navigator:browser',
URI_BROWSER = 'chrome://browser/content/browser.xul',
URI_BROWSER = prefs.get('browser.chromeURL', null),
NAME = '_blank',
FEATURES = 'chrome,all,dialog=no,non-private';
@ -186,6 +187,9 @@ function open(uri, options) {
uri = uri || URI_BROWSER;
options = options || {};
if (!uri)
throw new Error('browser.chromeURL is undefined, please provide an explicit uri');
if (['chrome', 'resource', 'data'].indexOf(io.newURI(uri, null, null).scheme) < 0)
throw new Error('only chrome, resource and data uris are allowed');

View File

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

View File

@ -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;

View File

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

View File

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

View File

@ -2,28 +2,20 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
;(function(id, factory) { // Module boilerplate :(
if (typeof(define) === 'function') { // RequireJS
define(factory);
} else if (typeof(require) === 'function') { // CommonJS
factory.call(this, require, exports, module);
} else if (~String(this).indexOf('BackstagePass')) { // JSM
this[factory.name] = {};
factory(function require(uri) {
var imports = {};
this['Components'].utils.import(uri, imports);
return imports;
}, this[factory.name], { uri: __URI__, id: id });
this.EXPORTED_SYMBOLS = [factory.name];
} else if (~String(this).indexOf('Sandbox')) { // Sandbox
factory(function require(uri) {}, this, { uri: __URI__, id: id });
} else { // Browser or alike
var globals = this
factory(function require(id) {
return globals[id];
}, (globals[id] = {}), { uri: document.location.href + '#' + id, id: id });
;((factory) => { // Module boilerplate :(
if (typeof(require) === 'function') { // CommonJS
require("chrome").Cu.import(module.uri, exports);
}
}).call(this, 'loader', function Loader(require, exports, module) {
else if (~String(this).indexOf('BackstagePass')) { // JSM
let module = { uri: __URI__, id: "toolkit/loader", exports: Object.create(null) }
factory(module);
Object.assign(this, module.exports);
this.EXPORTED_SYMBOLS = Object.getOwnPropertyNames(module.exports);
}
else {
throw Error("Loading environment is not supported");
}
})(module => {
'use strict';
@ -38,11 +30,16 @@ const { loadSubScript } = Cc['@mozilla.org/moz/jssubscript-loader;1'].
getService(Ci.mozIJSSubScriptLoader);
const { notifyObservers } = Cc['@mozilla.org/observer-service;1'].
getService(Ci.nsIObserverService);
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
const { Reflect } = Cu.import("resource://gre/modules/reflect.jsm", {});
const { ConsoleAPI } = Cu.import("resource://gre/modules/devtools/Console.jsm");
const { join: pathJoin, normalize, dirname } = Cu.import("resource://gre/modules/osfile/ospath_unix.jsm");
XPCOMUtils.defineLazyGetter(this, "XulApp", () => {
let xulappURI = module.uri.replace("toolkit/loader.js",
"sdk/system/xul-app.jsm");
return Cu.import(xulappURI, {});
});
// Define some shortcuts.
const bind = Function.call.bind(Function.bind);
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
@ -90,7 +87,7 @@ const descriptor = iced(function descriptor(object) {
});
return value;
});
exports.descriptor = descriptor;
Loader.descriptor = descriptor;
// Freeze important built-ins so they can't be used by untrusted code as a
// message passing channel.
@ -127,15 +124,15 @@ const override = iced(function override(target, source) {
});
return define({}, properties);
});
exports.override = override;
Loader.override = override;
function sourceURI(uri) { return String(uri).split(" -> ").pop(); }
exports.sourceURI = iced(sourceURI);
Loader.sourceURI = iced(sourceURI);
function isntLoaderFrame(frame) { return frame.fileName !== module.uri }
function parseURI(uri) { return String(uri).split(" -> ").pop(); }
exports.parseURI = parseURI;
Loader.parseURI = parseURI;
function parseStack(stack) {
let lines = String(stack).split("\n");
@ -158,7 +155,7 @@ function parseStack(stack) {
return frames;
}, []);
}
exports.parseStack = parseStack;
Loader.parseStack = parseStack;
function serializeStack(frames) {
return frames.reduce(function(stack, frame) {
@ -169,7 +166,7 @@ function serializeStack(frames) {
stack;
}, "");
}
exports.serializeStack = serializeStack;
Loader.serializeStack = serializeStack;
function readURI(uri) {
let stream = NetUtil.newChannel2(uri,
@ -201,7 +198,7 @@ function join (...paths) {
resolved = resolved.replace(/^chrome\:\/([^\/])/, 'chrome://$1');
return resolved;
}
exports.join = join;
Loader.join = join;
// Function takes set of options and returns a JS sandbox. Function may be
// passed set of options:
@ -253,7 +250,7 @@ const Sandbox = iced(function Sandbox(options) {
return sandbox;
});
exports.Sandbox = Sandbox;
Loader.Sandbox = Sandbox;
// Evaluates code from the given `uri` into given `sandbox`. If
// `options.source` is passed, then that code is evaluated instead.
@ -273,7 +270,7 @@ const evaluate = iced(function evaluate(sandbox, uri, options) {
return source ? Cu.evalInSandbox(source, sandbox, version, uri, line)
: loadSubScript(uri, sandbox, encoding);
});
exports.evaluate = evaluate;
Loader.evaluate = evaluate;
// Populates `exports` of the given CommonJS `module` object, in the context
// of the given `loader` by evaluating code associated with it.
@ -306,7 +303,8 @@ const load = iced(function load(loader, module) {
descriptors[name] = getOwnPropertyDescriptor(globals, name)
});
define(sandbox, descriptors);
} else {
}
else {
sandbox = Sandbox({
name: module.uri,
prototype: create(globals, descriptors),
@ -323,7 +321,8 @@ const load = iced(function load(loader, module) {
try {
evaluate(sandbox, module.uri);
} catch (error) {
}
catch (error) {
let { message, fileName, lineNumber } = error;
let stack = error.stack || Error().stack;
let frames = parseStack(stack).filter(isntLoaderFrame);
@ -361,12 +360,19 @@ const load = iced(function load(loader, module) {
});
}
if (loader.checkCompatibility) {
let err = XulApp.incompatibility(module);
if (err) {
throw err;
}
}
if (module.exports && typeof(module.exports) === 'object')
freeze(module.exports);
return module;
});
exports.load = load;
Loader.load = load;
// Utility function to normalize module `uri`s so they have `.js` extension.
function normalizeExt (uri) {
@ -403,7 +409,7 @@ const resolve = iced(function resolve(id, base) {
return resolved;
});
exports.resolve = resolve;
Loader.resolve = resolve;
// Node-style module lookup
// Takes an id and path and attempts to load a file using node's resolving
@ -412,7 +418,7 @@ exports.resolve = resolve;
// http://nodejs.org/api/modules.html#modules_all_together
const nodeResolve = iced(function nodeResolve(id, requirer, { rootURI }) {
// Resolve again
id = exports.resolve(id, requirer);
id = Loader.resolve(id, requirer);
// we assume that extensions are correct, i.e., a directory doesnt't have '.js'
// and a js file isn't named 'file.json.js'
@ -441,7 +447,7 @@ const nodeResolve = iced(function nodeResolve(id, requirer, { rootURI }) {
// with `resolveURI` -- if during runtime, then `resolve` will throw.
return void 0;
});
exports.nodeResolve = nodeResolve;
Loader.nodeResolve = nodeResolve;
// Attempts to load `path` and then `path.js`
// Returns `path` with valid file, or `undefined` otherwise
@ -538,7 +544,7 @@ const resolveURI = iced(function resolveURI(id, mapping) {
}
return void 0; // otherwise we raise a warning, see bug 910304
});
exports.resolveURI = resolveURI;
Loader.resolveURI = resolveURI;
// Creates version of `require` that will be exposed to the given `module`
// in the context of the given `loader`. Each module gets own limited copy
@ -652,7 +658,7 @@ const Require = iced(function Require(loader, requirer) {
// found in the paths most likely, like `sdk/tabs`, which should
// be resolved relatively if needed using traditional resolve
if (!requirement) {
requirement = isRelative(id) ? exports.resolve(id, requirer.id) : id;
requirement = isRelative(id) ? Loader.resolve(id, requirer.id) : id;
}
} else {
// Resolve `id` to its requirer if it's relative.
@ -679,7 +685,7 @@ const Require = iced(function Require(loader, requirer) {
require.main = loader.main === requirer ? requirer : undefined;
return iced(require);
});
exports.Require = Require;
Loader.Require = Require;
const main = iced(function main(loader, id) {
// If no main entry provided, and native loader is used,
@ -690,7 +696,7 @@ const main = iced(function main(loader, id) {
let module = loader.main = loader.modules[uri] = Module(id, uri);
return loader.load(loader, module).exports;
});
exports.main = main;
Loader.main = main;
// Makes module object that is made available to CommonJS modules when they
// are evaluated, along with `exports` and `require`.
@ -701,7 +707,7 @@ const Module = iced(function Module(id, uri) {
uri: { value: uri }
});
});
exports.Module = Module;
Loader.Module = Module;
// Takes `loader`, and unload `reason` string and notifies all observers that
// they should cleanup after them-self.
@ -716,7 +722,7 @@ const unload = iced(function unload(loader, reason) {
let subject = { wrappedJSObject: loader.destructor };
notifyObservers(subject, 'sdk:loader:destroy', reason);
});
exports.unload = unload;
Loader.unload = unload;
// Function makes new loader that can be used to load CommonJS modules
// described by a given `options.manifest`. Loader takes following options:
@ -731,24 +737,29 @@ exports.unload = unload;
// module object (that has `uri` property) and `baseURI` of the loader.
// If `resolve` does not returns `uri` string exception will be thrown by
// an associated `require` call.
const Loader = iced(function Loader(options) {
let console = new ConsoleAPI({
consoleID: options.id ? "addon/" + options.id : ""
});
function Loader(options) {
let {
modules, globals, resolve, paths, rootURI, manifest, requireMap, isNative,
metadata, sharedGlobal, sharedGlobalBlacklist
metadata, sharedGlobal, sharedGlobalBlacklist, checkCompatibility
} = override({
paths: {},
modules: {},
globals: {
console: console
get console() {
// Import Console.jsm from here to prevent loading it until someone uses it
let { ConsoleAPI } = Cu.import("resource://gre/modules/devtools/Console.jsm");
let console = new ConsoleAPI({
consoleID: options.id ? "addon/" + options.id : ""
});
Object.defineProperty(this, "console", { value: console });
return this.console;
}
},
checkCompatibility: false,
resolve: options.isNative ?
// Make the returned resolve function have the same signature
(id, requirer) => exports.nodeResolve(id, requirer, { rootURI: rootURI }) :
exports.resolve,
(id, requirer) => Loader.nodeResolve(id, requirer, { rootURI: rootURI }) :
Loader.resolve,
sharedGlobalBlacklist: ["sdk/indexed-db"]
}, options);
@ -825,6 +836,7 @@ const Loader = iced(function Loader(options) {
invisibleToDebugger: { enumerable: false,
value: options.invisibleToDebugger || false },
load: { enumerable: false, value: options.load || load },
checkCompatibility: { enumerable: false, value: checkCompatibility },
// Main (entry point) module, it can be set only once, since loader
// instance can have only one main module.
main: new function() {
@ -846,8 +858,8 @@ const Loader = iced(function Loader(options) {
}
return freeze(create(null, returnObj));
});
exports.Loader = Loader;
};
Loader.Loader = Loader;
let isJSONURI = uri => uri.substr(-5) === '.json';
let isJSMURI = uri => uri.substr(-4) === '.jsm';
@ -860,7 +872,7 @@ let isRelative = id => id[0] === '.'
const generateMap = iced(function generateMap(options, callback) {
let { rootURI, resolve, paths } = override({
paths: {},
resolve: exports.nodeResolve
resolve: Loader.nodeResolve
}, options);
rootURI = addTrailingSlash(rootURI);
@ -882,7 +894,7 @@ const generateMap = iced(function generateMap(options, callback) {
}, {}, callback);
});
exports.generateMap = generateMap;
Loader.generateMap = generateMap;
// Default `main` entry to './index.js' and ensure is relative,
// since node allows 'lib/index.js' without relative `./`
@ -950,6 +962,8 @@ function findModuleIncludes (uri, callback) {
}
function walk (src, callback) {
// Import Reflect.jsm from here to prevent loading it until someone uses it
let { Reflect } = Cu.import("resource://gre/modules/reflect.jsm", {});
let nodes = Reflect.parse(src);
traverse(nodes, callback);
}
@ -988,4 +1002,5 @@ function isRequire (node) {
&& node.arguments[0].type === 'Literal';
}
module.exports = iced(Loader);
});

View File

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

View File

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

View File

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

View File

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

View File

@ -861,8 +861,7 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None,
jid=jid,
update_url=options.update_url,
bootstrap=True,
enable_mobile=options.enable_mobile,
harness_options=harness_options)
enable_mobile=options.enable_mobile)
if command == "xpi" and options.update_link:
if not options.update_link.startswith("https"):

View File

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

View File

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

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