import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,8 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.exports = require("./test-main.js");
require('sdk/test/runner').runTestsFromModule(module);

View file

@ -0,0 +1,12 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { id } = require("sdk/self");
const { getAddonByID } = require("sdk/addon/manager");
exports["test getAddonByID"] = function*(assert) {
let addon = yield getAddonByID(id);
assert.equal(addon.id, id, "getAddonByID works");
}

View file

@ -0,0 +1,7 @@
{
"id": "test-addon-manager@jetpack",
"main": "./lib/main.js",
"name": "test-addon-manager",
"version": "0.0.1",
"author": "Erik Vold"
}

View file

@ -0,0 +1,14 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const { id } = require('sdk/self');
const { getAddonByID } = require('sdk/addon/manager');
exports.testContributors = function*(assert) {
let addon = yield getAddonByID(id);
assert.equal(addon.creator.name, 'test <test@mozilla.com>', '< and > characters work');
}
require('sdk/test/runner').runTestsFromModule(module);

View file

@ -0,0 +1,6 @@
{
"id": "test-addon-author-email@jetpack",
"author": "test <test@mozilla.com>",
"version": "0.0.1",
"main": "./main.js"
}

View file

@ -0,0 +1,39 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* Ensures using child_process and underlying subprocess.jsm
* works within an addon
*/
const { exec } = require("sdk/system/child_process");
const { platform, pathFor } = require("sdk/system");
const PROFILE_DIR = pathFor("ProfD");
const isWindows = platform.toLowerCase().indexOf("win") === 0;
const app = require("sdk/system/xul-app");
// Once Bug 903018 is resolved, just move the application testing to
// module.metadata.engines
if (app.is("Firefox")) {
exports["test child_process in an addon"] = (assert, done) => {
exec(isWindows ? "DIR /A-D" : "ls -al", {
cwd: PROFILE_DIR
}, (err, stdout, stderr) => {
assert.equal(err, null, "no errors");
assert.equal(stderr, "", "stderr is empty");
assert.ok(/extensions\.ini/.test(stdout), "stdout output of `ls -al` finds files");
if (isWindows)
assert.ok(!/<DIR>/.test(stdout), "passing args works");
else
assert.ok(/d(r[-|w][-|x]){3}/.test(stdout), "passing args works");
done();
});
};
} else {
exports["test unsupported"] = (assert) => assert.pass("This application is unsupported.");
}
require("sdk/test/runner").runTestsFromModule(module);

View file

@ -0,0 +1,5 @@
{
"id": "test-child-process@jetpack",
"main": "./index.js",
"version": "0.0.1"
}

View file

@ -0,0 +1,5 @@
content test chrome/content/
skin test classic/1.0 chrome/skin/
locale test en-US chrome/locale/en-US/
locale test ja-JP chrome/locale/ja-JP/

View file

@ -0,0 +1,4 @@
<?xml version="1.0"?>
<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
windowtype="test:window">
</dialog>

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/. -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body></body>
</html>

View file

@ -0,0 +1 @@
test=テスト

View file

@ -0,0 +1,4 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
test{}

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';
self.port.on('echo', _ => {
self.port.emit('echo', '');
});
self.port.emit('start', '');

View file

@ -0,0 +1,97 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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, Cc, Ci } = require('chrome');
const Request = require('sdk/request').Request;
const { WindowTracker } = require('sdk/deprecated/window-utils');
const { close, open } = require('sdk/window/helpers');
const { data } = require('sdk/self');
const { Panel } = require('sdk/panel');
const XUL_URL = 'chrome://test/content/new-window.xul'
const { Services } = Cu.import('resource://gre/modules/Services.jsm', {});
const { NetUtil } = Cu.import('resource://gre/modules/NetUtil.jsm', {});
exports.testChromeSkin = function(assert, done) {
let skinURL = 'chrome://test/skin/style.css';
Request({
url: skinURL,
overrideMimeType: 'text/plain',
onComplete: function (response) {
assert.ok(/test\{\}\s*$/.test(response.text), 'chrome.manifest skin folder was registered!');
done();
}
}).get();
assert.pass('requesting ' + skinURL);
}
exports.testChromeContent = function(assert, done) {
let wt = WindowTracker({
onTrack: function(window) {
if (window.document.documentElement.getAttribute('windowtype') === 'test:window') {
assert.pass('test xul window was opened');
wt.unload();
close(window).then(done, assert.fail);
}
}
});
open(XUL_URL).then(
assert.pass.bind(assert, 'opened ' + XUL_URL),
assert.fail);
assert.pass('opening ' + XUL_URL);
}
exports.testChromeLocale = function(assert) {
let jpLocalePath = Cc['@mozilla.org/chrome/chrome-registry;1'].
getService(Ci.nsIChromeRegistry).
convertChromeURL(NetUtil.newURI('chrome://test/locale/description.properties')).
spec.replace(/(en\-US|ja\-JP)/, 'ja-JP');
let enLocalePath = jpLocalePath.replace(/ja\-JP/, 'en-US');
let jpStringBundle = Services.strings.createBundle(jpLocalePath);
assert.equal(jpStringBundle.GetStringFromName('test'),
'テスト',
'locales ja-JP folder was copied correctly');
let enStringBundle = Services.strings.createBundle(enLocalePath);
assert.equal(enStringBundle.GetStringFromName('test'),
'Test',
'locales en-US folder was copied correctly');
}
exports.testChromeInPanel = function*(assert) {
let panel = Panel({
contentURL: 'chrome://test/content/panel.html',
contentScriptWhen: 'end',
contentScriptFile: data.url('panel.js')
});
yield new Promise(resolve => panel.port.once('start', resolve));
assert.pass('start was emitted');
yield new Promise(resolve => {
panel.once('show', resolve);
panel.show();
});
assert.pass('panel shown');
yield new Promise(resolve => {
panel.port.once('echo', resolve);
panel.port.emit('echo');
});
assert.pass('got echo');
panel.destroy();
assert.pass('panel is destroyed');
}
require('sdk/test/runner').runTestsFromModule(module);

View file

@ -0,0 +1,5 @@
{
"id": "test-chrome@jetpack",
"main": "./main.js",
"version": "0.0.1"
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,89 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 { PageMod } = require("sdk/page-mod");
const tabs = require("sdk/tabs");
const { startServerAsync } = require("./httpd");
const serverPort = 8099;
const TEST_TAB_URL = "about:mozilla";
exports.testCrossDomainIframe = function(assert, done) {
let server = startServerAsync(serverPort);
server.registerPathHandler("/iframe", function handle(request, response) {
response.write("<html><body>foo</body></html>");
});
let pageMod = PageMod({
include: TEST_TAB_URL,
contentScript: "new " + function ContentScriptScope() {
self.on("message", function (url) {
let iframe = document.createElement("iframe");
iframe.addEventListener("load", function onload() {
iframe.removeEventListener("load", onload, false);
self.postMessage(iframe.contentWindow.document.body.innerHTML);
}, false);
iframe.setAttribute("src", url);
document.documentElement.appendChild(iframe);
});
},
onAttach: function(w) {
w.on("message", function (body) {
assert.equal(body, "foo", "received iframe html content");
pageMod.destroy();
w.tab.close(function() {
server.stop(done);
});
});
w.postMessage("http://localhost:" + serverPort + "/iframe");
}
});
tabs.open({
url: TEST_TAB_URL,
inBackground: true
});
};
exports.testCrossDomainXHR = function(assert, done) {
let server = startServerAsync(serverPort);
server.registerPathHandler("/xhr", function handle(request, response) {
response.write("foo");
});
let pageMod = PageMod({
include: TEST_TAB_URL,
contentScript: "new " + function ContentScriptScope() {
self.on("message", function (url) {
let request = new XMLHttpRequest();
request.overrideMimeType("text/plain");
request.open("GET", url, true);
request.onload = function () {
self.postMessage(request.responseText);
};
request.send(null);
});
},
onAttach: function(w) {
w.on("message", function (body) {
assert.equal(body, "foo", "received XHR content");
pageMod.destroy();
w.tab.close(function() {
server.stop(done);
});
});
w.postMessage("http://localhost:" + serverPort + "/xhr");
}
});
tabs.open({
url: TEST_TAB_URL,
inBackground: true
});
};
require("sdk/test/runner").runTestsFromModule(module);

View file

@ -0,0 +1,8 @@
{
"id": "content-permissions@jetpack",
"permissions": {
"cross-domain-content": ["http://localhost:8099"]
},
"main": "./main.js",
"version": "0.0.1"
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,90 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 { PageMod } = require("sdk/page-mod");
const tabs = require("sdk/tabs");
const { startServerAsync } = require("./httpd");
const { setTimeout } = require("sdk/timers");
const serverPort = 8099;
exports.testContentScriptLatencyRegression = function*(assert) {
let server = startServerAsync(serverPort);
server.registerPathHandler("/", function handle(request, response) {
response.write(`<html>
<head>
<link rel="stylesheet" href="/slow.css">
</head>
<body>
slow loading page...
</body>
</html>`);
});
server.registerPathHandler("/slow.css", function handle(request, response) {
response.processAsync();
response.setHeader('Content-Type', 'text/css', false);
setTimeout(_ => {
response.write("body { background: red; }");
response.finish();
}, 2000);
});
let pageMod;
let worker = yield new Promise((resolve) => {
pageMod = PageMod({
include: "http://localhost:8099/",
attachTo: "top",
contentScriptWhen: "start",
contentScript: "new " + function ContentScriptScope() {
self.port.on("a-port-message", function () {
self.port.emit("document-ready-state", document.readyState);
});
},
onAttach: function(w) {
resolve(w);
}
});
tabs.open({
url: "http://localhost:8099/",
inBackground: true
});
});
worker.port.emit("a-port-message");
let waitForPortMessage = new Promise((resolve) => {
worker.port.once("document-ready-state", (msg) => {
resolve(msg);
});
});
let documentReadyState = yield waitForPortMessage;
assert.notEqual(
"complete", documentReadyState,
"content script received the port message when the page was still loading"
);
assert.notEqual(
"uninitialized", documentReadyState,
"content script should be frozen if document.readyState is still uninitialized"
);
assert.ok(
["loading", "interactive"].includes(documentReadyState),
"content script message received with document.readyState was interactive or loading"
);
// Cleanup.
pageMod.destroy();
yield new Promise((resolve) => worker.tab.close(resolve));
yield new Promise((resolve) => server.stop(resolve));
};
require("sdk/test/runner").runTestsFromModule(module);

View file

@ -0,0 +1,6 @@
{
"id": "content-script-messages-latency@jetpack",
"main": "./main.js",
"version": "0.0.1"
}

View file

@ -0,0 +1,19 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const { id } = require('sdk/self');
const { getAddonByID } = require('sdk/addon/manager');
exports.testContributors = function*(assert) {
let addon = yield getAddonByID(id);
let count = 0;
addon.contributors.forEach(({ name }) => {
assert.equal(name, ++count == 1 ? 'A' : 'B', 'The contributors keys are correct');
});
assert.equal(count, 2, 'The key count is correct');
assert.equal(addon.contributors.length, 2, 'The key length is correct');
}
require('sdk/test/runner').runTestsFromModule(module);

View file

@ -0,0 +1,6 @@
{
"id": "test-contributors@jetpack",
"contributors": [ "A", "B" ],
"main": "./main.js",
"version": "0.0.1"
}

View file

@ -0,0 +1,29 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const simple = require('sdk/simple-prefs');
const service = require('sdk/preferences/service');
const { id, preferencesBranch } = require('sdk/self');
const { getAddonByID } = require('sdk/addon/manager');
exports.testCurlyID = function(assert) {
assert.equal(id, '{34a1eae1-c20a-464f-9b0e-000000000000}', 'curly ID is curly');
assert.equal(simple.prefs.test13, 26, 'test13 is 26');
simple.prefs.test14 = '15';
assert.equal(service.get('extensions.{34a1eae1-c20a-464f-9b0e-000000000000}.test14'), '15', 'test14 is 15');
assert.equal(service.get('extensions.{34a1eae1-c20a-464f-9b0e-000000000000}.test14'), simple.prefs.test14, 'simple test14 also 15');
}
// from `/test/test-self.js`, adapted to `sdk/test/assert` API
exports.testSelfID = function*(assert) {
assert.equal(typeof(id), 'string', 'self.id is a string');
assert.ok(id.length > 0, 'self.id not empty');
let addon = yield getAddonByID(id);
assert.ok(addon, 'found addon with self.id');
}
require('sdk/test/runner').runTestsFromModule(module);

View file

@ -0,0 +1,13 @@
{
"id": "{34a1eae1-c20a-464f-9b0e-000000000000}",
"fullName": "curly ID test",
"author": "Tomislav Jovanovic",
"preferences": [{
"name": "test13",
"type": "integer",
"title": "test13",
"value": 26
}],
"main": "./lib/main.js",
"version": "0.0.1"
}

View file

@ -0,0 +1,19 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const { id } = require('sdk/self');
const { getAddonByID } = require('sdk/addon/manager');
exports.testDevelopers = function*(assert) {
let addon = yield getAddonByID(id);
let count = 0;
addon.developers.forEach(({ name }) => {
assert.equal(name, ++count == 1 ? 'A' : 'B', 'The developers keys are correct');
});
assert.equal(count, 2, 'The key count is correct');
assert.equal(addon.developers.length, 2, 'The key length is correct');
}
require('sdk/test/runner').runTestsFromModule(module);

View file

@ -0,0 +1,8 @@
{
"id": "test-developers@jetpack",
"title": "Test developers package key",
"author": "Erik Vold",
"developers": [ "A", "B" ],
"main": "./main.js",
"version": "0.0.1"
}

View file

@ -0,0 +1,5 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
self.postMessage("msg from contentScriptFile");

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/. -->
<html>
<head>
<meta charset="UTF-8">
<title>Page Worker test</title>
</head>
<body>
<p id="paragraph">Lorem ipsum dolor sit amet.</p>
</body>
</html>

View file

@ -0,0 +1,29 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// get title directly
self.postMessage(["equal", document.title, "Page Worker test",
"Correct page title accessed directly"]);
// get <p> directly
var p = document.getElementById("paragraph");
self.postMessage(["ok", !!p, "<p> can be accessed directly"]);
self.postMessage(["equal", p.firstChild.nodeValue,
"Lorem ipsum dolor sit amet.",
"Correct text node expected"]);
// Modify page
var div = document.createElement("div");
div.setAttribute("id", "block");
div.appendChild(document.createTextNode("Test text created"));
document.body.appendChild(div);
// Check back the modification
div = document.getElementById("block");
self.postMessage(["ok", !!div, "<div> can be accessed directly"]);
self.postMessage(["equal", div.firstChild.nodeValue,
"Test text created", "Correct text node expected"]);
self.postMessage(["done"]);

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/. -->
<html>
<head>
<meta charset="UTF-8">
<title>foo</title>
</head>
<body>
<p>bar</p>
</body>
</html>

View file

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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,22 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const { merge } = require('sdk/util/object');
const { version } = require('sdk/system');
const SKIPPING_TESTS = {
"test skip": (assert) => assert.pass("nothing to test here")
};
merge(module.exports, require('./test-content-script'));
merge(module.exports, require('./test-content-worker'));
merge(module.exports, require('./test-page-worker'));
// run e10s tests only on builds from trunk, fx-team, Nightly..
if (!version.endsWith('a1')) {
module.exports = SKIPPING_TESTS;
}
require('sdk/test/runner').runTestsFromModule(module);

View file

@ -0,0 +1,845 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const hiddenFrames = require("sdk/frame/hidden-frame");
const { create: makeFrame } = require("sdk/frame/utils");
const { window } = require("sdk/addon/window");
const { Loader } = require('sdk/test/loader');
const { URL } = require("sdk/url");
const testURI = require("./fixtures").url("test.html");
const testHost = URL(testURI).scheme + '://' + URL(testURI).host;
/*
* Utility function that allow to easily run a proxy test with a clean
* new HTML document. See first unit test for usage.
*/
function createProxyTest(html, callback) {
return function (assert, done) {
let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(html);
let principalLoaded = false;
let element = makeFrame(window.document, {
nodeName: "iframe",
type: "content",
allowJavascript: true,
allowPlugins: true,
allowAuth: true,
uri: testURI
});
element.addEventListener("DOMContentLoaded", onDOMReady, false);
function onDOMReady() {
// Reload frame after getting principal from `testURI`
if (!principalLoaded) {
element.setAttribute("src", url);
principalLoaded = true;
return;
}
assert.equal(element.getAttribute("src"), url, "correct URL loaded");
element.removeEventListener("DOMContentLoaded", onDOMReady,
false);
let xrayWindow = element.contentWindow;
let rawWindow = xrayWindow.wrappedJSObject;
let isDone = false;
let helper = {
xrayWindow: xrayWindow,
rawWindow: rawWindow,
createWorker: function (contentScript) {
return createWorker(assert, xrayWindow, contentScript, helper.done);
},
done: function () {
if (isDone)
return;
isDone = true;
element.parentNode.removeChild(element);
done();
}
};
callback(helper, assert);
}
};
}
function createWorker(assert, xrayWindow, contentScript, done) {
let loader = Loader(module);
let Worker = loader.require("sdk/content/worker").Worker;
let worker = Worker({
window: xrayWindow,
contentScript: [
'let assert, done; new ' + function () {
assert = function assert(v, msg) {
self.port.emit("assert", {assertion:v, msg:msg});
}
done = function done() {
self.port.emit("done");
}
},
contentScript
]
});
worker.port.on("done", done);
worker.port.on("assert", function (data) {
assert.ok(data.assertion, data.msg);
});
return worker;
}
/* Examples for the `createProxyTest` uses */
var html = "<script>var documentGlobal = true</script>";
exports["test Create Proxy Test"] = createProxyTest(html, function (helper, assert) {
// You can get access to regular `test` object in second argument of
// `createProxyTest` method:
assert.ok(helper.rawWindow.documentGlobal,
"You have access to a raw window reference via `helper.rawWindow`");
assert.ok(!("documentGlobal" in helper.xrayWindow),
"You have access to an XrayWrapper reference via `helper.xrayWindow`");
// If you do not create a Worker, you have to call helper.done(),
// in order to say when your test is finished
helper.done();
});
exports["test Create Proxy Test With Worker"] = createProxyTest("", function (helper) {
helper.createWorker(
"new " + function WorkerScope() {
assert(true, "You can do assertions in your content script");
// And if you create a worker, you either have to call `done`
// from content script or helper.done()
done();
}
);
});
exports["test Create Proxy Test With Events"] = createProxyTest("", function (helper, assert) {
let worker = helper.createWorker(
"new " + function WorkerScope() {
self.port.emit("foo");
}
);
worker.port.on("foo", function () {
assert.pass("You can use events");
// And terminate your test with helper.done:
helper.done();
});
});
/* Disabled due to bug 1038432
// Bug 714778: There was some issue around `toString` functions
// that ended up being shared between content scripts
exports["test Shared To String Proxies"] = createProxyTest("", function(helper) {
let worker = helper.createWorker(
'new ' + function ContentScriptScope() {
// We ensure that `toString` can't be modified so that nothing could
// leak to/from the document and between content scripts
// It only applies to JS proxies, there isn't any such issue with xrays.
//document.location.toString = function foo() {};
document.location.toString.foo = "bar";
assert("foo" in document.location.toString, "document.location.toString can be modified");
assert(document.location.toString() == "data:text/html;charset=utf-8,",
"First document.location.toString()");
self.postMessage("next");
}
);
worker.on("message", function () {
helper.createWorker(
'new ' + function ContentScriptScope2() {
assert(!("foo" in document.location.toString),
"document.location.toString is different for each content script");
assert(document.location.toString() == "data:text/html;charset=utf-8,",
"Second document.location.toString()");
done();
}
);
});
});
*/
// Ensure that postMessage is working correctly across documents with an iframe
var html = '<iframe id="iframe" name="test" src="data:text/html;charset=utf-8," />';
exports["test postMessage"] = createProxyTest(html, function (helper, assert) {
let ifWindow = helper.xrayWindow.document.getElementById("iframe").contentWindow;
// Listen without proxies, to check that it will work in regular case
// simulate listening from a web document.
ifWindow.addEventListener("message", function listener(event) {
ifWindow.removeEventListener("message", listener, false);
// As we are in system principal, event is an XrayWrapper
// xrays use current compartments when calling postMessage method.
// Whereas js proxies was using postMessage method compartment,
// not the caller one.
assert.strictEqual(event.source, helper.xrayWindow,
"event.source is the top window");
assert.equal(event.origin, testHost, "origin matches testHost");
assert.equal(event.data, "{\"foo\":\"bar\\n \\\"escaped\\\".\"}",
"message data is correct");
helper.done();
}, false);
helper.createWorker(
'new ' + function ContentScriptScope() {
var json = JSON.stringify({foo : "bar\n \"escaped\"."});
document.getElementById("iframe").contentWindow.postMessage(json, "*");
}
);
});
var html = '<input id="input2" type="checkbox" />';
exports["test Object Listener"] = createProxyTest(html, function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
// Test objects being given as event listener
let input = document.getElementById("input2");
let myClickListener = {
called: false,
handleEvent: function(event) {
assert(this === myClickListener, "`this` is the original object");
assert(!this.called, "called only once");
this.called = true;
assert(event.target, input, "event.target is the wrapped window");
done();
}
};
window.addEventListener("click", myClickListener, true);
input.click();
window.removeEventListener("click", myClickListener, true);
}
);
});
exports["test Object Listener 2"] = createProxyTest("", function (helper) {
helper.createWorker(
('new ' + function ContentScriptScope() {
// variable replaced with `testHost`
let testHost = "TOKEN";
// Verify object as DOM event listener
let myMessageListener = {
called: false,
handleEvent: function(event) {
window.removeEventListener("message", myMessageListener, true);
assert(this == myMessageListener, "`this` is the original object");
assert(!this.called, "called only once");
this.called = true;
assert(event.target == document.defaultView, "event.target is the wrapped window");
assert(event.source == document.defaultView, "event.source is the wrapped window");
assert(event.origin == testHost, "origin matches testHost");
assert(event.data == "ok", "message data is correct");
done();
}
};
window.addEventListener("message", myMessageListener, true);
document.defaultView.postMessage("ok", '*');
}
).replace("TOKEN", testHost));
});
var html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' +
'<input id="input2" type="checkbox" />';
exports.testStringOverload = createProxyTest(html, function (helper, assert) {
helper.createWorker(
'new ' + function ContentScriptScope() {
// RightJS is hacking around String.prototype, and do similar thing:
// Pass `this` from a String prototype method.
// It is funny because typeof this == "object"!
// So that when we pass `this` to a native method,
// our proxy code can fail on another even more crazy thing.
// See following test to see what fails around proxies.
String.prototype.update = function () {
assert(typeof this == "object", "in update, `this` is an object");
assert(this.toString() == "input", "in update, `this.toString works");
return document.querySelectorAll(this);
};
assert("input".update().length == 3, "String.prototype overload works");
done();
}
);
});
exports["test Element.matches()"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
// Check matches XrayWrappers bug (Bug 658909):
// Test that Element.matches() does not return bad results when we are
// not calling it from the node itself.
assert(document.createElement( "div" ).matches("div"),
"matches works while being called from the node");
assert(document.documentElement.matches.call(
document.createElement( "div" ),
"div"
),
"matches works while being called from a " +
"function reference to " +
"document.documentElement.matches.call");
done();
}
);
});
exports["test Events Overload"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
// If we add a "____proxy" attribute on XrayWrappers in order to store
// the related proxy to create an unique proxy for each wrapper;
// we end up setting this attribute to prototype objects :x
// And so, instances created with such prototype will be considered
// as equal to the prototype ...
// // Internal method that return the proxy for a given XrayWrapper
// function proxify(obj) {
// if (obj._proxy) return obj._proxy;
// return obj._proxy = Proxy.create(...);
// }
//
// // Get a proxy of an XrayWrapper prototype object
// let proto = proxify(xpcProto);
//
// // Use this proxy as a prototype
// function Constr() {}
// Constr.proto = proto;
//
// // Try to create an instance using this prototype
// let xpcInstance = new Constr();
// let wrapper = proxify(xpcInstance)
//
// xpcProto._proxy = proto and as xpcInstance.__proto__ = xpcProto,
// xpcInstance._proxy = proto ... and profixy(xpcInstance) = proto :(
//
let proto = window.document.createEvent('HTMLEvents').__proto__;
window.Event.prototype = proto;
let event = document.createEvent('HTMLEvents');
assert(event !== proto, "Event should not be equal to its prototype");
event.initEvent('dataavailable', true, true);
assert(event.type === 'dataavailable', "Events are working fine");
done();
}
);
});
exports["test Nested Attributes"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
// XrayWrappers has a bug when you set an attribute on it,
// in some cases, it creates an unnecessary wrapper that introduces
// a different object that refers to the same original object
// Check that our wrappers don't reproduce this bug
// SEE BUG 658560: Fix identity problem with CrossOriginWrappers
let o = {sandboxObject:true};
window.nested = o;
o.foo = true;
assert(o === window.nested, "Nested attribute to sandbox object should not be proxified");
window.nested = document;
assert(window.nested === document, "Nested attribute to proxy should not be double proxified");
done();
}
);
});
exports["test Form nodeName"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
let body = document.body;
// Check form[nodeName]
let form = document.createElement("form");
let input = document.createElement("input");
input.setAttribute("name", "test");
form.appendChild(input);
body.appendChild(form);
assert(form.test == input, "form[nodeName] is valid");
body.removeChild(form);
done();
}
);
});
exports["test localStorage"] = createProxyTest("", function (helper, assert) {
let worker = helper.createWorker(
'new ' + function ContentScriptScope() {
// Check localStorage:
assert(window.localStorage, "has access to localStorage");
window.localStorage.name = 1;
assert(window.localStorage.name == 1, "localStorage appears to work");
self.port.on("step2", function () {
window.localStorage.clear();
assert(window.localStorage.name == undefined, "localStorage really, really works");
done();
});
self.port.emit("step1");
}
);
worker.port.on("step1", function () {
assert.equal(helper.rawWindow.localStorage.name, 1, "localStorage really works");
worker.port.emit("step2");
});
});
exports["test Auto Unwrap Custom Attributes"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
let body = document.body;
// Setting a custom object to a proxy attribute is not wrapped when we get it afterward
let object = {custom: true, enumerable: false};
body.customAttribute = object;
assert(object === body.customAttribute, "custom JS attributes are not wrapped");
done();
}
);
});
exports["test Object Tag"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
// <object>, <embed> and other tags return typeof 'function'
let flash = document.createElement("object");
assert(typeof flash == "function", "<object> is typeof 'function'");
assert(flash.toString().match(/\[object HTMLObjectElement.*\]/), "<object> is HTMLObjectElement");
assert("setAttribute" in flash, "<object> has a setAttribute method");
done();
}
);
});
exports["test Highlight toString Behavior"] = createProxyTest("", function (helper, assert) {
// We do not have any workaround this particular use of toString
// applied on <object> elements. So disable this test until we found one!
//assert.equal(helper.rawWindow.Object.prototype.toString.call(flash), "[object HTMLObjectElement]", "<object> is HTMLObjectElement");
function f() {};
let funToString = Object.prototype.toString.call(f);
assert.ok(/\[object Function.*\]/.test(funToString), "functions are functions 1");
// This is how jquery call toString:
let strToString = helper.rawWindow.Object.prototype.toString.call("");
assert.ok(/\[object String.*\]/.test(strToString), "strings are strings");
let o = {__exposedProps__:{}};
let objToString = helper.rawWindow.Object.prototype.toString.call(o);
assert.ok(/\[object Object.*\]/.test(objToString), "objects are objects");
// Make sure to pass a function from the same compartments
// or toString will return [object Object] on FF8+
let f2 = helper.rawWindow.eval("(function () {})");
let funToString2 = helper.rawWindow.Object.prototype.toString.call(f2);
assert.ok(/\[object Function.*\]/.test(funToString2), "functions are functions 2");
helper.done();
});
exports["test Document TagName"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
let body = document.body;
// Check document[tagName]
let div = document.createElement("div");
div.setAttribute("name", "test");
body.appendChild(div);
assert(!document.test, "document[divName] is undefined");
body.removeChild(div);
let form = document.createElement("form");
form.setAttribute("name", "test");
body.appendChild(form);
assert(document.test == form, "document[formName] is valid");
body.removeChild(form);
let img = document.createElement("img");
img.setAttribute("name", "test");
body.appendChild(img);
assert(document.test == img, "document[imgName] is valid");
body.removeChild(img);
done();
}
);
});
var html = '<iframe id="iframe" name="test" src="data:text/html;charset=utf-8," />';
exports["test Window Frames"] = createProxyTest(html, function (helper) {
helper.createWorker(
'let glob = this; new ' + function ContentScriptScope() {
// Check window[frameName] and window.frames[i]
let iframe = document.getElementById("iframe");
//assert(window.frames.length == 1, "The iframe is reported in window.frames check1");
//assert(window.frames[0] == iframe.contentWindow, "The iframe is reported in window.frames check2");
assert(window.test == iframe.contentWindow, "window[frameName] is valid");
done();
}
);
});
exports["test Collections"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
// Highlight XPCNativeWrapper bug with HTMLCollection
// tds[0] is only defined on first access :o
let body = document.body;
let div = document.createElement("div");
body.appendChild(div);
div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
let tds = div.getElementsByTagName("td");
assert(tds[0] == tds[0], "We can get array element multiple times");
body.removeChild(div);
done();
}
);
});
var html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' +
'<input id="input2" type="checkbox" />';
exports["test Collections 2"] = createProxyTest(html, function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
// Verify that NodeList/HTMLCollection are working fine
let body = document.body;
let inputs = body.getElementsByTagName("input");
assert(body.childNodes.length == 3, "body.childNodes length is correct");
assert(inputs.length == 3, "inputs.length is correct");
assert(body.childNodes[0] == inputs[0], "body.childNodes[0] is correct");
assert(body.childNodes[1] == inputs[1], "body.childNodes[1] is correct");
assert(body.childNodes[2] == inputs[2], "body.childNodes[2] is correct");
let count = 0;
for(let i in body.childNodes) {
count++;
}
assert(count >= 3, "body.childNodes is iterable");
done();
}
);
});
exports["test XMLHttpRequest"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
// XMLHttpRequest doesn't support XMLHttpRequest.apply,
// that may break our proxy code
assert(new window.XMLHttpRequest(), "we are able to instantiate XMLHttpRequest object");
done();
}
);
});
exports["test XPathResult"] = createProxyTest("", function (helper, assert) {
const XPathResultTypes = ["ANY_TYPE",
"NUMBER_TYPE", "STRING_TYPE", "BOOLEAN_TYPE",
"UNORDERED_NODE_ITERATOR_TYPE",
"ORDERED_NODE_ITERATOR_TYPE",
"UNORDERED_NODE_SNAPSHOT_TYPE",
"ORDERED_NODE_SNAPSHOT_TYPE",
"ANY_UNORDERED_NODE_TYPE",
"FIRST_ORDERED_NODE_TYPE"];
// Check XPathResult bug with constants being undefined on XPCNativeWrapper
let xpcXPathResult = helper.xrayWindow.XPathResult;
XPathResultTypes.forEach(function(type, i) {
assert.equal(xpcXPathResult.wrappedJSObject[type],
helper.rawWindow.XPathResult[type],
"XPathResult's constants are valid on unwrapped node");
assert.equal(xpcXPathResult[type], i,
"XPathResult's constants are defined on " +
"XPCNativeWrapper (platform bug #)");
});
let value = helper.rawWindow.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE;
let worker = helper.createWorker(
'new ' + function ContentScriptScope() {
self.port.on("value", function (value) {
// Check that our work around is working:
assert(window.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE === value,
"XPathResult works correctly on Proxies");
done();
});
}
);
worker.port.emit("value", value);
});
exports["test Prototype Inheritance"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
// Verify that inherited prototype function like initEvent
// are handled correctly. (e2.type will return an error if it's not the case)
let event1 = document.createEvent( 'MouseEvents' );
event1.initEvent( "click", true, true );
let event2 = document.createEvent( 'MouseEvents' );
event2.initEvent( "click", true, true );
assert(event2.type == "click", "We are able to create an event");
done();
}
);
});
exports["test Functions"] = createProxyTest("", function (helper) {
helper.rawWindow.callFunction = function callFunction(f) {
return f();
};
helper.rawWindow.isEqual = function isEqual(a, b) {
return a == b;
};
// bug 784116: workaround in order to allow proxy code to cache proxies on
// these functions:
helper.rawWindow.callFunction.__exposedProps__ = {__proxy: 'rw'};
helper.rawWindow.isEqual.__exposedProps__ = {__proxy: 'rw'};
helper.createWorker(
'new ' + function ContentScriptScope() {
// Check basic usage of functions
let closure2 = function () {return "ok";};
assert(window.wrappedJSObject.callFunction(closure2) == "ok", "Function references work");
// Ensure that functions are cached when being wrapped to native code
let closure = function () {};
assert(window.wrappedJSObject.isEqual(closure, closure), "Function references are cached before being wrapped to native");
done();
}
);
});
var html = '<input id="input2" type="checkbox" />';
exports["test Listeners"] = createProxyTest(html, function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
// Verify listeners:
let input = document.getElementById("input2");
assert(input, "proxy.getElementById works");
function onclick() {};
input.onclick = onclick;
assert(input.onclick === onclick, "on* attributes are equal to original function set");
let addEventListenerCalled = false;
let expandoCalled = false;
input.addEventListener("click", function onclick(event) {
input.removeEventListener("click", onclick, true);
assert(!addEventListenerCalled, "closure given to addEventListener is called once");
if (addEventListenerCalled)
return;
addEventListenerCalled = true;
assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals");
let input2 = document.getElementById("input2");
input.onclick = function (event) {
input.onclick = null;
assert(!expandoCalled, "closure set to expando is called once");
if (expandoCalled) return;
expandoCalled = true;
assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals");
setTimeout(function () {
input.click();
done();
}, 0);
}
setTimeout(function () {
input.click();
}, 0);
}, true);
input.click();
}
);
});
exports["test requestAnimationFrame"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
var self = (function() { return this; })();
window.requestAnimationFrame(function callback() {
assert(self == this, "self is equal to `this`");
done();
});
}
);
});
exports["testGlobalScope"] = createProxyTest("", function (helper) {
helper.createWorker(
'let toplevelScope = true;' +
'assert(window.toplevelScope, "variables in toplevel scope are set to `window` object");' +
'assert(this.toplevelScope, "variables in toplevel scope are set to `this` object");' +
'done();'
);
});
// Bug 715755: proxy code throw an exception on COW
// Create an http server in order to simulate real cross domain documents
exports["test Cross Domain Iframe"] = createProxyTest("", function (helper) {
let serverPort = 8099;
let server = require("./httpd").startServerAsync(serverPort);
server.registerPathHandler("/", function handle(request, response) {
// Returns the webpage that receive a message and forward it back to its
// parent document by appending ' world'.
let content = "<html><head><meta charset='utf-8'></head>\n";
content += "<script>\n";
content += " window.addEventListener('message', function (event) {\n";
content += " parent.postMessage(event.data + ' world', '*');\n";
content += " }, true);\n";
content += "</script>\n";
content += "<body></body>\n";
content += "</html>\n";
response.write(content);
});
let worker = helper.createWorker(
'new ' + function ContentScriptScope() {
// Waits for the server page url
self.on("message", function (url) {
// Creates an iframe with this page
let iframe = document.createElement("iframe");
iframe.addEventListener("load", function onload() {
iframe.removeEventListener("load", onload, true);
try {
// Try to communicate with iframe's content
window.addEventListener("message", function onmessage(event) {
window.removeEventListener("message", onmessage, true);
assert(event.data == "hello world", "COW works properly");
self.port.emit("end");
}, true);
iframe.contentWindow.postMessage("hello", "*");
} catch(e) {
assert(false, "COW fails : "+e.message);
}
}, true);
iframe.setAttribute("src", url);
document.body.appendChild(iframe);
});
}
);
worker.port.on("end", function () {
server.stop(helper.done);
});
worker.postMessage("http://localhost:" + serverPort + "/");
});
// Bug 769006: Ensure that MutationObserver works fine with proxies
var html = '<a href="foo">link</a>';
exports["test MutationObvserver"] = createProxyTest(html, function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
if (typeof MutationObserver == "undefined") {
assert(true, "No MutationObserver for this FF version");
done();
return;
}
let link = document.getElementsByTagName("a")[0];
// Register a Mutation observer
let obs = new MutationObserver(function(mutations){
// Ensure that mutation data are valid
assert(mutations.length == 1, "only one attribute mutation");
let mutation = mutations[0];
assert(mutation.type == "attributes", "check `type`");
assert(mutation.target == link, "check `target`");
assert(mutation.attributeName == "href", "check `attributeName`");
assert(mutation.oldValue == "foo", "check `oldValue`");
obs.disconnect();
done();
});
obs.observe(document, {
subtree: true,
attributes: true,
attributeOldValue: true,
attributeFilter: ["href"]
});
// Modify the DOM
link.setAttribute("href", "bar");
}
);
});
var html = '<script>' +
'var accessCheck = function() {' +
' assert(true, "exporting function works");' +
' try{' +
' exportedObj.prop;' +
' assert(false, "content should not have access to content-script");' +
' } catch(e) {' +
' assert(e.toString().indexOf("Permission denied") != -1,' +
' "content should not have access to content-script");' +
' }' +
'}</script>';
exports["test nsEp for content-script"] = createProxyTest(html, function (helper) {
helper.createWorker(
'let glob = this; new ' + function ContentScriptScope() {
exportFunction(assert, unsafeWindow, { defineAs: "assert" });
window.wrappedJSObject.assert(true, "assert exported");
window.wrappedJSObject.exportedObj = { prop: 42 };
window.wrappedJSObject.accessCheck();
done();
}
);
});
// require("sdk/test").run(exports);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,524 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Loader } = require('sdk/test/loader');
const { Page } = require("sdk/page-worker");
const { URL } = require("sdk/url");
const fixtures = require("./fixtures");
const testURI = fixtures.url("test.html");
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 Isolate = fn => "(" + fn + ")()";
exports.testSimplePageCreation = function(assert, done) {
let page = new Page({
contentScript: "self.postMessage(window.location.href)",
contentScriptWhen: "end",
onMessage: function (message) {
assert.equal(message, "about:blank",
"Page Worker should start with a blank page by default");
assert.equal(this, page, "The 'this' object is the page itself.");
done();
}
});
}
/*
* Tests that we can't be tricked by document overloads as we have access
* to wrapped nodes
*/
exports.testWrappedDOM = function(assert, done) {
let page = Page({
allow: { script: true },
contentURL: "data:text/html;charset=utf-8,<script>document.getElementById=3;window.scrollTo=3;</script>",
contentScript: 'new ' + function() {
function send() {
self.postMessage([typeof(document.getElementById), typeof(window.scrollTo)]);
}
if (document.readyState !== 'complete')
window.addEventListener('load', send, true)
else
send();
},
onMessage: function (message) {
assert.equal(message[0],
"function",
"getElementById from content script is the native one");
assert.equal(message[1],
"function",
"scrollTo from content script is the native one");
done();
}
});
}
/*
// We do not offer unwrapped access to DOM since bug 601295 landed
// See 660780 to track progress of unwrap feature
exports.testUnwrappedDOM = function(assert, done) {
let page = Page({
allow: { script: true },
contentURL: "data:text/html;charset=utf-8,<script>document.getElementById=3;window.scrollTo=3;</script>",
contentScript: "window.addEventListener('load', function () { " +
"return self.postMessage([typeof(unsafeWindow.document.getElementById), " +
"typeof(unsafeWindow.scrollTo)]); }, true)",
onMessage: function (message) {
assert.equal(message[0],
"number",
"document inside page is free to be changed");
assert.equal(message[1],
"number",
"window inside page is free to be changed");
done();
}
});
}
*/
exports.testPageProperties = function(assert) {
let page = new Page();
for (let prop of ['contentURL', 'allow', 'contentScriptFile',
'contentScript', 'contentScriptWhen', 'on',
'postMessage', 'removeListener']) {
assert.ok(prop in page, prop + " property is defined on page.");
}
assert.ok(() => page.postMessage("foo") || true,
"postMessage doesn't throw exception on page.");
}
exports.testConstructorAndDestructor = function(assert, done) {
let loader = Loader(module);
let { Page } = loader.require("sdk/page-worker");
let global = loader.sandbox("sdk/page-worker");
let pagesReady = 0;
let page1 = Page({
contentScript: "self.postMessage('')",
contentScriptWhen: "end",
onMessage: pageReady
});
let page2 = Page({
contentScript: "self.postMessage('')",
contentScriptWhen: "end",
onMessage: pageReady
});
assert.notEqual(page1, page2,
"Page 1 and page 2 should be different objects.");
function pageReady() {
if (++pagesReady == 2) {
page1.destroy();
page2.destroy();
assert.ok(isDestroyed(page1), "page1 correctly unloaded.");
assert.ok(isDestroyed(page2), "page2 correctly unloaded.");
loader.unload();
done();
}
}
}
exports.testAutoDestructor = function(assert, done) {
let loader = Loader(module);
let { Page } = loader.require("sdk/page-worker");
let page = Page({
contentScript: "self.postMessage('')",
contentScriptWhen: "end",
onMessage: function() {
loader.unload();
assert.ok(isDestroyed(page), "Page correctly unloaded.");
done();
}
});
}
exports.testValidateOptions = function(assert) {
assert.throws(
() => Page({ contentURL: 'home' }),
/The `contentURL` option must be a valid URL\./,
"Validation correctly denied a non-URL contentURL"
);
assert.throws(
() => Page({ onMessage: "This is not a function."}),
/The option "onMessage" must be one of the following types: function/,
"Validation correctly denied a non-function onMessage."
);
assert.pass("Options validation is working.");
}
exports.testContentAndAllowGettersAndSetters = function(assert, done) {
let content = "data:text/html;charset=utf-8,<script>window.localStorage.allowScript=3;</script>";
// Load up the page with testURI initially for the resource:// principal,
// then load the actual data:* content, as data:* URIs no longer
// have localStorage
let page = Page({
contentURL: testURI,
contentScript: "if (window.location.href==='"+testURI+"')" +
" self.postMessage('reload');" +
"else " +
" self.postMessage(window.localStorage.allowScript)",
contentScriptWhen: "end",
onMessage: step0
});
function step0(message) {
if (message === 'reload')
return page.contentURL = content;
assert.equal(message, "3",
"Correct value expected for allowScript - 3");
assert.equal(page.contentURL, content,
"Correct content expected");
page.removeListener('message', step0);
page.on('message', step1);
page.allow = { script: false };
page.contentURL = content =
"data:text/html;charset=utf-8,<script>window.localStorage.allowScript='f'</script>";
}
function step1(message) {
assert.equal(message, "3",
"Correct value expected for allowScript - 3");
assert.equal(page.contentURL, content, "Correct content expected");
page.removeListener('message', step1);
page.on('message', step2);
page.allow = { script: true };
page.contentURL = content =
"data:text/html;charset=utf-8,<script>window.localStorage.allowScript='g'</script>";
}
function step2(message) {
assert.equal(message, "g",
"Correct value expected for allowScript - g");
assert.equal(page.contentURL, content, "Correct content expected");
page.removeListener('message', step2);
page.on('message', step3);
page.allow.script = false;
page.contentURL = content =
"data:text/html;charset=utf-8,<script>window.localStorage.allowScript=3</script>";
}
function step3(message) {
assert.equal(message, "g",
"Correct value expected for allowScript - g");
assert.equal(page.contentURL, content, "Correct content expected");
page.removeListener('message', step3);
page.on('message', step4);
page.allow.script = true;
page.contentURL = content =
"data:text/html;charset=utf-8,<script>window.localStorage.allowScript=4</script>";
}
function step4(message) {
assert.equal(message, "4",
"Correct value expected for allowScript - 4");
assert.equal(page.contentURL, content, "Correct content expected");
done();
}
}
exports.testOnMessageCallback = function(assert, done) {
Page({
contentScript: "self.postMessage('')",
contentScriptWhen: "end",
onMessage: function() {
assert.pass("onMessage callback called");
done();
}
});
}
exports.testMultipleOnMessageCallbacks = function(assert, done) {
let count = 0;
let page = Page({
contentScript: "self.postMessage('')",
contentScriptWhen: "end",
onMessage: () => count += 1
});
page.on('message', () => count += 2);
page.on('message', () => count *= 3);
page.on('message', () =>
assert.equal(count, 9, "All callbacks were called, in order."));
page.on('message', done);
};
exports.testLoadContentPage = function(assert, done) {
let page = Page({
onMessage: function(message) {
// The message is an array whose first item is the test method to call
// and the rest of whose items are arguments to pass it.
let msg = message.shift();
if (msg == "done")
return done();
assert[msg].apply(assert, message);
},
contentURL: fixtures.url("test-page-worker.html"),
contentScriptFile: fixtures.url("test-page-worker.js"),
contentScriptWhen: "ready"
});
}
exports.testLoadContentPageRelativePath = function(assert, done) {
let page = require("sdk/page-worker").Page({
onMessage: function(message) {
// The message is an array whose first item is the test method to call
// and the rest of whose items are arguments to pass it.
let msg = message.shift();
if (msg == "done")
return done();
assert[msg].apply(assert, message);
},
contentURL: "./test-page-worker.html",
contentScriptFile: "./test-page-worker.js",
contentScriptWhen: "ready"
});
}
exports.testAllowScriptDefault = function(assert, done) {
let page = Page({
onMessage: function(message) {
assert.ok(message, "Script is allowed to run by default.");
done();
},
contentURL: "data:text/html;charset=utf-8,<script>document.documentElement.setAttribute('foo', 3);</script>",
contentScript: "self.postMessage(document.documentElement.getAttribute('foo'))",
contentScriptWhen: "ready"
});
}
exports.testAllowScript = function(assert, done) {
let page = Page({
onMessage: function(message) {
assert.ok(message, "Script runs when allowed to do so.");
done();
},
allow: { script: true },
contentURL: "data:text/html;charset=utf-8,<script>document.documentElement.setAttribute('foo', 3);</script>",
contentScript: "self.postMessage(document.documentElement.hasAttribute('foo') && " +
" document.documentElement.getAttribute('foo') == 3)",
contentScriptWhen: "ready"
});
}
exports.testPingPong = function(assert, done) {
let page = Page({
contentURL: 'data:text/html;charset=utf-8,ping-pong',
contentScript: 'self.on("message", function(message) { return self.postMessage("pong"); });'
+ 'self.postMessage("ready");',
onMessage: function(message) {
if ('ready' == message) {
page.postMessage('ping');
}
else {
assert.ok(message, 'pong', 'Callback from contentScript');
done();
}
}
});
};
exports.testRedirect = function (assert, done) {
let page = Page({
contentURL: 'data:text/html;charset=utf-8,first-page',
contentScriptWhen: "end",
contentScript: '' +
'if (/first-page/.test(document.location.href)) ' +
' document.location.href = "data:text/html;charset=utf-8,redirect";' +
'else ' +
' self.port.emit("redirect", document.location.href);'
});
page.port.on('redirect', function (url) {
assert.equal(url, 'data:text/html;charset=utf-8,redirect', 'Reinjects contentScript on reload');
done();
});
};
exports.testRedirectIncludeArrays = function (assert, done) {
let firstURL = 'data:text/html;charset=utf-8,first-page';
let page = Page({
contentURL: firstURL,
contentScript: '(function () {' +
'self.port.emit("load", document.location.href);' +
' self.port.on("redirect", function (url) {' +
' document.location.href = url;' +
' })' +
'})();',
include: ['about:blank', 'data:*']
});
page.port.on('load', function (url) {
if (url === firstURL) {
page.port.emit('redirect', 'about:blank');
} else if (url === 'about:blank') {
page.port.emit('redirect', 'about:mozilla');
assert.ok('`include` property handles arrays');
assert.equal(url, 'about:blank', 'Redirects work with accepted domains');
done();
} else if (url === 'about:mozilla') {
assert.fail('Should not redirect to restricted domain');
}
});
};
exports.testRedirectFromWorker = function (assert, done) {
let firstURL = 'data:text/html;charset=utf-8,first-page';
let secondURL = 'data:text/html;charset=utf-8,second-page';
let thirdURL = 'data:text/html;charset=utf-8,third-page';
let page = Page({
contentURL: firstURL,
contentScript: '(function () {' +
'self.port.emit("load", document.location.href);' +
' self.port.on("redirect", function (url) {' +
' document.location.href = url;' +
' })' +
'})();',
include: 'data:*'
});
page.port.on('load', function (url) {
if (url === firstURL) {
page.port.emit('redirect', secondURL);
} else if (url === secondURL) {
page.port.emit('redirect', thirdURL);
} else if (url === thirdURL) {
page.port.emit('redirect', 'about:mozilla');
assert.equal(url, thirdURL, 'Redirects work with accepted domains on include strings');
done();
} else {
assert.fail('Should not redirect to unauthorized domains');
}
});
};
exports.testRedirectWithContentURL = function (assert, done) {
let firstURL = 'data:text/html;charset=utf-8,first-page';
let secondURL = 'data:text/html;charset=utf-8,second-page';
let thirdURL = 'data:text/html;charset=utf-8,third-page';
let page = Page({
contentURL: firstURL,
contentScript: '(function () {' +
'self.port.emit("load", document.location.href);' +
'})();',
include: 'data:*'
});
page.port.on('load', function (url) {
if (url === firstURL) {
page.contentURL = secondURL;
} else if (url === secondURL) {
page.contentURL = thirdURL;
} else if (url === thirdURL) {
page.contentURL = 'about:mozilla';
assert.equal(url, thirdURL, 'Redirects work with accepted domains on include strings');
done();
} else {
assert.fail('Should not redirect to unauthorized domains');
}
});
};
exports.testMultipleDestroys = function(assert) {
let page = Page();
page.destroy();
page.destroy();
assert.pass("Multiple destroys should not cause an error");
};
exports.testContentScriptOptionsOption = function(assert, done) {
let page = new Page({
contentScript: "self.postMessage( [typeof self.options.d, self.options] );",
contentScriptWhen: "end",
contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}},
onMessage: function(msg) {
assert.equal(msg[0], 'undefined', 'functions are stripped from contentScriptOptions');
assert.equal(typeof msg[1], 'object', 'object as contentScriptOptions');
assert.equal(msg[1].a, true, 'boolean in contentScriptOptions');
assert.equal(msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions');
assert.equal(msg[1].c, 'string', 'string in contentScriptOptions');
done();
}
});
};
exports.testMessageQueue = function (assert, done) {
let page = new Page({
contentScript: 'self.on("message", function (m) {' +
'self.postMessage(m);' +
'});',
contentURL: 'data:text/html;charset=utf-8,',
});
page.postMessage('ping');
page.on('message', function (m) {
assert.equal(m, 'ping', 'postMessage should queue messages');
done();
});
};
exports.testWindowStopDontBreak = function (assert, done) {
const { Ci, Cc } = require('chrome');
const consoleService = Cc['@mozilla.org/consoleservice;1'].
getService(Ci.nsIConsoleService);
const listener = {
observe: ({message}) => {
if (message.includes('contentWorker is null'))
assert.fail('contentWorker is null');
}
};
consoleService.registerListener(listener)
let page = new Page({
contentURL: 'data:text/html;charset=utf-8,testWindowStopDontBreak',
contentScriptWhen: 'ready',
contentScript: Isolate(() => {
window.stop();
self.port.on('ping', () => self.port.emit('pong'));
})
});
page.port.on('pong', () => {
assert.pass('page-worker works after window.stop');
page.destroy();
consoleService.unregisterListener(listener);
done();
});
page.port.emit("ping");
};
function isDestroyed(page) {
try {
page.postMessage("foo");
}
catch (err) {
if (err.message == ERR_DESTROYED) {
return true;
}
else {
throw err;
}
}
return false;
}
// require("sdk/test").run(exports);

View file

@ -0,0 +1,11 @@
{
"name": "e10s-content",
"title": "e10s-content",
"id": "jid1-ZZaXFHAPlHDDD@jetpack",
"description": "run content worker tests in e10s mode",
"author": "Tomislav Jovanovic",
"license": "MPL-2.0",
"version": "0.1.0",
"main": "./lib/main.js",
"e10s": true
}

View file

@ -0,0 +1,29 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html>
<head>
<meta charset="UTF-8">
<title>HTML Localization</title>
</head>
<body>
<div data-l10n-id="Not translated">Kept as-is</div>
<ul data-l10n-id="Translated">
<li>Inner html content is replaced,</li>
<li data-l10n-id="text-content">
Elements with data-l10n-id attribute whose parent element is translated
will be replaced by the content of the translation.
</li>
</ul>
<div data-l10n-id="text-content">No</div>
<div data-l10n-id="Translated">
A data-l10n-id value can be used in multiple elements
</div>
<a data-l10n-id="link-attributes" title="Certain whitelisted attributes get translated too" alt="No" accesskey="A"></a>
<input data-l10n-id="input" type="text" placeholder="Form placeholders are translateable">
<menu>
<menuitem data-l10n-id="contextitem" label="Labels of select options and context menus are translateable">
</menu>
</body>
</html

View file

@ -0,0 +1,38 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Translated= Yes
text-content=no <b>HTML</b> injection
downloadsCount=%d downloads
downloadsCount[one]=one download
pluralTest=fallback to other
pluralTest[zero]=optional zero form
explicitPlural[one]=one
explicitPlural[other]=other
# You can use unicode char escaping in order to inject space at the beginning/
# end of your string. (Regular spaces are automatically ignore by .properties
# file parser)
unicodeEscape = \u0020\u0040\u0020
# this string equals to " @ "
# bug 1033309 plurals with multiple placeholders
first_identifier[one]=first entry is %s and the second one is %s.
first_identifier=the entries are %s and %s.
second_identifier[other]=first entry is %s and the second one is %s.
third_identifier=first entry is %s and the second one is %s.
# bug 824489 allow translation of element attributes
link-attributes.title=Yes
link-attributes.alt=Yes
link-attributes.accesskey=B
input.placeholder=Yes
contextitem.label=Yes
link-attributes.ariaLabel=Yes
link-attributes.ariaValueText=Value
link-attributes.ariaMozHint=Hint

View file

@ -0,0 +1,5 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Translated= jes

View file

@ -0,0 +1,14 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Translated= Oui
placeholderString= Placeholder %s
# Plural forms
%d downloads=%d téléchargements
%d downloads[one]=%d téléchargement
downloadsCount=%d téléchargements
downloadsCount[one]=%d téléchargement

View file

@ -0,0 +1,289 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const prefs = require("sdk/preferences/service");
const { Loader } = require('sdk/test/loader');
const { resolveURI } = require('toolkit/loader');
const { rootURI, isNative } = require("@loader/options");
const { usingJSON } = require('sdk/l10n/json/core');
const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
const PREF_SELECTED_LOCALE = "general.useragent.locale";
function setLocale(locale) {
prefs.set(PREF_MATCH_OS_LOCALE, false);
prefs.set(PREF_SELECTED_LOCALE, locale);
}
function resetLocale() {
prefs.reset(PREF_MATCH_OS_LOCALE);
prefs.reset(PREF_SELECTED_LOCALE);
}
function definePseudo(loader, id, exports) {
let uri = resolveURI(id, loader.mapping);
loader.modules[uri] = { exports: exports };
}
function createTest(locale, testFunction) {
return function (assert, done) {
let loader = Loader(module);
// Change the locale before loading new l10n modules in order to load
// the right .json file
setLocale(locale);
// Initialize main l10n module in order to load new locale files
loader.require("sdk/l10n/loader").
load(rootURI).
then(null, function failure(error) {
if (!isNative)
assert.fail("Unable to load locales: " + error);
}).
then(function success(data) {
definePseudo(loader, '@l10n/data', data ? data : null);
// Execute the given test function
try {
testFunction(assert, loader, function onDone() {
loader.unload();
resetLocale();
done();
});
}
catch(e) {
console.exception(e);
}
},
function failure(error) {
assert.fail("Unable to load locales: " + error);
});
};
}
exports.testExactMatching = createTest("fr-FR", function(assert, loader, done) {
let _ = loader.require("sdk/l10n").get;
assert.equal(_("Not translated"), "Not translated",
"Key not translated");
assert.equal(_("Translated"), "Oui",
"Simple key translated");
// Placeholders
assert.equal(_("placeholderString", "works"), "Placeholder works",
"Value with placeholder");
assert.equal(_("Placeholder %s", "works"), "Placeholder works",
"Key without value but with placeholder");
assert.equal(_("Placeholders %2s %1s %s.", "working", "are", "correctly"),
"Placeholders are working correctly.",
"Multiple placeholders");
// Plurals
assert.equal(_("downloadsCount", 0),
"0 téléchargement",
"PluralForm form 'one' for 0 in french");
assert.equal(_("downloadsCount", 1),
"1 téléchargement",
"PluralForm form 'one' for 1 in french");
assert.equal(_("downloadsCount", 2),
"2 téléchargements",
"PluralForm form 'other' for n > 1 in french");
done();
});
exports.testHtmlLocalizationPageWorker = createTest("en-GB", function(assert, loader, done) {
// Ensure initing html component that watch document creations
// Note that this module is automatically initialized in
// cuddlefish.js:Loader.main in regular addons. But it isn't for unit tests.
let loaderHtmlL10n = loader.require("sdk/l10n/html");
loaderHtmlL10n.enable();
let uri = require("sdk/self").data.url("test-localization.html");
let worker = loader.require("sdk/page-worker").Page({
contentURL: uri,
contentScript: "new " + function ContentScriptScope() {
let nodes = document.body.querySelectorAll("*[data-l10n-id]");
self.postMessage([nodes[0].innerHTML,
nodes[1].innerHTML,
nodes[2].innerHTML,
nodes[3].innerHTML,
nodes[4].title,
nodes[4].getAttribute("alt"),
nodes[4].getAttribute("accesskey"),
nodes[4].getAttribute("aria-label"),
nodes[4].getAttribute("aria-valuetext"),
nodes[4].getAttribute("aria-moz-hint"),
nodes[5].placeholder,
nodes[6].label]);
},
onMessage: function (data) {
assert.equal(
data[0],
"Kept as-is",
"Nodes with unknown id in .properties are kept 'as-is'"
);
assert.equal(data[1], "Yes", "HTML is translated");
assert.equal(
data[2],
"no &lt;b&gt;HTML&lt;/b&gt; injection",
"Content from .properties is text content; HTML can't be injected."
);
assert.equal(data[3], "Yes", "Multiple elements with same data-l10n-id are accepted.");
// Attribute translation tests
assert.equal(data[4], "Yes", "Title attributes gets translated.");
assert.equal(data[5], "Yes", "Alt attributes gets translated.");
assert.equal(data[6], "B", "Accesskey gets translated.");
assert.equal(data[7], "Yes", "Aria-Label gets translated.");
assert.equal(data[8], "Value", "Aria-valuetext gets translated.");
assert.equal(data[9], "Hint", "Aria-moz-hint gets translated.");
assert.equal(data[10], "Yes", "Form placeholders are translateable.");
assert.equal(data[11], "Yes", "Labels of select options and context menus are translateable.");
done();
}
});
});
exports.testHtmlLocalization = createTest("en-GB", function(assert, loader, done) {
// Ensure initing html component that watch document creations
// Note that this module is automatically initialized in
// cuddlefish.js:Loader.main in regular addons. But it isn't for unit tests.
let loaderHtmlL10n = loader.require("sdk/l10n/html");
loaderHtmlL10n.enable();
let uri = require("sdk/self").data.url("test-localization.html");
loader.require("sdk/tabs").open({
url: uri,
onReady: function(tab) {
tab.attach({
contentURL: uri,
contentScript: "new " + function ContentScriptScope() {
let nodes = document.body.querySelectorAll("*[data-l10n-id]");
self.postMessage([nodes[0].innerHTML,
nodes[1].innerHTML,
nodes[2].innerHTML,
nodes[3].innerHTML,
nodes[4].title,
nodes[4].getAttribute("alt"),
nodes[4].getAttribute("accesskey"),
nodes[4].getAttribute("aria-label"),
nodes[4].getAttribute("aria-valuetext"),
nodes[4].getAttribute("aria-moz-hint"),
nodes[5].placeholder,
nodes[6].label]);
},
onMessage: function (data) {
assert.equal(
data[0],
"Kept as-is",
"Nodes with unknown id in .properties are kept 'as-is'"
);
assert.equal(data[1], "Yes", "HTML is translated");
assert.equal(
data[2],
"no &lt;b&gt;HTML&lt;/b&gt; injection",
"Content from .properties is text content; HTML can't be injected."
);
assert.equal(data[3], "Yes", "Multiple elements with same data-l10n-id are accepted.");
// Attribute translation tests
assert.equal(data[4], "Yes", "Title attributes gets translated.");
assert.equal(data[5], "Yes", "Alt attributes gets translated.");
assert.equal(data[6], "B", "Accesskey gets translated.");
assert.equal(data[7], "Yes", "Aria-Label gets translated.");
assert.equal(data[8], "Value", "Aria-valuetext gets translated.");
assert.equal(data[9], "Hint", "Aria-moz-hint gets translated.");
assert.equal(data[10], "Yes", "Form placeholders are translateable.");
assert.equal(data[11], "Yes", "Labels of select options and context menus are translateable.");
tab.close(done);
}
});
}
});
});
exports.testEnUsLocaleName = createTest("en-US", function(assert, loader, done) {
let _ = loader.require("sdk/l10n").get;
assert.equal(_("Not translated"), "Not translated",
"String w/o translation is kept as-is");
assert.equal(_("Translated"), "Yes",
"String with translation is correctly translated");
// Check Unicode char escaping sequences
assert.equal(_("unicodeEscape"), " @ ",
"Unicode escaped sequances are correctly converted");
// Check plural forms regular matching
assert.equal(_("downloadsCount", 0),
"0 downloads",
"PluralForm form 'other' for 0 in english");
assert.equal(_("downloadsCount", 1),
"one download",
"PluralForm form 'one' for 1 in english");
assert.equal(_("downloadsCount", 2),
"2 downloads",
"PluralForm form 'other' for n != 1 in english");
// Check optional plural forms
assert.equal(_("pluralTest", 0),
"optional zero form",
"PluralForm form 'zero' can be optionaly specified. (Isn't mandatory in english)");
assert.equal(_("pluralTest", 1),
"fallback to other",
"If the specific plural form is missing, we fallback to 'other'");
// Ensure that we can omit specifying the generic key without [other]
// key[one] = ...
// key[other] = ... # Instead of `key = ...`
assert.equal(_("explicitPlural", 1),
"one",
"PluralForm form can be omitting generic key [i.e. without ...[other] at end of key)");
assert.equal(_("explicitPlural", 10),
"other",
"PluralForm form can be omitting generic key [i.e. without ...[other] at end of key)");
assert.equal(_("first_identifier", "ONE", "TWO"), "the entries are ONE and TWO.", "first_identifier no count");
assert.equal(_("first_identifier", 0, "ONE", "TWO"), "the entries are ONE and TWO.", "first_identifier with count = 0");
assert.equal(_("first_identifier", 1, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "first_identifier with count = 1");
assert.equal(_("first_identifier", 2, "ONE", "TWO"), "the entries are ONE and TWO.", "first_identifier with count = 2");
assert.equal(_("second_identifier", "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with no count");
assert.equal(_("second_identifier", 0, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with count = 0");
assert.equal(_("second_identifier", 1, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with count = 1");
assert.equal(_("second_identifier", 2, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with count = 2");
assert.equal(_("third_identifier", "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "third_identifier with no count");
assert.equal(_("third_identifier", 0, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "third_identifier with count = 0");
assert.equal(_("third_identifier", 2, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "third_identifier with count = 2");
done();
});
exports.testUsingJSON = function(assert) {
assert.equal(usingJSON, !isNative, 'using json');
}
exports.testShortLocaleName = createTest("eo", function(assert, loader, done) {
let _ = loader.require("sdk/l10n").get;
assert.equal(_("Not translated"), "Not translated",
"String w/o translation is kept as-is");
assert.equal(_("Translated"), "jes",
"String with translation is correctly translated");
done();
});
// Before running tests, disable HTML service which is automatially enabled
// in api-utils/addon/runner.js
require('sdk/l10n/html').disable();
require("sdk/test/runner").runTestsFromModule(module);

View file

@ -0,0 +1,5 @@
{
"id": "e10s-l10n@jetpack",
"main": "./main.js",
"version": "0.0.1"
}

View file

@ -0,0 +1,578 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 LOCAL_URI = "about:robots";
const REMOTE_URI = "data:text/html;charset=utf-8,remote";
const { Cu } = require('chrome');
const { Loader } = require('sdk/test/loader');
const { getTabs, openTab, closeTab, setTabURL, getBrowserForTab, getURI } = require('sdk/tabs/utils');
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
const { cleanUI } = require("sdk/test/utils");
const { setTimeout } = require("sdk/timers");
const { promiseEvent, promiseDOMEvent, promiseEventOnItemAndContainer,
waitForProcesses, getChildFrameCount, isE10S } = require("./utils");
const { after } = require('sdk/test/utils');
const { processID } = require('sdk/system/runtime');
const { set } = require('sdk/preferences/service');
// The hidden preload browser messes up our frame counts
set('browser.newtab.preload', false);
function promiseTabFrameAttach(frames) {
return new Promise(resolve => {
let listener = function(frame, ...args) {
if (!frame.isTab)
return;
frames.off("attach", listener);
resolve([frame, ...args]);
}
frames.on("attach", listener);
});
}
// Check that we see a process stop and start
exports["test process restart"] = function*(assert) {
if (!isE10S) {
assert.pass("Skipping test in non-e10s mode");
return;
}
let window = getMostRecentBrowserWindow();
let tabs = getTabs(window);
assert.equal(tabs.length, 1, "Should have just the one tab to start with");
let tab = tabs[0];
let browser = getBrowserForTab(tab);
let loader = new Loader(module);
let { processes, frames } = yield waitForProcesses(loader);
let remoteProcess = Array.filter(processes, p => p.isRemote)[0];
let localProcess = Array.filter(processes, p => !p.isRemote)[0];
let remoteFrame = Array.filter(frames, f => f.process == remoteProcess)[0];
// Switch the remote tab to a local URI which should kill the remote process
let frameDetach = promiseEventOnItemAndContainer(assert, remoteFrame, frames, 'detach');
let frameAttach = promiseTabFrameAttach(frames);
let processDetach = promiseEventOnItemAndContainer(assert, remoteProcess, processes, 'detach');
let browserLoad = promiseDOMEvent(browser, "load", true);
setTabURL(tab, LOCAL_URI);
// The load should kill the remote frame
yield frameDetach;
// And create a new frame in the local process
let [newFrame] = yield frameAttach;
assert.equal(newFrame.process, localProcess, "New frame should be in the local process");
// And kill the process
yield processDetach;
yield browserLoad;
frameDetach = promiseEventOnItemAndContainer(assert, newFrame, frames, 'detach');
let processAttach = promiseEvent(processes, 'attach');
frameAttach = promiseTabFrameAttach(frames);
browserLoad = promiseDOMEvent(browser, "load", true);
setTabURL(tab, REMOTE_URI);
// The load should kill the remote frame
yield frameDetach;
// And create a new remote process
[remoteProcess] = yield processAttach;
assert.ok(remoteProcess.isRemote, "Process should be remote");
// And create a new frame in the remote process
[newFrame] = yield frameAttach;
assert.equal(newFrame.process, remoteProcess, "New frame should be in the remote process");
yield browserLoad;
browserLoad = promiseDOMEvent(browser, "load", true);
setTabURL(tab, "about:blank");
yield browserLoad;
loader.unload();
};
// Test that we find the right number of processes and that messaging between
// them works and none of the streams cross
exports["test process list"] = function*(assert) {
let loader = new Loader(module);
let { processes } = loader.require('sdk/remote/parent');
let processCount = 0;
processes.forEvery(processes => processCount++);
yield waitForProcesses(loader);
let remoteProcesses = Array.filter(processes, process => process.isRemote);
let localProcesses = Array.filter(processes, process => !process.isRemote);
assert.equal(localProcesses.length, 1, "Should always be one process");
if (isE10S) {
assert.equal(remoteProcesses.length, 1, "Should be one remote process");
}
else {
assert.equal(remoteProcesses.length, 0, "Should be no remote processes");
}
assert.equal(processCount, processes.length, "Should have seen all processes");
processCount = 0;
processes.forEvery(process => processCount++);
assert.equal(processCount, processes.length, "forEvery should send existing processes to the listener");
localProcesses[0].port.on('sdk/test/pong', (process, key) => {
assert.equal(key, "local", "Should not have seen a pong from the local process with the wrong key");
});
if (isE10S) {
remoteProcesses[0].port.on('sdk/test/pong', (process, key) => {
assert.equal(key, "remote", "Should not have seen a pong from the remote process with the wrong key");
});
}
let promise = promiseEventOnItemAndContainer(assert, localProcesses[0].port, processes.port, 'sdk/test/pong', localProcesses[0]);
localProcesses[0].port.emit('sdk/test/ping', "local");
let reply = yield promise;
assert.equal(reply[0], "local", "Saw the process reply with the right key");
if (isE10S) {
promise = promiseEventOnItemAndContainer(assert, remoteProcesses[0].port, processes.port, 'sdk/test/pong', remoteProcesses[0]);
remoteProcesses[0].port.emit('sdk/test/ping', "remote");
reply = yield promise;
assert.equal(reply[0], "remote", "Saw the process reply with the right key");
assert.notEqual(localProcesses[0], remoteProcesses[0], "Processes should be different");
}
loader.unload();
};
// Test that the frame lists are kept up to date
exports["test frame list"] = function*(assert) {
function browserFrames(list) {
return Array.filter(list, b => b.isTab).length;
}
let window = getMostRecentBrowserWindow();
let tabs = getTabs(window);
assert.equal(tabs.length, 1, "Should have just the one tab to start with");
let loader = new Loader(module);
let { processes, frames } = yield waitForProcesses(loader);
assert.equal(browserFrames(frames), getTabs(window).length, "Should be the right number of browser frames.");
assert.equal((yield getChildFrameCount(processes)), frames.length, "Child processes should have the right number of frames");
let promise = promiseTabFrameAttach(frames);
let tab1 = openTab(window, LOCAL_URI);
let [frame1] = yield promise;
assert.ok(!!frame1, "Should have seen the new frame");
assert.ok(!frame1.process.isRemote, "Frame should not be remote");
assert.equal(browserFrames(frames), getTabs(window).length, "Should be the right number of browser frames.");
assert.equal((yield getChildFrameCount(processes)), frames.length, "Child processes should have the right number of frames");
promise = promiseTabFrameAttach(frames);
let tab2 = openTab(window, REMOTE_URI);
let [frame2] = yield promise;
assert.ok(!!frame2, "Should have seen the new frame");
if (isE10S)
assert.ok(frame2.process.isRemote, "Frame should be remote");
else
assert.ok(!frame2.process.isRemote, "Frame should not be remote");
assert.equal(browserFrames(frames), getTabs(window).length, "Should be the right number of browser frames.");
assert.equal((yield getChildFrameCount(processes)), frames.length, "Child processes should have the right number of frames");
frames.port.emit('sdk/test/ping')
yield new Promise(resolve => {
let count = 0;
let listener = () => {
console.log("Saw pong");
count++;
if (count == frames.length) {
frames.port.off('sdk/test/pong', listener);
resolve();
}
};
frames.port.on('sdk/test/pong', listener);
});
let badListener = () => {
assert.fail("Should not have seen a response through this frame");
}
frame1.port.on('sdk/test/pong', badListener);
frame2.port.emit('sdk/test/ping', 'b');
let [key] = yield promiseEventOnItemAndContainer(assert, frame2.port, frames.port, 'sdk/test/pong', frame2);
assert.equal(key, 'b', "Should have seen the right response");
frame1.port.off('sdk/test/pong', badListener);
frame2.port.on('sdk/test/pong', badListener);
frame1.port.emit('sdk/test/ping', 'b');
[key] = yield promiseEventOnItemAndContainer(assert, frame1.port, frames.port, 'sdk/test/pong', frame1);
assert.equal(key, 'b', "Should have seen the right response");
frame2.port.off('sdk/test/pong', badListener);
promise = promiseEventOnItemAndContainer(assert, frame1, frames, 'detach');
closeTab(tab1);
yield promise;
assert.equal(browserFrames(frames), getTabs(window).length, "Should be the right number of browser frames.");
assert.equal((yield getChildFrameCount(processes)), frames.length, "Child processes should have the right number of frames");
promise = promiseEventOnItemAndContainer(assert, frame2, frames, 'detach');
closeTab(tab2);
yield promise;
assert.equal(browserFrames(frames), getTabs(window).length, "Should be the right number of browser frames.");
assert.equal((yield getChildFrameCount(processes)), frames.length, "Child processes should have the right number of frames");
loader.unload();
};
// Test that multiple loaders get their own loaders in the child and messages
// don't cross. Unload should work
exports["test new loader"] = function*(assert) {
let loader1 = new Loader(module);
let { processes: processes1 } = yield waitForProcesses(loader1);
let loader2 = new Loader(module);
let { processes: processes2 } = yield waitForProcesses(loader2);
let process1 = [...processes1][0];
let process2 = [...processes2][0];
process1.port.on('sdk/test/pong', (process, key) => {
assert.equal(key, "a", "Should have seen the right pong");
});
process2.port.on('sdk/test/pong', (process, key) => {
assert.equal(key, "b", "Should have seen the right pong");
});
process1.port.emit('sdk/test/ping', 'a');
yield promiseEvent(process1.port, 'sdk/test/pong');
process2.port.emit('sdk/test/ping', 'b');
yield promiseEvent(process2.port, 'sdk/test/pong');
loader1.unload();
process2.port.emit('sdk/test/ping', 'b');
yield promiseEvent(process2.port, 'sdk/test/pong');
loader2.unload();
};
// Test that unloading the loader unloads the child instances
exports["test unload"] = function*(assert) {
let window = getMostRecentBrowserWindow();
let loader = new Loader(module);
let { frames } = yield waitForProcesses(loader);
let promise = promiseTabFrameAttach(frames);
let tab = openTab(window, "data:,<html/>");
let browser = getBrowserForTab(tab);
yield promiseDOMEvent(browser, "load", true);
let [frame] = yield promise;
assert.ok(!!frame, "Should have seen the new frame");
promise = promiseDOMEvent(browser, 'hashchange');
frame.port.emit('sdk/test/testunload');
loader.unload("shutdown");
yield promise;
let hash = getURI(tab).replace(/.*#/, "");
assert.equal(hash, "unloaded:shutdown", "Saw the correct hash change.")
closeTab(tab);
}
// Test that unloading the loader causes the child to see frame detach events
exports["test frame detach on unload"] = function*(assert) {
let window = getMostRecentBrowserWindow();
let loader = new Loader(module);
let { frames } = yield waitForProcesses(loader);
let promise = promiseTabFrameAttach(frames);
let tab = openTab(window, "data:,<html/>");
let browser = getBrowserForTab(tab);
yield promiseDOMEvent(browser, "load", true);
let [frame] = yield promise;
assert.ok(!!frame, "Should have seen the new frame");
promise = promiseDOMEvent(browser, 'hashchange');
frame.port.emit('sdk/test/testdetachonunload');
loader.unload("shutdown");
yield promise;
let hash = getURI(tab).replace(/.*#/, "");
assert.equal(hash, "unloaded", "Saw the correct hash change.")
closeTab(tab);
}
// Test that DOM event listener on the frame object works
exports["test frame event listeners"] = function*(assert) {
let window = getMostRecentBrowserWindow();
let loader = new Loader(module);
let { frames } = yield waitForProcesses(loader);
let promise = promiseTabFrameAttach(frames);
let tab = openTab(window, "data:text/html,<html></html>");
let browser = getBrowserForTab(tab);
yield promiseDOMEvent(browser, "load", true);
let [frame] = yield promise;
assert.ok(!!frame, "Should have seen the new frame");
frame.port.emit('sdk/test/registerframeevent');
promise = Promise.all([
promiseEvent(frame.port, 'sdk/test/sawreply'),
promiseEvent(frame.port, 'sdk/test/eventsent')
]);
frame.port.emit('sdk/test/sendevent');
yield promise;
frame.port.emit('sdk/test/unregisterframeevent');
promise = promiseEvent(frame.port, 'sdk/test/eventsent');
frame.port.on('sdk/test/sawreply', () => {
assert.fail("Should not have seen the event listener reply");
});
frame.port.emit('sdk/test/sendevent');
yield promise;
closeTab(tab);
loader.unload();
}
// Test that DOM event listener on the frames object works
exports["test frames event listeners"] = function*(assert) {
let window = getMostRecentBrowserWindow();
let loader = new Loader(module);
let { frames } = yield waitForProcesses(loader);
let promise = promiseTabFrameAttach(frames);
let tab = openTab(window, "data:text/html,<html></html>");
let browser = getBrowserForTab(tab);
yield promiseDOMEvent(browser, "load", true);
let [frame] = yield promise;
assert.ok(!!frame, "Should have seen the new frame");
frame.port.emit('sdk/test/registerframesevent');
promise = Promise.all([
promiseEvent(frame.port, 'sdk/test/sawreply'),
promiseEvent(frame.port, 'sdk/test/eventsent')
]);
frame.port.emit('sdk/test/sendevent');
yield promise;
frame.port.emit('sdk/test/unregisterframesevent');
promise = promiseEvent(frame.port, 'sdk/test/eventsent');
frame.port.on('sdk/test/sawreply', () => {
assert.fail("Should not have seen the event listener reply");
});
frame.port.emit('sdk/test/sendevent');
yield promise;
closeTab(tab);
loader.unload();
}
// Test that unloading unregisters frame DOM events
exports["test unload removes frame event listeners"] = function*(assert) {
let window = getMostRecentBrowserWindow();
let loader = new Loader(module);
let { frames } = yield waitForProcesses(loader);
let loader2 = new Loader(module);
let { frames: frames2 } = yield waitForProcesses(loader2);
let promise = promiseTabFrameAttach(frames);
let promise2 = promiseTabFrameAttach(frames2);
let tab = openTab(window, "data:text/html,<html></html>");
let browser = getBrowserForTab(tab);
yield promiseDOMEvent(browser, "load", true);
let [frame] = yield promise;
let [frame2] = yield promise2;
assert.ok(!!frame && !!frame2, "Should have seen the new frame");
frame.port.emit('sdk/test/registerframeevent');
promise = Promise.all([
promiseEvent(frame2.port, 'sdk/test/sawreply'),
promiseEvent(frame2.port, 'sdk/test/eventsent')
]);
frame2.port.emit('sdk/test/sendevent');
yield promise;
loader.unload();
promise = promiseEvent(frame2.port, 'sdk/test/eventsent');
frame2.port.on('sdk/test/sawreply', () => {
assert.fail("Should not have seen the event listener reply");
});
frame2.port.emit('sdk/test/sendevent');
yield promise;
closeTab(tab);
loader2.unload();
}
// Test that unloading unregisters frames DOM events
exports["test unload removes frames event listeners"] = function*(assert) {
let window = getMostRecentBrowserWindow();
let loader = new Loader(module);
let { frames } = yield waitForProcesses(loader);
let loader2 = new Loader(module);
let { frames: frames2 } = yield waitForProcesses(loader2);
let promise = promiseTabFrameAttach(frames);
let promise2 = promiseTabFrameAttach(frames2);
let tab = openTab(window, "data:text/html,<html></html>");
let browser = getBrowserForTab(tab);
yield promiseDOMEvent(browser, "load", true);
let [frame] = yield promise;
let [frame2] = yield promise2;
assert.ok(!!frame && !!frame2, "Should have seen the new frame");
frame.port.emit('sdk/test/registerframesevent');
promise = Promise.all([
promiseEvent(frame2.port, 'sdk/test/sawreply'),
promiseEvent(frame2.port, 'sdk/test/eventsent')
]);
frame2.port.emit('sdk/test/sendevent');
yield promise;
loader.unload();
promise = promiseEvent(frame2.port, 'sdk/test/eventsent');
frame2.port.on('sdk/test/sawreply', () => {
assert.fail("Should not have seen the event listener reply");
});
frame2.port.emit('sdk/test/sendevent');
yield promise;
closeTab(tab);
loader2.unload();
}
// Check that the child frame has the right properties
exports["test frame properties"] = function*(assert) {
let loader = new Loader(module);
let { frames } = yield waitForProcesses(loader);
let promise = new Promise(resolve => {
let count = frames.length;
let listener = (frame, properties) => {
assert.equal(properties.isTab, frame.isTab,
"Child frame should have the same isTab property");
if (--count == 0) {
frames.port.off('sdk/test/replyproperties', listener);
resolve();
}
}
frames.port.on('sdk/test/replyproperties', listener);
})
frames.port.emit('sdk/test/checkproperties');
yield promise;
loader.unload();
}
// Check that non-remote processes have the same process ID and remote processes
// have different IDs
exports["test processID"] = function*(assert) {
let loader = new Loader(module);
let { processes } = yield waitForProcesses(loader);
for (let process of processes) {
process.port.emit('sdk/test/getprocessid');
let [p, ID] = yield promiseEvent(process.port, 'sdk/test/processid');
if (process.isRemote) {
assert.notEqual(ID, processID, "Remote processes should have a different process ID");
}
else {
assert.equal(ID, processID, "Remote processes should have the same process ID");
}
}
loader.unload();
}
// Check that sdk/remote/parent and sdk/remote/child can only be loaded in the
// appropriate loaders
exports["test cannot load in wrong loader"] = function*(assert) {
let loader = new Loader(module);
let { processes } = yield waitForProcesses(loader);
try {
require('sdk/remote/child');
assert.fail("Should not have been able to load sdk/remote/child");
}
catch (e) {
assert.ok(/Cannot load sdk\/remote\/child in a main process loader/.test(e),
"Should have seen the right exception.");
}
for (let process of processes) {
processes.port.emit('sdk/test/parentload');
let [_, isChildLoader, loaded, message] = yield promiseEvent(processes.port, 'sdk/test/parentload');
assert.ok(isChildLoader, "Process should see itself in a child loader.");
assert.ok(!loaded, "Process couldn't load sdk/remote/parent.");
assert.ok(/Cannot load sdk\/remote\/parent in a child loader/.test(message),
"Should have seen the right exception.");
}
loader.unload();
};
exports["test send cpow"] = function*(assert) {
if (!isE10S) {
assert.pass("Skipping test in non-e10s mode");
return;
}
let window = getMostRecentBrowserWindow();
let tabs = getTabs(window);
assert.equal(tabs.length, 1, "Should have just the one tab to start with");
let tab = tabs[0];
let browser = getBrowserForTab(tab);
assert.ok(Cu.isCrossProcessWrapper(browser.contentWindow),
"Should have a CPOW for the browser content window");
let loader = new Loader(module);
let { processes } = yield waitForProcesses(loader);
processes.port.emitCPOW('sdk/test/cpow', ['foobar'], { window: browser.contentWindow });
let [process, arg, id] = yield promiseEvent(processes.port, 'sdk/test/cpow');
assert.ok(process.isRemote, "Response should come from the remote process");
assert.equal(arg, "foobar", "Argument should have passed through");
assert.equal(id, browser.outerWindowID, "Should have got the ID from the child");
};
after(exports, function*(name, assert) {
yield cleanUI();
});
require('sdk/test/runner').runTestsFromModule(module);

View file

@ -0,0 +1,9 @@
{
"name": "e10s-remote",
"title": "e10s-remote",
"id": "remote@jetpack",
"description": "Run remote tests",
"version": "1.0.0",
"main": "main.js",
"e10s": true
}

View file

@ -0,0 +1,129 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 { when } = require('sdk/system/unload');
const { process, frames } = require('sdk/remote/child');
const { loaderID } = require('@loader/options');
const { processID } = require('sdk/system/runtime');
const system = require('sdk/system/events');
const { Cu } = require('chrome');
const { isChildLoader } = require('sdk/remote/core');
const { getOuterId } = require('sdk/window/utils');
function log(str) {
console.log("remote[" + loaderID + "][" + processID + "]: " + str);
}
log("module loaded");
process.port.emit('sdk/test/load');
process.port.on('sdk/test/ping', (process, key) => {
log("received process ping");
process.port.emit('sdk/test/pong', key);
});
var frameCount = 0;
frames.forEvery(frame => {
frameCount++;
frame.on('detach', () => {
frameCount--;
});
frame.port.on('sdk/test/ping', (frame, key) => {
log("received frame ping");
frame.port.emit('sdk/test/pong', key);
});
});
frames.port.on('sdk/test/checkproperties', frame => {
frame.port.emit('sdk/test/replyproperties', {
isTab: frame.isTab
});
});
process.port.on('sdk/test/count', () => {
log("received count ping");
process.port.emit('sdk/test/count', frameCount);
});
process.port.on('sdk/test/getprocessid', () => {
process.port.emit('sdk/test/processid', processID);
});
frames.port.on('sdk/test/testunload', (frame) => {
// Cache the content since the frame will have been destroyed by the time
// we see the unload event.
let content = frame.content;
when((reason) => {
content.location = "#unloaded:" + reason;
});
});
frames.port.on('sdk/test/testdetachonunload', (frame) => {
let content = frame.content;
frame.on('detach', () => {
console.log("Detach from " + frame.content.location);
frame.content.location = "#unloaded";
});
});
frames.port.on('sdk/test/sendevent', (frame) => {
let doc = frame.content.document;
let listener = () => {
frame.port.emit('sdk/test/sawreply');
}
system.on("Test:Reply", listener);
let event = new frame.content.CustomEvent("Test:Event");
doc.dispatchEvent(event);
system.off("Test:Reply", listener);
frame.port.emit('sdk/test/eventsent');
});
process.port.on('sdk/test/parentload', () => {
let loaded = false;
let message = "";
try {
require('sdk/remote/parent');
loaded = true;
}
catch (e) {
message = "" + e;
}
process.port.emit('sdk/test/parentload',
isChildLoader,
loaded,
message
)
});
function listener(event) {
// Use the raw observer service here since it will be usable even if the
// loader has unloaded
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
Services.obs.notifyObservers(null, "Test:Reply", "");
}
frames.port.on('sdk/test/registerframesevent', (frame) => {
frames.addEventListener("Test:Event", listener, true);
});
frames.port.on('sdk/test/unregisterframesevent', (frame) => {
frames.removeEventListener("Test:Event", listener, true);
});
frames.port.on('sdk/test/registerframeevent', (frame) => {
frame.addEventListener("Test:Event", listener, true);
});
frames.port.on('sdk/test/unregisterframeevent', (frame) => {
frame.removeEventListener("Test:Event", listener, true);
});
process.port.on('sdk/test/cpow', (process, arg, cpows) => {
process.port.emit('sdk/test/cpow', arg, getOuterId(cpows.window));
});

View file

@ -0,0 +1,110 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 { Task: { async } } = Cu.import('resource://gre/modules/Task.jsm', {});
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
const REMOTE_MODULE = "./remote-module";
function promiseEvent(emitter, event) {
console.log("Waiting for " + event);
return new Promise(resolve => {
emitter.once(event, (...args) => {
console.log("Saw " + event);
resolve(args);
});
});
}
exports.promiseEvent = promiseEvent;
function promiseDOMEvent(target, event, isCapturing = false) {
console.log("Waiting for " + event);
return new Promise(resolve => {
let listener = (event) => {
target.removeEventListener(event, listener, isCapturing);
resolve(event);
};
target.addEventListener(event, listener, isCapturing);
})
}
exports.promiseDOMEvent = promiseDOMEvent;
const promiseEventOnItemAndContainer = async(function*(assert, itemport, container, event, item = itemport) {
let itemEvent = promiseEvent(itemport, event);
let containerEvent = promiseEvent(container, event);
let itemArgs = yield itemEvent;
let containerArgs = yield containerEvent;
assert.equal(containerArgs[0], item, "Should have seen a container event for the right item");
assert.equal(JSON.stringify(itemArgs), JSON.stringify(containerArgs), "Arguments should have matched");
// Strip off the item from the returned arguments
return itemArgs.slice(1);
});
exports.promiseEventOnItemAndContainer = promiseEventOnItemAndContainer;
const waitForProcesses = async(function*(loader) {
console.log("Starting remote");
let { processes, frames, remoteRequire } = loader.require('sdk/remote/parent');
remoteRequire(REMOTE_MODULE, module);
let events = [];
// In e10s we should expect to see two processes
let expectedCount = isE10S ? 2 : 1;
yield new Promise(resolve => {
let count = 0;
// Wait for a process to be detected
let listener = process => {
console.log("Saw a process attach");
// Wait for the remote module to load in this process
process.port.once('sdk/test/load', () => {
console.log("Saw a remote module load");
count++;
if (count == expectedCount) {
processes.off('attach', listener);
resolve();
}
});
}
processes.on('attach', listener);
});
console.log("Remote ready");
return { processes, frames, remoteRequire };
});
exports.waitForProcesses = waitForProcesses;
// Counts the frames in all the child processes
const getChildFrameCount = async(function*(processes) {
let frameCount = 0;
for (let process of processes) {
process.port.emit('sdk/test/count');
let [p, count] = yield promiseEvent(process.port, 'sdk/test/count');
frameCount += count;
}
return frameCount;
});
exports.getChildFrameCount = getChildFrameCount;
const mainWindow = getMostRecentBrowserWindow();
const isE10S = mainWindow.gMultiProcessBrowser;
exports.isE10S = isE10S;
if (isE10S) {
console.log("Testing in E10S mode");
// We expect a child process to already be present, make sure that is the case
mainWindow.XULBrowserWindow.forceInitialBrowserRemote();
}
else {
console.log("Testing in non-E10S mode");
}

View file

@ -0,0 +1,23 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 { merge } = require('sdk/util/object');
const { version } = require('sdk/system');
const SKIPPING_TESTS = {
"test skip": (assert) => assert.pass("nothing to test here")
};
merge(module.exports, require('./test-tab'));
merge(module.exports, require('./test-tab-events'));
merge(module.exports, require('./test-tab-observer'));
merge(module.exports, require('./test-tab-utils'));
// run e10s tests only on builds from trunk, fx-team, Nightly..
if (!version.endsWith('a1')) {
module.exports = SKIPPING_TESTS;
}
require('sdk/test/runner').runTestsFromModule(module);

View file

@ -0,0 +1,91 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const { Loader } = require('sdk/test/loader');
const { loader } = LoaderWithHookedConsole(module);
const pb = loader.require('sdk/private-browsing');
const pbUtils = loader.require('sdk/private-browsing/utils');
const xulApp = require("sdk/system/xul-app");
const { open: openWindow, getMostRecentBrowserWindow } = require('sdk/window/utils');
const { openTab, getTabContentWindow, getActiveTab, setTabURL, closeTab } = require('sdk/tabs/utils');
const promise = require("sdk/core/promise");
const windowHelpers = require('sdk/window/helpers');
const events = require("sdk/system/events");
function LoaderWithHookedConsole(module) {
let globals = {};
let errors = [];
globals.console = Object.create(console, {
error: {
value: function(e) {
errors.push(e);
if (!/DEPRECATED:/.test(e)) {
console.error(e);
}
}
}
});
let loader = Loader(module, globals);
return {
loader: loader,
errors: errors
}
}
exports.pb = pb;
exports.pbUtils = pbUtils;
exports.LoaderWithHookedConsole = LoaderWithHookedConsole;
exports.openWebpage = function openWebpage(url, enablePrivate) {
if (xulApp.is("Fennec")) {
let chromeWindow = getMostRecentBrowserWindow();
let rawTab = openTab(chromeWindow, url, {
isPrivate: enablePrivate
});
return {
ready: promise.resolve(getTabContentWindow(rawTab)),
close: function () {
closeTab(rawTab);
// Returns a resolved promise as there is no need to wait
return promise.resolve();
}
};
}
else {
let win = openWindow(null, {
features: {
private: enablePrivate
}
});
let deferred = promise.defer();
// Wait for delayed startup code to be executed, in order to ensure
// that the window is really ready
events.on("browser-delayed-startup-finished", function onReady({subject}) {
if (subject == win) {
events.off("browser-delayed-startup-finished", onReady);
deferred.resolve(win);
let rawTab = getActiveTab(win);
setTabURL(rawTab, url);
deferred.resolve(getTabContentWindow(rawTab));
}
}, true);
return {
ready: deferred.promise,
close: function () {
return windowHelpers.close(win);
}
};
}
return null;
}

View file

@ -0,0 +1,238 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Loader } = require("sdk/test/loader");
const utils = require("sdk/tabs/utils");
const { open, close } = require("sdk/window/helpers");
const { getMostRecentBrowserWindow } = require("sdk/window/utils");
const { events } = require("sdk/tab/events");
const { on, off } = require("sdk/event/core");
const { resolve, defer } = require("sdk/core/promise");
var isFennec = require("sdk/system/xul-app").is("Fennec");
function test(options) {
return function(assert, done) {
let tabEvents = [];
let tabs = [];
let { promise, resolve: resolveP } = defer();
let win = isFennec ? resolve(getMostRecentBrowserWindow()) :
open(null, {
features: { private: true, toolbar:true, chrome: true }
});
let window = null;
// Firefox events are fired sync; Fennec events async
// this normalizes the tests
function handler (event) {
tabEvents.push(event);
runIfReady();
}
function runIfReady () {
let releventEvents = getRelatedEvents(tabEvents, tabs);
if (options.readyWhen(releventEvents))
options.end({
tabs: tabs,
events: releventEvents,
assert: assert,
done: resolveP
});
}
win.then(function(w) {
window = w;
on(events, "data", handler);
options.start({ tabs: tabs, window: window });
// Execute here for synchronous FF events, as the handlers
// were called before tabs were pushed to `tabs`
runIfReady();
return promise;
}).then(function() {
off(events, "data", handler);
return isFennec ? null : close(window);
}).then(done, assert.fail);
};
}
// Just making sure that tab events work for already opened tabs not only
// for new windows.
exports["test current window"] = test({
readyWhen: events => events.length === 3,
start: ({ tabs, window }) => {
let tab = utils.openTab(window, 'data:text/plain,open');
tabs.push(tab);
utils.closeTab(tab);
},
end: ({ tabs, events, assert, done }) => {
let [open, select, close] = events;
let tab = tabs[0];
assert.equal(open.type, "TabOpen");
assert.equal(open.target, tab);
assert.equal(select.type, "TabSelect");
assert.equal(select.target, tab);
assert.equal(close.type, "TabClose");
assert.equal(close.target, tab);
done();
}
});
exports["test open"] = test({
readyWhen: events => events.length === 2,
start: ({ tabs, window }) => {
tabs.push(utils.openTab(window, 'data:text/plain,open'));
},
end: ({ tabs, events, assert, done }) => {
let [open, select] = events;
let tab = tabs[0];
assert.equal(open.type, "TabOpen");
assert.equal(open.target, tab);
assert.equal(select.type, "TabSelect");
assert.equal(select.target, tab);
done();
}
});
exports["test open -> close"] = test({
readyWhen: events => events.length === 3,
start: ({ tabs, window }) => {
// First tab is useless we just open it so that closing second tab won't
// close window on some platforms.
utils.openTab(window, 'data:text/plain,ignore');
let tab = utils.openTab(window, 'data:text/plain,open-close');
tabs.push(tab);
utils.closeTab(tab);
},
end: ({ tabs, events, assert, done }) => {
let [open, select, close] = events;
let tab = tabs[0];
assert.equal(open.type, "TabOpen");
assert.equal(open.target, tab);
assert.equal(select.type, "TabSelect");
assert.equal(select.target, tab);
assert.equal(close.type, "TabClose");
assert.equal(close.target, tab);
done();
}
});
exports["test open -> open -> select"] = test({
readyWhen: events => events.length === 5,
start: ({tabs, window}) => {
tabs.push(utils.openTab(window, 'data:text/plain,Tab-1'));
tabs.push(utils.openTab(window, 'data:text/plain,Tab-2'));
utils.activateTab(tabs[0], window);
},
end: ({ tabs, events, assert, done }) => {
let [ tab1, tab2 ] = tabs;
let tab1Events = 0;
getRelatedEvents(events, tab1).map(event => {
tab1Events++;
if (tab1Events === 1)
assert.equal(event.type, "TabOpen", "first tab opened");
else
assert.equal(event.type, "TabSelect", "first tab selected");
assert.equal(event.target, tab1);
});
assert.equal(tab1Events, 3, "first tab has 3 events");
let tab2Opened;
getRelatedEvents(events, tab2).map(event => {
if (!tab2Opened)
assert.equal(event.type, "TabOpen", "second tab opened");
else
assert.equal(event.type, "TabSelect", "second tab selected");
tab2Opened = true;
assert.equal(event.target, tab2);
});
done();
}
});
exports["test open -> pin -> unpin"] = test({
readyWhen: events => events.length === (isFennec ? 2 : 5),
start: ({ tabs, window }) => {
tabs.push(utils.openTab(window, 'data:text/plain,pin-unpin'));
utils.pin(tabs[0]);
utils.unpin(tabs[0]);
},
end: ({ tabs, events, assert, done }) => {
let [open, select, move, pin, unpin] = events;
let tab = tabs[0];
assert.equal(open.type, "TabOpen");
assert.equal(open.target, tab);
assert.equal(select.type, "TabSelect");
assert.equal(select.target, tab);
if (isFennec) {
assert.pass("Tab pin / unpin is not supported by Fennec");
}
else {
assert.equal(move.type, "TabMove");
assert.equal(move.target, tab);
assert.equal(pin.type, "TabPinned");
assert.equal(pin.target, tab);
assert.equal(unpin.type, "TabUnpinned");
assert.equal(unpin.target, tab);
}
done();
}
});
exports["test open -> open -> move "] = test({
readyWhen: events => events.length === (isFennec ? 4 : 5),
start: ({tabs, window}) => {
tabs.push(utils.openTab(window, 'data:text/plain,Tab-1'));
tabs.push(utils.openTab(window, 'data:text/plain,Tab-2'));
utils.move(tabs[0], 2);
},
end: ({ tabs, events, assert, done }) => {
let [ tab1, tab2 ] = tabs;
let tab1Events = 0;
getRelatedEvents(events, tab1).map(event => {
tab1Events++;
if (tab1Events === 1)
assert.equal(event.type, "TabOpen", "first tab opened");
else if (tab1Events === 2)
assert.equal(event.type, "TabSelect", "first tab selected");
else if (tab1Events === 3 && isFennec)
assert.equal(event.type, "TabMove", "first tab moved");
assert.equal(event.target, tab1);
});
assert.equal(tab1Events, isFennec ? 2 : 3,
"correct number of events for first tab");
let tab2Events = 0;
getRelatedEvents(events, tab2).map(event => {
tab2Events++;
if (tab2Events === 1)
assert.equal(event.type, "TabOpen", "second tab opened");
else
assert.equal(event.type, "TabSelect", "second tab selected");
assert.equal(event.target, tab2);
});
done();
}
});
function getRelatedEvents (events, tabs) {
return events.filter(({target}) => ~([].concat(tabs)).indexOf(target));
}
// require("sdk/test").run(exports);

View file

@ -0,0 +1,46 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// TODO Fennec support in Bug #894525
module.metadata = {
"engines": {
"Firefox": "*"
}
}
const { openTab, closeTab } = require("sdk/tabs/utils");
const { Loader } = require("sdk/test/loader");
const { setTimeout } = require("sdk/timers");
exports["test unload tab observer"] = function(assert, done) {
let loader = Loader(module);
let window = loader.require("sdk/deprecated/window-utils").activeBrowserWindow;
let observer = loader.require("sdk/tabs/observer").observer;
let opened = 0;
let closed = 0;
observer.on("open", function onOpen(window) { opened++; });
observer.on("close", function onClose(window) { closed++; });
// Open and close tab to trigger observers.
closeTab(openTab(window, "data:text/html;charset=utf-8,tab-1"));
// Unload the module so that all listeners set by observer are removed.
loader.unload();
// Open and close tab once again.
closeTab(openTab(window, "data:text/html;charset=utf-8,tab-2"));
// Enqueuing asserts to make sure that assertion is not performed early.
setTimeout(function () {
assert.equal(1, opened, "observer open was called before unload only");
assert.equal(1, closed, "observer close was called before unload only");
done();
}, 0);
};
// require("test").run(exports);

View file

@ -0,0 +1,67 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 { getTabs } = require('sdk/tabs/utils');
const { isWindowPBSupported, isTabPBSupported } = require('sdk/private-browsing/utils');
const { browserWindows } = require('sdk/windows');
const tabs = require('sdk/tabs');
const { isPrivate } = require('sdk/private-browsing');
const { openTab, closeTab, getTabContentWindow, getOwnerWindow } = require('sdk/tabs/utils');
const { open, close } = require('sdk/window/helpers');
const { windows } = require('sdk/window/utils');
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
const { fromIterator } = require('sdk/util/array');
if (isWindowPBSupported) {
exports.testGetTabsPWPB = function(assert, done) {
let tabCount = getTabs().length;
let windowCount = browserWindows.length;
open(null, {
features: {
private: true,
toolbar: true,
chrome: true
}
}).then(function(window) {
assert.ok(isPrivate(window), 'new tab is private');
assert.equal(getTabs().length, tabCount, 'there are no new tabs found');
getTabs().forEach(function(tab) {
assert.equal(isPrivate(tab), false, 'all found tabs are not private');
assert.equal(isPrivate(getOwnerWindow(tab)), false, 'all found tabs are not private');
assert.equal(isPrivate(getTabContentWindow(tab)), false, 'all found tabs are not private');
});
assert.equal(browserWindows.length, windowCount, 'there are no new windows found');
fromIterator(browserWindows).forEach(function(window) {
assert.equal(isPrivate(window), false, 'all found windows are not private');
});
assert.equal(windows(null, {includePrivate: true}).length, 2, 'there are really two windows');
close(window).then(done);
});
};
}
else if (isTabPBSupported) {
exports.testGetTabsPTPB = function(assert, done) {
let startTabCount = getTabs().length;
let tab = openTab(getMostRecentBrowserWindow(), 'about:blank', {
isPrivate: true
});
assert.ok(isPrivate(getTabContentWindow(tab)), 'new tab is private');
let utils_tabs = getTabs();
assert.equal(utils_tabs.length, startTabCount + 1,
'there are two tabs found');
assert.equal(utils_tabs[utils_tabs.length-1], tab,
'the last tab is the opened tab');
assert.equal(browserWindows.length, 1, 'there is only one window');
closeTab(tab);
done();
};
}

View file

@ -0,0 +1,87 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 tabs = require("sdk/tabs"); // From addon-kit
const windowUtils = require("sdk/deprecated/window-utils");
const app = require("sdk/system/xul-app");
const { viewFor } = require("sdk/view/core");
const { modelFor } = require("sdk/model/core");
const { getTabId, isTab } = require("sdk/tabs/utils");
const { defer } = require("sdk/lang/functional");
exports["test behavior on close"] = function(assert, done) {
tabs.open({
url: "about:mozilla",
onReady: function(tab) {
assert.equal(tab.url, "about:mozilla", "Tab has the expected url");
// if another test ends before closing a tab then index != 1 here
assert.ok(tab.index >= 1, "Tab has the expected index, a value greater than 0");
tab.close(function () {
assert.equal(tab.url, undefined,
"After being closed, tab attributes are undefined (url)");
assert.equal(tab.index, undefined,
"After being closed, tab attributes are undefined (index)");
if (app.is("Firefox")) {
// Ensure that we can call destroy multiple times without throwing;
// Fennec doesn't use this internal utility
tab.destroy();
tab.destroy();
}
done();
});
}
});
};
exports["test viewFor(tab)"] = (assert, done) => {
// Note we defer handlers as length collection is updated after
// handler is invoked, so if test is finished before counnts are
// updated wrong length will show up in followup tests.
tabs.once("open", defer(tab => {
const view = viewFor(tab);
assert.ok(view, "view is returned");
assert.equal(getTabId(view), tab.id, "tab has a same id");
tab.close(defer(done));
}));
tabs.open({ url: "about:mozilla" });
};
exports["test modelFor(xulTab)"] = (assert, done) => {
tabs.open({
url: "about:mozilla",
onReady: tab => {
const view = viewFor(tab);
assert.ok(view, "view is returned");
assert.ok(isTab(view), "view is underlaying tab");
assert.equal(getTabId(view), tab.id, "tab has a same id");
assert.equal(modelFor(view), tab, "modelFor(view) is SDK tab");
tab.close(defer(done));
}
});
};
exports["test tab.readyState"] = (assert, done) => {
tabs.open({
url: "data:text/html;charset=utf-8,test_readyState",
onOpen: (tab) => {
assert.notEqual(["uninitialized", "loading"].indexOf(tab.readyState), -1,
"tab is either uninitialized or loading when onOpen");
},
onReady: (tab) => {
assert.notEqual(["interactive", "complete"].indexOf(tab.readyState), -1,
"tab is either interactive or complete when onReady");
},
onLoad: (tab) => {
assert.equal(tab.readyState, "complete", "tab is complete onLoad");
tab.close(defer(done));
}
});
}
// require("sdk/test").run(exports);

View file

@ -0,0 +1,11 @@
{
"name": "e10s-tabs",
"title": "e10s-tabs",
"id": "jid1-ZZaXFHAPlHwbgw@jetpack",
"description": "run tab tests in e10s mode",
"author": "Tomislav Jovanovic",
"license": "MPL-2.0",
"version": "0.1.0",
"main": "./lib/main.js",
"e10s": true
}

View file

@ -0,0 +1,65 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const { getMostRecentBrowserWindow, isBrowser } = require('sdk/window/utils');
const { promise: windowPromise, close, focus } = require('sdk/window/helpers');
const { openTab, closeTab, getBrowserForTab } = require('sdk/tabs/utils');
const { WindowTracker } = require('sdk/deprecated/window-utils');
const { version, platform } = require('sdk/system');
const { when } = require('sdk/system/unload');
const tabs = require('sdk/tabs');
const SKIPPING_TESTS = {
"test skip": (assert) => assert.pass("nothing to test here")
};
exports.testTabIsRemote = function(assert, done) {
const url = 'data:text/html,test-tab-is-remote';
let tab = openTab(getMostRecentBrowserWindow(), url);
assert.ok(tab.linkedBrowser.isRemoteBrowser, "The new tab should be remote");
// can't simply close a remote tab before it is loaded, bug 1006043
let mm = getBrowserForTab(tab).messageManager;
mm.addMessageListener('7', function listener() {
mm.removeMessageListener('7', listener);
tabs.once('close', done);
closeTab(tab);
})
mm.loadFrameScript('data:,sendAsyncMessage("7")', true);
}
// run e10s tests only on builds from trunk, fx-team, Nightly..
if (!version.endsWith('a1')) {
module.exports = {};
}
function replaceWindow(remote) {
let next = null;
let old = getMostRecentBrowserWindow();
let promise = new Promise(resolve => {
let tracker = WindowTracker({
onTrack: window => {
if (window !== next)
return;
resolve(window);
tracker.unload();
}
});
})
next = old.OpenBrowserWindow({ remote });
return promise.then(focus).then(_ => close(old));
}
// bug 1054482 - e10s test addons time out on linux
if (platform === 'linux') {
module.exports = SKIPPING_TESTS;
require('sdk/test/runner').runTestsFromModule(module);
}
else {
replaceWindow(true).then(_ =>
require('sdk/test/runner').runTestsFromModule(module));
when(_ => replaceWindow(false));
}

View file

@ -0,0 +1,10 @@
{
"name": "e10s",
"title": "e10s",
"id": "jid1-DYaXFHAPlHwbgw@jetpack",
"description": "a basic e10s test add-on",
"author": "Tomislav Jovanovic",
"license": "MPL-2.0",
"version": "0.1.0",
"main": "./lib/main.js"
}

View file

@ -0,0 +1,159 @@
const tabs = require("sdk/tabs");
const webExtension = require('sdk/webextension');
exports.testEmbeddedWebExtensionModuleInitializedException = function (assert) {
let actualErr;
assert.throws(
() => webExtension.initFromBootstrapAddonParam({webExtension: null}),
/'sdk\/webextension' module has been already initialized/,
"Got the expected exception if the module is initialized twice"
);
};
exports.testEmbeddedWebExtensionBackgroungPage = function* (assert) {
try {
const api = yield webExtension.startup();
assert.ok(api, `webextension waitForStartup promise successfully resolved`);
const apiSecondStartup = yield webExtension.startup();
assert.equal(api, apiSecondStartup, "Got the same API object from the second startup call");
const {browser} = api;
let messageListener;
let waitForBackgroundPageMessage = new Promise((resolve, reject) => {
let numExpectedMessage = 2;
messageListener = (msg, sender, sendReply) => {
numExpectedMessage -= 1;
if (numExpectedMessage == 1) {
assert.equal(msg, "bg->sdk message",
"Got the expected message from the background page");
sendReply("sdk reply");
} else if (numExpectedMessage == 0) {
assert.equal(msg, "sdk reply",
"The background page received the expected reply message");
resolve();
} else {
console.error("Unexpected message received", {msg,sender, numExpectedMessage});
assert.ok(false, `unexpected message received`);
reject();
}
};
browser.runtime.onMessage.addListener(messageListener);
});
let portListener;
let waitForBackgroundPagePort = new Promise((resolve, reject) => {
portListener = (port) => {
let numExpectedMessages = 2;
port.onMessage.addListener((msg) => {
numExpectedMessages -= 1;
if (numExpectedMessages == 1) {
// Check that the legacy context has been able to receive the first port message
// and reply with a port message to the background page.
assert.equal(msg, "bg->sdk port message",
"Got the expected port message from the background page");
port.postMessage("sdk->bg port message");
} else if (numExpectedMessages == 0) {
// Check that the background page has received the above port message.
assert.equal(msg, "bg received sdk->bg port message",
"The background page received the expected port message");
}
});
port.onDisconnect.addListener(() => {
assert.equal(numExpectedMessages, 0, "Got the expected number of port messages");
resolve();
});
};
browser.runtime.onConnect.addListener(portListener);
});
yield Promise.all([
waitForBackgroundPageMessage,
waitForBackgroundPagePort,
]).then(() => {
browser.runtime.onMessage.removeListener(messageListener);
browser.runtime.onConnect.removeListener(portListener);
});
} catch (err) {
assert.fail(`Unexpected webextension startup exception: ${err} - ${err.stack}`);
}
};
exports.testEmbeddedWebExtensionContentScript = function* (assert, done) {
try {
const {browser} = yield webExtension.startup();
assert.ok(browser, `webextension startup promise resolved successfully to the API object`);
let messageListener;
let waitForContentScriptMessage = new Promise((resolve, reject) => {
let numExpectedMessage = 2;
messageListener = (msg, sender, sendReply) => {
numExpectedMessage -= 1;
if (numExpectedMessage == 1) {
assert.equal(msg, "content script->sdk message",
"Got the expected message from the content script");
sendReply("sdk reply");
} else if (numExpectedMessage == 0) {
assert.equal(msg, "sdk reply",
"The content script received the expected reply message");
resolve();
} else {
console.error("Unexpected message received", {msg,sender, numExpectedMessage});
assert.ok(false, `unexpected message received`);
reject();
}
};
browser.runtime.onMessage.addListener(messageListener);
});
let portListener;
let waitForContentScriptPort = new Promise((resolve, reject) => {
portListener = (port) => {
let numExpectedMessages = 2;
port.onMessage.addListener((msg) => {
numExpectedMessages -= 1;
if (numExpectedMessages == 1) {
assert.equal(msg, "content script->sdk port message",
"Got the expected message from the content script port");
port.postMessage("sdk->content script port message");
} else if (numExpectedMessages == 0) {
assert.equal(msg, "content script received sdk->content script port message",
"The content script received the expected port message");
}
});
port.onDisconnect.addListener(() => {
assert.equal(numExpectedMessages, 0, "Got the epected number of port messages");
resolve();
});
};
browser.runtime.onConnect.addListener(portListener);
});
let url = "http://example.org/";
var openedTab;
tabs.once('open', function onOpen(tab) {
openedTab = tab;
});
tabs.open(url);
yield Promise.all([
waitForContentScriptMessage,
waitForContentScriptPort,
]).then(() => {
browser.runtime.onMessage.removeListener(messageListener);
browser.runtime.onConnect.removeListener(portListener);
openedTab.close();
});
} catch (err) {
assert.fail(`Unexpected webextension startup exception: ${err} - ${err.stack}`);
}
};
require("sdk/test/runner").runTestsFromModule(module);

View file

@ -0,0 +1,6 @@
{
"id": "embedded-webextension@jetpack",
"version": "0.1.0",
"main": "./main.js",
"hasEmbeddedWebExtension": true
}

View file

@ -0,0 +1,10 @@
browser.runtime.sendMessage("bg->sdk message", (reply) => {
browser.runtime.sendMessage(reply);
});
let port = browser.runtime.connect();
port.onMessage.addListener((msg) => {
port.postMessage(`bg received ${msg}`);
port.disconnect();
});
port.postMessage("bg->sdk port message");

View file

@ -0,0 +1,10 @@
browser.runtime.sendMessage("content script->sdk message", (reply) => {
browser.runtime.sendMessage(reply);
});
let port = browser.runtime.connect();
port.onMessage.addListener((msg) => {
port.postMessage(`content script received ${msg}`);
port.disconnect();
});
port.postMessage("content script->sdk port message");

View file

@ -0,0 +1,18 @@
{
"name": "Test SDK Embedded WebExtension",
"description": "",
"version": "0.1.0",
"applications": {
"gecko": {
"id": "embedded-webextension@jetpack"
}
},
"manifest_version": 2,
"permissions": ["tabs"],
"background": {
"scripts": ["background-page.js"]
},
"content_scripts": [
{"matches": ["<all_urls>"], "js": ["content-script.js"]}
]
}

View file

@ -0,0 +1,48 @@
[addon-manager.xpi]
[author-email.xpi]
[child_process.xpi]
[chrome.xpi]
[content-permissions.xpi]
[content-script-messages-latency.xpi]
[contributors.xpi]
[curly-id.xpi]
[developers.xpi]
[e10s.xpi]
skip-if = true
[e10s-content.xpi]
skip-if = true
[e10s-l10n.xpi]
skip-if = true
[e10s-remote.xpi]
skip-if = true
[e10s-tabs.xpi]
skip-if = true
[embedded-webextension.xpi]
[l10n.xpi]
[l10n-properties.xpi]
[layout-change.xpi]
[main.xpi]
[name-in-numbers.xpi]
[name-in-numbers-plus.xpi]
[packaging.xpi]
[packed.xpi]
[page-mod-debugger-post.xpi]
[page-mod-debugger-pre.xpi]
[page-worker.xpi]
skip-if = true # Bug 1288619 and Bug 1288708
[places.xpi]
[predefined-id-with-at.xpi]
[preferences-branch.xpi]
[private-browsing-supported.xpi]
skip-if = true
[remote.xpi]
[require.xpi]
[self.xpi]
[simple-prefs.xpi]
[simple-prefs-l10n.xpi]
[simple-prefs-regression.xpi]
[standard-id.xpi]
[tab-close-on-startup.xpi]
[toolkit-require-reload.xpi]
[translators.xpi]
[unsafe-content-script.xpi]

View file

@ -0,0 +1,11 @@
[App]
Vendor=Varma
Name=Test App
Version=1.0
BuildID=20060101
Copyright=Copyright (c) 2009 Atul Varma
ID=xulapp@toolness.com
[Gecko]
MinVersion=1.9.2.0
MaxVersion=2.0.*

View file

@ -0,0 +1,339 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// @see http://dxr.mozilla.org/mozilla-central/source/js/src/xpconnect/loader/mozJSComponentLoader.cpp
'use strict';
// IMPORTANT: Avoid adding any initialization tasks here, if you need to do
// something before add-on is loaded consider addon/runner module instead!
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu,
results: Cr, manager: Cm } = Components;
const ioService = Cc['@mozilla.org/network/io-service;1'].
getService(Ci.nsIIOService);
const resourceHandler = ioService.getProtocolHandler('resource').
QueryInterface(Ci.nsIResProtocolHandler);
const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1'].
getService(Ci.mozIJSSubScriptLoader);
const prefService = Cc['@mozilla.org/preferences-service;1'].
getService(Ci.nsIPrefService).
QueryInterface(Ci.nsIPrefBranch);
const appInfo = Cc["@mozilla.org/xre/app-info;1"].
getService(Ci.nsIXULAppInfo);
const vc = Cc["@mozilla.org/xpcom/version-comparator;1"].
getService(Ci.nsIVersionComparator);
const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm");
const REASON = [ 'unknown', 'startup', 'shutdown', 'enable', 'disable',
'install', 'uninstall', 'upgrade', 'downgrade' ];
const bind = Function.call.bind(Function.bind);
var loader = null;
var unload = null;
var cuddlefishSandbox = null;
var nukeTimer = null;
// Utility function that synchronously reads local resource from the given
// `uri` and returns content string.
function readURI(uri) {
let channel = NetUtil.newChannel({
uri: NetUtil.newURI(uri, "UTF-8"),
loadUsingSystemPrincipal: true
});
let stream = channel.open2();
let cstream = Cc['@mozilla.org/intl/converter-input-stream;1'].
createInstance(Ci.nsIConverterInputStream);
cstream.init(stream, 'UTF-8', 0, 0);
let str = {};
let data = '';
let read = 0;
do {
read = cstream.readString(0xffffffff, str);
data += str.value;
} while (read != 0);
cstream.close();
return data;
}
// We don't do anything on install & uninstall yet, but in a future
// we should allow add-ons to cleanup after uninstall.
function install(data, reason) {}
function uninstall(data, reason) {}
function startup(data, reasonCode) {
try {
let reason = REASON[reasonCode];
// URI for the root of the XPI file.
// 'jar:' URI if the addon is packed, 'file:' URI otherwise.
// (Used by l10n module in order to fetch `locale` folder)
let rootURI = data.resourceURI.spec;
// TODO: Maybe we should perform read harness-options.json asynchronously,
// since we can't do anything until 'sessionstore-windows-restored' anyway.
let options = JSON.parse(readURI(rootURI + './harness-options.json'));
let id = options.jetpackID;
let name = options.name;
// Clean the metadata
options.metadata[name]['permissions'] = options.metadata[name]['permissions'] || {};
// freeze the permissionss
Object.freeze(options.metadata[name]['permissions']);
// freeze the metadata
Object.freeze(options.metadata[name]);
// Register a new resource 'domain' for this addon which is mapping to
// XPI's `resources` folder.
// Generate the domain name by using jetpack ID, which is the extension ID
// by stripping common characters that doesn't work as a domain name:
let uuidRe =
/^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/;
let domain = id.
toLowerCase().
replace(/@/g, '-at-').
replace(/\./g, '-dot-').
replace(uuidRe, '$1');
let prefixURI = 'resource://' + domain + '/';
let resourcesURI = ioService.newURI(rootURI + '/resources/', null, null);
resourceHandler.setSubstitution(domain, resourcesURI);
// Create path to URLs mapping supported by loader.
let paths = {
// Relative modules resolve to add-on package lib
'./': prefixURI + name + '/lib/',
'./tests/': prefixURI + name + '/tests/',
'': 'resource://gre/modules/commonjs/'
};
// Maps addon lib and tests ressource folders for each package
paths = Object.keys(options.metadata).reduce(function(result, name) {
result[name + '/'] = prefixURI + name + '/lib/'
result[name + '/tests/'] = prefixURI + name + '/tests/'
return result;
}, paths);
// We need to map tests folder when we run sdk tests whose package name
// is stripped
if (name == 'addon-sdk')
paths['tests/'] = prefixURI + name + '/tests/';
let useBundledSDK = options['force-use-bundled-sdk'];
if (!useBundledSDK) {
try {
useBundledSDK = prefService.getBoolPref("extensions.addon-sdk.useBundledSDK");
}
catch (e) {
// Pref doesn't exist, allow using Firefox shipped SDK
}
}
// Starting with Firefox 21.0a1, we start using modules shipped into firefox
// Still allow using modules from the xpi if the manifest tell us to do so.
// And only try to look for sdk modules in xpi if the xpi actually ship them
if (options['is-sdk-bundled'] &&
(vc.compare(appInfo.version, '21.0a1') < 0 || useBundledSDK)) {
// Maps sdk module folders to their resource folder
paths[''] = prefixURI + 'addon-sdk/lib/';
// test.js is usually found in root commonjs or SDK_ROOT/lib/ folder,
// so that it isn't shipped in the xpi. Keep a copy of it in sdk/ folder
// until we no longer support SDK modules in XPI:
paths['test'] = prefixURI + 'addon-sdk/lib/sdk/test.js';
}
// Retrieve list of module folder overloads based on preferences in order to
// eventually used a local modules instead of files shipped into Firefox.
let branch = prefService.getBranch('extensions.modules.' + id + '.path');
paths = branch.getChildList('', {}).reduce(function (result, name) {
// Allows overloading of any sub folder by replacing . by / in pref name
let path = name.substr(1).split('.').join('/');
// Only accept overloading folder by ensuring always ending with `/`
if (path) path += '/';
let fileURI = branch.getCharPref(name);
// On mobile, file URI has to end with a `/` otherwise, setSubstitution
// takes the parent folder instead.
if (fileURI[fileURI.length-1] !== '/')
fileURI += '/';
// Maps the given file:// URI to a resource:// in order to avoid various
// failure that happens with file:// URI and be close to production env
let resourcesURI = ioService.newURI(fileURI, null, null);
let resName = 'extensions.modules.' + domain + '.commonjs.path' + name;
resourceHandler.setSubstitution(resName, resourcesURI);
result[path] = 'resource://' + resName + '/';
return result;
}, paths);
// Make version 2 of the manifest
let manifest = options.manifest;
// Import `cuddlefish.js` module using a Sandbox and bootstrap loader.
let cuddlefishPath = 'loader/cuddlefish.js';
let cuddlefishURI = 'resource://gre/modules/commonjs/sdk/' + cuddlefishPath;
if (paths['sdk/']) { // sdk folder has been overloaded
// (from pref, or cuddlefish is still in the xpi)
cuddlefishURI = paths['sdk/'] + cuddlefishPath;
}
else if (paths['']) { // root modules folder has been overloaded
cuddlefishURI = paths[''] + 'sdk/' + cuddlefishPath;
}
cuddlefishSandbox = loadSandbox(cuddlefishURI);
let cuddlefish = cuddlefishSandbox.exports;
// Normalize `options.mainPath` so that it looks like one that will come
// in a new version of linker.
let main = options.mainPath;
unload = cuddlefish.unload;
loader = cuddlefish.Loader({
paths: paths,
// modules manifest.
manifest: manifest,
// Add-on ID used by different APIs as a unique identifier.
id: id,
// Add-on name.
name: name,
// Add-on version.
version: options.metadata[name].version,
// Add-on package descriptor.
metadata: options.metadata[name],
// Add-on load reason.
loadReason: reason,
prefixURI: prefixURI,
// Add-on URI.
rootURI: rootURI,
// options used by system module.
// File to write 'OK' or 'FAIL' (exit code emulation).
resultFile: options.resultFile,
// Arguments passed as --static-args
staticArgs: options.staticArgs,
// Arguments related to test runner.
modules: {
'@test/options': {
allTestModules: options.allTestModules,
iterations: options.iterations,
filter: options.filter,
profileMemory: options.profileMemory,
stopOnError: options.stopOnError,
verbose: options.verbose,
parseable: options.parseable,
checkMemory: options.check_memory,
}
}
});
let module = cuddlefish.Module('sdk/loader/cuddlefish', cuddlefishURI);
let require = cuddlefish.Require(loader, module);
require('sdk/addon/runner').startup(reason, {
loader: loader,
main: main,
prefsURI: rootURI + 'defaults/preferences/prefs.js'
});
} catch (error) {
dump('Bootstrap error: ' +
(error.message ? error.message : String(error)) + '\n' +
(error.stack || error.fileName + ': ' + error.lineNumber) + '\n');
throw error;
}
};
function loadSandbox(uri) {
let proto = {
sandboxPrototype: {
loadSandbox: loadSandbox,
ChromeWorker: ChromeWorker
}
};
let sandbox = Cu.Sandbox(systemPrincipal, proto);
// Create a fake commonjs environnement just to enable loading loader.js
// correctly
sandbox.exports = {};
sandbox.module = { uri: uri, exports: sandbox.exports };
sandbox.require = function (id) {
if (id !== "chrome")
throw new Error("Bootstrap sandbox `require` method isn't implemented.");
return Object.freeze({ Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm,
CC: bind(CC, Components), components: Components,
ChromeWorker: ChromeWorker });
};
scriptLoader.loadSubScript(uri, sandbox, 'UTF-8');
return sandbox;
}
function unloadSandbox(sandbox) {
if (Cu.getClassName(sandbox, true) == "Sandbox")
Cu.nukeSandbox(sandbox);
}
function setTimeout(callback, delay) {
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback({ notify: callback }, delay,
Ci.nsITimer.TYPE_ONE_SHOT);
return timer;
}
function shutdown(data, reasonCode) {
let reason = REASON[reasonCode];
if (loader) {
unload(loader, reason);
unload = null;
// Don't waste time cleaning up if the application is shutting down
if (reason != "shutdown") {
// Avoid leaking all modules when something goes wrong with one particular
// module. Do not clean it up immediatly in order to allow executing some
// actions on addon disabling.
// We need to keep a reference to the timer, otherwise it is collected
// and won't ever fire.
nukeTimer = setTimeout(nukeModules, 1000);
}
}
};
function nukeModules() {
nukeTimer = null;
// module objects store `exports` which comes from sandboxes
// We should avoid keeping link to these object to avoid leaking sandboxes
for (let key in loader.modules) {
delete loader.modules[key];
}
// Direct links to sandboxes should be removed too
for (let key in loader.sandboxes) {
let sandbox = loader.sandboxes[key];
delete loader.sandboxes[key];
// Bug 775067: From FF17 we can kill all CCW from a given sandbox
unloadSandbox(sandbox);
}
loader = null;
// both `toolkit/loader` and `system/xul-app` are loaded as JSM's via
// `cuddlefish.js`, and needs to be unloaded to avoid memory leaks, when
// the addon is unload.
unloadSandbox(cuddlefishSandbox.loaderSandbox);
unloadSandbox(cuddlefishSandbox.xulappSandbox);
// Bug 764840: We need to unload cuddlefish otherwise it will stay alive
// and keep a reference to this compartment.
unloadSandbox(cuddlefishSandbox);
cuddlefishSandbox = null;
}

View file

@ -0,0 +1,33 @@
<?xml version="1.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/. -->
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>xulapp@toolness.com</em:id>
<em:version>1.0</em:version>
<em:type>2</em:type>
<em:bootstrap>true</em:bootstrap>
<em:unpack>false</em:unpack>
<!-- Firefox -->
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>21.0</em:minVersion>
<em:maxVersion>25.0a1</em:maxVersion>
</Description>
</em:targetApplication>
<!-- Front End MetaData -->
<em:name>Test App</em:name>
<em:description>Harness for tests.</em:description>
<em:creator>Mozilla Corporation</em:creator>
<em:homepageURL></em:homepageURL>
<em:optionsType></em:optionsType>
<em:updateURL></em:updateURL>
</Description>
</RDF>

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/.
Translated= Yes
text-content=no <b>HTML</b> injection
downloadsCount=%d downloads
downloadsCount[one]=one download
pluralTest=fallback to other
pluralTest[zero]=optional zero form
explicitPlural[one]=one
explicitPlural[other]=other
# You can use unicode char escaping in order to inject space at the beginning/
# end of your string. (Regular spaces are automatically ignore by .properties
# file parser)
unicodeEscape = \u0020\u0040\u0020
# this string equals to " @ "
# bug 1033309 plurals with multiple placeholders
first_identifier[one]=first entry is %s and the second one is %s.
first_identifier=the entries are %s and %s.
second_identifier[other]=first entry is %s and the second one is %s.
third_identifier=first entry is %s and the second one is %s.

View file

@ -0,0 +1,22 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Translated= Yes
text-content=no <b>HTML</b> injection
downloadsCount=%d downloads
downloadsCount[one]=one download
pluralTest=fallback to other
pluralTest[zero]=optional zero form
explicitPlural[one]=one
explicitPlural[other]=other
# You can use unicode char escaping in order to inject space at the beginning/
# end of your string. (Regular spaces are automatically ignore by .properties
# file parser)
unicodeEscape = \u0020\u0040\u0020
# this string equals to " @ "

View file

@ -0,0 +1,5 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Translated= jes

View file

@ -0,0 +1,14 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Translated= Oui
placeholderString= Placeholder %s
# Plural forms
%d downloads=%d téléchargements
%d downloads[one]=%d téléchargement
downloadsCount=%d téléchargements
downloadsCount[one]=%d téléchargement

View file

@ -0,0 +1,24 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html>
<head>
<meta charset="UTF-8">
<title>HTML Localization</title>
</head>
<body>
<div data-l10n-id="Not translated">Kept as-is</div>
<ul data-l10n-id="Translated">
<li>Inner html content is replaced,</li>
<li data-l10n-id="text-content">
Elements with data-l10n-id attribute whose parent element is translated
will be replaced by the content of the translation.
</li>
</ul>
<div data-l10n-id="text-content">No</div>
<div data-l10n-id="Translated">
A data-l10n-id value can be used in multiple elements
</div>
</body>
</html

View file

@ -0,0 +1,202 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const prefs = require("sdk/preferences/service");
const { Loader } = require('sdk/test/loader');
const { resolveURI } = require('toolkit/loader');
const { rootURI } = require("@loader/options");
const { usingJSON } = require('sdk/l10n/json/core');
const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
const PREF_SELECTED_LOCALE = "general.useragent.locale";
function setLocale(locale) {
prefs.set(PREF_MATCH_OS_LOCALE, false);
prefs.set(PREF_SELECTED_LOCALE, locale);
}
function resetLocale() {
prefs.reset(PREF_MATCH_OS_LOCALE);
prefs.reset(PREF_SELECTED_LOCALE);
}
function definePseudo(loader, id, exports) {
let uri = resolveURI(id, loader.mapping);
loader.modules[uri] = { exports: exports };
}
function createTest(locale, testFunction) {
return function (assert, done) {
let loader = Loader(module);
// Change the locale before loading new l10n modules in order to load
// the right .json file
setLocale(locale);
// Initialize main l10n module in order to load new locale files
loader.require("sdk/l10n/loader").
load(rootURI).
then(function success(data) {
definePseudo(loader, '@l10n/data', data);
// Execute the given test function
try {
testFunction(assert, loader, function onDone() {
loader.unload();
resetLocale();
done();
});
}
catch(e) {
console.exception(e);
}
},
function failure(error) {
assert.fail("Unable to load locales: " + error);
});
};
}
exports.testExactMatching = createTest("fr-FR", function(assert, loader, done) {
let _ = loader.require("sdk/l10n").get;
assert.equal(_("Not translated"), "Not translated",
"Key not translated");
assert.equal(_("Translated"), "Oui",
"Simple key translated");
// Placeholders
assert.equal(_("placeholderString", "works"), "Placeholder works",
"Value with placeholder");
assert.equal(_("Placeholder %s", "works"), "Placeholder works",
"Key without value but with placeholder");
assert.equal(_("Placeholders %2s %1s %s.", "working", "are", "correctly"),
"Placeholders are working correctly.",
"Multiple placeholders");
// Plurals
assert.equal(_("downloadsCount", 0),
"0 téléchargement",
"PluralForm form 'one' for 0 in french");
assert.equal(_("downloadsCount", 1),
"1 téléchargement",
"PluralForm form 'one' for 1 in french");
assert.equal(_("downloadsCount", 2),
"2 téléchargements",
"PluralForm form 'other' for n > 1 in french");
done();
});
exports.testHtmlLocalization = createTest("en-GB", function(assert, loader, done) {
// Ensure initing html component that watch document creations
// Note that this module is automatically initialized in
// cuddlefish.js:Loader.main in regular addons. But it isn't for unit tests.
let loaderHtmlL10n = loader.require("sdk/l10n/html");
loaderHtmlL10n.enable();
let uri = require("sdk/self").data.url("test-localization.html");
let worker = loader.require("sdk/page-worker").Page({
contentURL: uri,
contentScript: "new " + function ContentScriptScope() {
let nodes = document.body.querySelectorAll("*[data-l10n-id]");
self.postMessage([nodes[0].innerHTML,
nodes[1].innerHTML,
nodes[2].innerHTML,
nodes[3].innerHTML]);
},
onMessage: function (data) {
assert.equal(
data[0],
"Kept as-is",
"Nodes with unknown id in .properties are kept 'as-is'"
);
assert.equal(data[1], "Yes", "HTML is translated");
assert.equal(
data[2],
"no &lt;b&gt;HTML&lt;/b&gt; injection",
"Content from .properties is text content; HTML can't be injected."
);
assert.equal(data[3], "Yes", "Multiple elements with same data-l10n-id are accepted.");
done();
}
});
});
exports.testEnUsLocaleName = createTest("en-GB", function(assert, loader, done) {
let _ = loader.require("sdk/l10n").get;
assert.equal(_("Not translated"), "Not translated",
"String w/o translation is kept as-is");
assert.equal(_("Translated"), "Yes",
"String with translation is correctly translated");
// Check Unicode char escaping sequences
assert.equal(_("unicodeEscape"), " @ ",
"Unicode escaped sequances are correctly converted");
// Check plural forms regular matching
assert.equal(_("downloadsCount", 0),
"0 downloads",
"PluralForm form 'other' for 0 in english");
assert.equal(_("downloadsCount", 1),
"one download",
"PluralForm form 'one' for 1 in english");
assert.equal(_("downloadsCount", 2),
"2 downloads",
"PluralForm form 'other' for n != 1 in english");
// Check optional plural forms
assert.equal(_("pluralTest", 0),
"optional zero form",
"PluralForm form 'zero' can be optionaly specified. (Isn't mandatory in english)");
assert.equal(_("pluralTest", 1),
"fallback to other",
"If the specific plural form is missing, we fallback to 'other'");
// Ensure that we can omit specifying the generic key without [other]
// key[one] = ...
// key[other] = ... # Instead of `key = ...`
assert.equal(_("explicitPlural", 1),
"one",
"PluralForm form can be omitting generic key [i.e. without ...[other] at end of key)");
assert.equal(_("explicitPlural", 10),
"other",
"PluralForm form can be omitting generic key [i.e. without ...[other] at end of key)");
assert.equal(_("first_identifier", "ONE", "TWO"), "the entries are ONE and TWO.", "first_identifier no count");
assert.equal(_("first_identifier", 0, "ONE", "TWO"), "the entries are ONE and TWO.", "first_identifier with count = 0");
assert.equal(_("first_identifier", 1, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "first_identifier with count = 1");
assert.equal(_("first_identifier", 2, "ONE", "TWO"), "the entries are ONE and TWO.", "first_identifier with count = 2");
assert.equal(_("second_identifier", "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with no count");
assert.equal(_("second_identifier", 0, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with count = 0");
assert.equal(_("second_identifier", 1, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with count = 1");
assert.equal(_("second_identifier", 2, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with count = 2");
assert.equal(_("third_identifier", "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "third_identifier with no count");
assert.equal(_("third_identifier", 0, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "third_identifier with count = 0");
assert.equal(_("third_identifier", 2, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "third_identifier with count = 2");
done();
});
exports.testUsingJSON = function(assert) {
assert.equal(usingJSON, false, 'not using json');
}
exports.testShortLocaleName = createTest("eo", function(assert, loader, done) {
let _ = loader.require("sdk/l10n").get;
assert.equal(_("Not translated"), "Not translated",
"String w/o translation is kept as-is");
assert.equal(_("Translated"), "jes",
"String with translation is correctly translated");
done();
});
// Before running tests, disable HTML service which is automatially enabled
// in api-utils/addon/runner.js
require('sdk/l10n/html').disable();
require("sdk/test/runner").runTestsFromModule(module);

View file

@ -0,0 +1,6 @@
{
"id": "test-l10n@jetpack",
"title": "Test L10n",
"main": "./main.js",
"version": "0.0.1"
}

View file

@ -0,0 +1,29 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html>
<head>
<meta charset="UTF-8">
<title>HTML Localization</title>
</head>
<body>
<div data-l10n-id="Not translated">Kept as-is</div>
<ul data-l10n-id="Translated">
<li>Inner html content is replaced,</li>
<li data-l10n-id="text-content">
Elements with data-l10n-id attribute whose parent element is translated
will be replaced by the content of the translation.
</li>
</ul>
<div data-l10n-id="text-content">No</div>
<div data-l10n-id="Translated">
A data-l10n-id value can be used in multiple elements
</div>
<a data-l10n-id="link-attributes" title="Certain whitelisted attributes get translated too" alt="No" accesskey="A"></a>
<input data-l10n-id="input" type="text" placeholder="Form placeholders are translateable">
<menu>
<menuitem data-l10n-id="contextitem" label="Labels of select options and context menus are translateable">
</menu>
</body>
</html

View file

@ -0,0 +1,38 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Translated= Yes
text-content=no <b>HTML</b> injection
downloadsCount=%d downloads
downloadsCount[one]=one download
pluralTest=fallback to other
pluralTest[zero]=optional zero form
explicitPlural[one]=one
explicitPlural[other]=other
# You can use unicode char escaping in order to inject space at the beginning/
# end of your string. (Regular spaces are automatically ignore by .properties
# file parser)
unicodeEscape = \u0020\u0040\u0020
# this string equals to " @ "
# bug 1033309 plurals with multiple placeholders
first_identifier[one]=first entry is %s and the second one is %s.
first_identifier=the entries are %s and %s.
second_identifier[other]=first entry is %s and the second one is %s.
third_identifier=first entry is %s and the second one is %s.
# bug 824489 allow translation of element attributes
link-attributes.title=Yes
link-attributes.alt=Yes
link-attributes.accesskey=B
input.placeholder=Yes
contextitem.label=Yes
link-attributes.ariaLabel=Yes
link-attributes.ariaValueText=Value
link-attributes.ariaMozHint=Hint

View file

@ -0,0 +1,5 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Translated= jes

View file

@ -0,0 +1,14 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Translated= Oui
placeholderString= Placeholder %s
# Plural forms
%d downloads=%d téléchargements
%d downloads[one]=%d téléchargement
downloadsCount=%d téléchargements
downloadsCount[one]=%d téléchargement

View file

@ -0,0 +1,289 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const prefs = require("sdk/preferences/service");
const { Loader } = require('sdk/test/loader');
const { resolveURI } = require('toolkit/loader');
const { rootURI, isNative } = require("@loader/options");
const { usingJSON } = require('sdk/l10n/json/core');
const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
const PREF_SELECTED_LOCALE = "general.useragent.locale";
function setLocale(locale) {
prefs.set(PREF_MATCH_OS_LOCALE, false);
prefs.set(PREF_SELECTED_LOCALE, locale);
}
function resetLocale() {
prefs.reset(PREF_MATCH_OS_LOCALE);
prefs.reset(PREF_SELECTED_LOCALE);
}
function definePseudo(loader, id, exports) {
let uri = resolveURI(id, loader.mapping);
loader.modules[uri] = { exports: exports };
}
function createTest(locale, testFunction) {
return function (assert, done) {
let loader = Loader(module);
// Change the locale before loading new l10n modules in order to load
// the right .json file
setLocale(locale);
// Initialize main l10n module in order to load new locale files
loader.require("sdk/l10n/loader").
load(rootURI).
then(null, function failure(error) {
if (!isNative)
assert.fail("Unable to load locales: " + error);
}).
then(function success(data) {
definePseudo(loader, '@l10n/data', data ? data : null);
// Execute the given test function
try {
testFunction(assert, loader, function onDone() {
loader.unload();
resetLocale();
done();
});
}
catch(e) {
console.exception(e);
}
},
function failure(error) {
assert.fail("Unable to load locales: " + error);
});
};
}
exports.testExactMatching = createTest("fr-FR", function(assert, loader, done) {
let _ = loader.require("sdk/l10n").get;
assert.equal(_("Not translated"), "Not translated",
"Key not translated");
assert.equal(_("Translated"), "Oui",
"Simple key translated");
// Placeholders
assert.equal(_("placeholderString", "works"), "Placeholder works",
"Value with placeholder");
assert.equal(_("Placeholder %s", "works"), "Placeholder works",
"Key without value but with placeholder");
assert.equal(_("Placeholders %2s %1s %s.", "working", "are", "correctly"),
"Placeholders are working correctly.",
"Multiple placeholders");
// Plurals
assert.equal(_("downloadsCount", 0),
"0 téléchargement",
"PluralForm form 'one' for 0 in french");
assert.equal(_("downloadsCount", 1),
"1 téléchargement",
"PluralForm form 'one' for 1 in french");
assert.equal(_("downloadsCount", 2),
"2 téléchargements",
"PluralForm form 'other' for n > 1 in french");
done();
});
exports.testHtmlLocalizationPageWorker = createTest("en-GB", function(assert, loader, done) {
// Ensure initing html component that watch document creations
// Note that this module is automatically initialized in
// cuddlefish.js:Loader.main in regular addons. But it isn't for unit tests.
let loaderHtmlL10n = loader.require("sdk/l10n/html");
loaderHtmlL10n.enable();
let uri = require("sdk/self").data.url("test-localization.html");
let worker = loader.require("sdk/page-worker").Page({
contentURL: uri,
contentScript: "new " + function ContentScriptScope() {
let nodes = document.body.querySelectorAll("*[data-l10n-id]");
self.postMessage([nodes[0].innerHTML,
nodes[1].innerHTML,
nodes[2].innerHTML,
nodes[3].innerHTML,
nodes[4].title,
nodes[4].getAttribute("alt"),
nodes[4].getAttribute("accesskey"),
nodes[4].getAttribute("aria-label"),
nodes[4].getAttribute("aria-valuetext"),
nodes[4].getAttribute("aria-moz-hint"),
nodes[5].placeholder,
nodes[6].label]);
},
onMessage: function (data) {
assert.equal(
data[0],
"Kept as-is",
"Nodes with unknown id in .properties are kept 'as-is'"
);
assert.equal(data[1], "Yes", "HTML is translated");
assert.equal(
data[2],
"no &lt;b&gt;HTML&lt;/b&gt; injection",
"Content from .properties is text content; HTML can't be injected."
);
assert.equal(data[3], "Yes", "Multiple elements with same data-l10n-id are accepted.");
// Attribute translation tests
assert.equal(data[4], "Yes", "Title attributes gets translated.");
assert.equal(data[5], "Yes", "Alt attributes gets translated.");
assert.equal(data[6], "B", "Accesskey gets translated.");
assert.equal(data[7], "Yes", "Aria-Label gets translated.");
assert.equal(data[8], "Value", "Aria-valuetext gets translated.");
assert.equal(data[9], "Hint", "Aria-moz-hint gets translated.");
assert.equal(data[10], "Yes", "Form placeholders are translateable.");
assert.equal(data[11], "Yes", "Labels of select options and context menus are translateable.");
done();
}
});
});
exports.testHtmlLocalization = createTest("en-GB", function(assert, loader, done) {
// Ensure initing html component that watch document creations
// Note that this module is automatically initialized in
// cuddlefish.js:Loader.main in regular addons. But it isn't for unit tests.
let loaderHtmlL10n = loader.require("sdk/l10n/html");
loaderHtmlL10n.enable();
let uri = require("sdk/self").data.url("test-localization.html");
loader.require("sdk/tabs").open({
url: uri,
onReady: function(tab) {
tab.attach({
contentURL: uri,
contentScript: "new " + function ContentScriptScope() {
let nodes = document.body.querySelectorAll("*[data-l10n-id]");
self.postMessage([nodes[0].innerHTML,
nodes[1].innerHTML,
nodes[2].innerHTML,
nodes[3].innerHTML,
nodes[4].title,
nodes[4].getAttribute("alt"),
nodes[4].getAttribute("accesskey"),
nodes[4].getAttribute("aria-label"),
nodes[4].getAttribute("aria-valuetext"),
nodes[4].getAttribute("aria-moz-hint"),
nodes[5].placeholder,
nodes[6].label]);
},
onMessage: function (data) {
assert.equal(
data[0],
"Kept as-is",
"Nodes with unknown id in .properties are kept 'as-is'"
);
assert.equal(data[1], "Yes", "HTML is translated");
assert.equal(
data[2],
"no &lt;b&gt;HTML&lt;/b&gt; injection",
"Content from .properties is text content; HTML can't be injected."
);
assert.equal(data[3], "Yes", "Multiple elements with same data-l10n-id are accepted.");
// Attribute translation tests
assert.equal(data[4], "Yes", "Title attributes gets translated.");
assert.equal(data[5], "Yes", "Alt attributes gets translated.");
assert.equal(data[6], "B", "Accesskey gets translated.");
assert.equal(data[7], "Yes", "Aria-Label gets translated.");
assert.equal(data[8], "Value", "Aria-valuetext gets translated.");
assert.equal(data[9], "Hint", "Aria-moz-hint gets translated.");
assert.equal(data[10], "Yes", "Form placeholders are translateable.");
assert.equal(data[11], "Yes", "Labels of select options and context menus are translateable.");
tab.close(done);
}
});
}
});
});
exports.testEnUsLocaleName = createTest("en-US", function(assert, loader, done) {
let _ = loader.require("sdk/l10n").get;
assert.equal(_("Not translated"), "Not translated",
"String w/o translation is kept as-is");
assert.equal(_("Translated"), "Yes",
"String with translation is correctly translated");
// Check Unicode char escaping sequences
assert.equal(_("unicodeEscape"), " @ ",
"Unicode escaped sequances are correctly converted");
// Check plural forms regular matching
assert.equal(_("downloadsCount", 0),
"0 downloads",
"PluralForm form 'other' for 0 in english");
assert.equal(_("downloadsCount", 1),
"one download",
"PluralForm form 'one' for 1 in english");
assert.equal(_("downloadsCount", 2),
"2 downloads",
"PluralForm form 'other' for n != 1 in english");
// Check optional plural forms
assert.equal(_("pluralTest", 0),
"optional zero form",
"PluralForm form 'zero' can be optionaly specified. (Isn't mandatory in english)");
assert.equal(_("pluralTest", 1),
"fallback to other",
"If the specific plural form is missing, we fallback to 'other'");
// Ensure that we can omit specifying the generic key without [other]
// key[one] = ...
// key[other] = ... # Instead of `key = ...`
assert.equal(_("explicitPlural", 1),
"one",
"PluralForm form can be omitting generic key [i.e. without ...[other] at end of key)");
assert.equal(_("explicitPlural", 10),
"other",
"PluralForm form can be omitting generic key [i.e. without ...[other] at end of key)");
assert.equal(_("first_identifier", "ONE", "TWO"), "the entries are ONE and TWO.", "first_identifier no count");
assert.equal(_("first_identifier", 0, "ONE", "TWO"), "the entries are ONE and TWO.", "first_identifier with count = 0");
assert.equal(_("first_identifier", 1, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "first_identifier with count = 1");
assert.equal(_("first_identifier", 2, "ONE", "TWO"), "the entries are ONE and TWO.", "first_identifier with count = 2");
assert.equal(_("second_identifier", "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with no count");
assert.equal(_("second_identifier", 0, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with count = 0");
assert.equal(_("second_identifier", 1, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with count = 1");
assert.equal(_("second_identifier", 2, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with count = 2");
assert.equal(_("third_identifier", "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "third_identifier with no count");
assert.equal(_("third_identifier", 0, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "third_identifier with count = 0");
assert.equal(_("third_identifier", 2, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "third_identifier with count = 2");
done();
});
exports.testUsingJSON = function(assert) {
assert.equal(usingJSON, !isNative, 'using json');
}
exports.testShortLocaleName = createTest("eo", function(assert, loader, done) {
let _ = loader.require("sdk/l10n").get;
assert.equal(_("Not translated"), "Not translated",
"String w/o translation is kept as-is");
assert.equal(_("Translated"), "jes",
"String with translation is correctly translated");
done();
});
// Before running tests, disable HTML service which is automatially enabled
// in api-utils/addon/runner.js
require('sdk/l10n/html').disable();
require("sdk/test/runner").runTestsFromModule(module);

View file

@ -0,0 +1,5 @@
{
"id": "test-l10n@jetpack",
"main": "./main.js",
"version": "0.0.1"
}

View file

@ -0,0 +1,15 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { isNative } = require("@loader/options");
if (isNative) {
module.exports = require("./test-toolkit-loader");
}
else {
module.exports = require("./test-cuddlefish-loader");
}
require("sdk/test/runner").runTestsFromModule(module);

View file

@ -0,0 +1,164 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 { LoaderWithHookedConsole } = require('sdk/test/loader');
const { loader } = LoaderWithHookedConsole(module);
const app = require("sdk/system/xul-app");
// This test makes sure that require statements used by all AMO hosted
// add-ons will be able to use old require statements.
// Tests are based on following usage data:
// https://docs.google.com/spreadsheet/ccc?key=0ApEBy-GRnGxzdHlRMHJ5RXN1aWJ4RGhINkxSd0FCQXc#gid=0
exports["test compatibility"] = function(assert) {
let { require } = loader;
assert.equal(require("self"),
require("sdk/self"), "sdk/self -> self");
assert.equal(require("tabs"),
require("sdk/tabs"), "sdk/tabs -> tabs");
if (app.is("Firefox")) {
assert.throws(() => require("widget"),
/Module `widget` is not found at/,
"There is no widget module");
assert.throws(() => require("sdk/widget"),
/Module `sdk\/widget` is not found at/,
"There is no sdk/widget module");
}
assert.equal(require("page-mod"),
require("sdk/page-mod"), "sdk/page-mod -> page-mod");
if (app.is("Firefox")) {
assert.equal(require("panel"),
require("sdk/panel"), "sdk/panel -> panel");
}
assert.equal(require("request"),
require("sdk/request"), "sdk/request -> request");
assert.equal(require("chrome"),
require("chrome"), "chrome -> chrome");
assert.equal(require("simple-storage"),
require("sdk/simple-storage"), "sdk/simple-storage -> simple-storage");
if (app.is("Firefox")) {
assert.equal(require("context-menu"),
require("sdk/context-menu"), "sdk/context-menu -> context-menu");
}
assert.equal(require("notifications"),
require("sdk/notifications"), "sdk/notifications -> notifications");
assert.equal(require("preferences-service"),
require("sdk/preferences/service"), "sdk/preferences/service -> preferences-service");
assert.equal(require("window-utils"),
require("sdk/deprecated/window-utils"), "sdk/deprecated/window-utils -> window-utils");
assert.equal(require("url"),
require("sdk/url"), "sdk/url -> url");
if (app.is("Firefox")) {
assert.equal(require("selection"),
require("sdk/selection"), "sdk/selection -> selection");
}
assert.equal(require("timers"),
require("sdk/timers"), "sdk/timers -> timers");
assert.equal(require("simple-prefs"),
require("sdk/simple-prefs"), "sdk/simple-prefs -> simple-prefs");
assert.equal(require("traceback"),
require("sdk/console/traceback"), "sdk/console/traceback -> traceback");
assert.equal(require("unload"),
require("sdk/system/unload"), "sdk/system/unload -> unload");
assert.equal(require("hotkeys"),
require("sdk/hotkeys"), "sdk/hotkeys -> hotkeys");
if (app.is("Firefox")) {
assert.equal(require("clipboard"),
require("sdk/clipboard"), "sdk/clipboard -> clipboard");
}
assert.equal(require("windows"),
require("sdk/windows"), "sdk/windows -> windows");
assert.equal(require("page-worker"),
require("sdk/page-worker"), "sdk/page-worker -> page-worker");
assert.equal(require("timer"),
require("sdk/timers"), "sdk/timers -> timer");
assert.equal(require("xhr"),
require("sdk/net/xhr"), "sdk/io/xhr -> xhr");
assert.equal(require("private-browsing"),
require("sdk/private-browsing"), "sdk/private-browsing -> private-browsing");
assert.equal(require("passwords"),
require("sdk/passwords"), "sdk/passwords -> passwords");
assert.equal(require("match-pattern"),
require("sdk/util/match-pattern"), "sdk/util/match-pattern -> match-pattern");
assert.equal(require("file"),
require("sdk/io/file"), "sdk/io/file -> file");
assert.equal(require("xul-app"),
require("sdk/system/xul-app"), "sdk/system/xul-app -> xul-app");
assert.equal(require("api-utils"),
require("sdk/deprecated/api-utils"), "sdk/deprecated/api-utils -> api-utils");
assert.equal(require("runtime"),
require("sdk/system/runtime"), "sdk/system/runtime -> runtime");
assert.equal(require("base64"),
require("sdk/base64"), "sdk/base64 -> base64");
assert.equal(require("xpcom"),
require("sdk/platform/xpcom"), "sdk/platform/xpcom -> xpcom");
assert.equal(require("keyboard/utils"),
require("sdk/keyboard/utils"), "sdk/keyboard/utils -> keyboard/utils");
assert.equal(require("system"),
require("sdk/system"), "sdk/system -> system");
assert.equal(require("querystring"),
require("sdk/querystring"), "sdk/querystring -> querystring");
assert.equal(require("tabs/utils"),
require("sdk/tabs/utils"), "sdk/tabs/utils -> tabs/utils");
assert.equal(require("dom/events"),
require("sdk/dom/events-shimmed"), "sdk/dom/events-shimmed -> dom/events");
assert.equal(require("tabs/tab.js"),
require("sdk/tabs/tab"), "sdk/tabs/tab -> tabs/tab.js");
assert.equal(require("environment"),
require("sdk/system/environment"), "sdk/system/environment -> environment");
assert.equal(require("test/assert"),
require("sdk/test/assert"), "sdk/test/assert -> test/assert");
assert.equal(require("hidden-frame"),
require("sdk/frame/hidden-frame"), "sdk/frame/hidden-frame -> hidden-frame");
assert.equal(require("collection"),
require("sdk/util/collection"), "sdk/util/collection -> collection");
assert.equal(require("array"),
require("sdk/util/array"), "sdk/util/array -> array");
};

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["test compatibility"] = function(assert) {
assert.throws(() => require("self"),
/^Module `self` is not found/,
"sdk/self -> self");
};

View file

@ -0,0 +1,7 @@
{
"id": "test-layout-change@jetpack",
"name": "test-layout-change",
"ignore-deprecated-path": true,
"version": "0.0.2",
"main": "./lib/main.js"
}

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';
const { setTimeout } = require('sdk/timers');
var mainStarted = false;
exports.main = function main(options, callbacks) {
mainStarted = true;
let tests = {};
tests.testMainArguments = function(assert) {
assert.ok(!!options, 'options argument provided to main');
assert.ok('loadReason' in options, 'loadReason is in options provided by main');
assert.equal(typeof callbacks.print, 'function', 'callbacks.print is a function');
assert.equal(typeof callbacks.quit, 'function', 'callbacks.quit is a function');
// Re-enable when bug 1251664 is fixed
//assert.equal(options.loadReason, 'install', 'options.loadReason is install');
}
require('sdk/test/runner').runTestsFromModule({exports: tests});
}
// this causes a fail if main does not start
setTimeout(function() {
if (mainStarted)
return;
// main didn't start, fail..
require("sdk/test/runner").runTestsFromModule({exports: {
testFail: assert => assert.fail('Main did not start..')
}});
}, 500);

View file

@ -0,0 +1,5 @@
{
"id": "test-main@jetpack",
"main": "./main.js",
"version": "0.0.1"
}

View file

@ -0,0 +1,12 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { name } = require("sdk/self");
exports["test self.name"] = (assert) => {
assert.equal(name, "0-0", "using '0-0' works.");
}
require("sdk/test/runner").runTestsFromModule(module);

View file

@ -0,0 +1,6 @@
{
"id": "name-in-numbers-plus@jetpack",
"name": "0-0",
"main": "./index.js",
"version": "0.0.1"
}

View file

@ -0,0 +1,12 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { name } = require("sdk/self");
exports["test self.name"] = (assert) => {
assert.equal(name, "5", "using '5' works.");
}
require("sdk/test/runner").runTestsFromModule(module);

View file

@ -0,0 +1,6 @@
{
"id": "name-in-numbers@jetpack",
"name": "5",
"main": "./index.js",
"version": "0.0.1"
}

View file

@ -0,0 +1,57 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var options = require("@loader/options");
var metadata = {};
if (options.isNative) {
metadata = require("./package.json");
metadata.permissions = {};
Object.freeze(metadata);
}
else {
metadata = options.metadata;
}
exports.testPackaging = function(assert) {
assert.equal(metadata.description,
"Add-on development made easy.",
"packaging metadata should be available");
try {
metadata.description = 'new description';
assert.fail('should not have been able to set options.metadata property');
}
catch (e) {}
assert.equal(metadata.description,
"Add-on development made easy.",
"packaging metadata should be frozen");
assert.equal(metadata.permissions['private-browsing'], undefined,
"private browsing metadata should be undefined");
assert.equal(metadata['private-browsing'], undefined,
"private browsing metadata should be be frozen");
assert.equal(options['private-browsing'], undefined,
"private browsing metadata should be be frozen");
try {
metadata['private-browsing'] = true;
assert.fail('should not have been able to set options.metadata property');
}
catch(e) {}
assert.equal(metadata['private-browsing'], undefined,
"private browsing metadata should be be frozen");
try {
options.permissions['private-browsing'] = true;
assert.fail('should not have been able to set options.metadata.permissions property');
}
catch (e) {}
assert.equal(metadata.permissions['private-browsing'], undefined,
"private browsing metadata should be be frozen");
};
require("sdk/test/runner").runTestsFromModule(module);

View file

@ -0,0 +1,6 @@
{
"id": "test-packaging@jetpack",
"description": "Add-on development made easy.",
"main": "./main.js",
"version": "0.0.1"
}

View file

@ -0,0 +1,20 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 { packed } = require("sdk/self");
const url = require("sdk/url");
exports["test self.packed"] = function (assert) {
assert.ok(packed, "require('sdk/self').packed is correct");
}
exports["test url.toFilename"] = function (assert) {
assert.throws(
function() { url.toFilename(module.uri); },
/cannot map to filename: /,
"url.toFilename() can fail for packed XPIs");
}
require("sdk/test/runner").runTestsFromModule(module);

View file

@ -0,0 +1,6 @@
{
"id": "test-url@jetpack",
"unpack": false,
"main": "./main.js",
"version": "0.0.1"
}

View file

@ -0,0 +1,11 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html>
<head>
<meta charset="UTF-8">
<title>Page Mod Debugger Test</title>
</head>
<body></body>
</html>

View file

@ -0,0 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
function runDebuggerStatement () {
window.document.body.setAttribute('style', 'background-color: red');
debugger;
window.document.body.setAttribute('style', 'background-color: green');
}
exportFunction(
runDebuggerStatement,
document.defaultView,
{ defineAs: "runDebuggerStatement" }
);

View file

@ -0,0 +1,136 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 { PageMod } = require('sdk/page-mod');
const tabs = require('sdk/tabs');
const { closeTab } = require('sdk/tabs/utils');
const promise = require('sdk/core/promise')
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
const { data } = require('sdk/self');
const { set } = require('sdk/preferences/service');
const { require: devtoolsRequire } = Cu.import("resource://devtools/shared/Loader.jsm", {});
const { DebuggerServer } = devtoolsRequire("devtools/server/main");
const { DebuggerClient } = devtoolsRequire("devtools/shared/client/main");
var gClient;
var ok;
var testName = 'testDebugger';
var iframeURL = 'data:text/html;charset=utf-8,' + testName;
var TAB_URL = 'data:text/html;charset=utf-8,' + encodeURIComponent('<iframe src="' + iframeURL + '" />');
TAB_URL = data.url('index.html');
var mod;
exports.testDebugger = function(assert, done) {
ok = assert.ok.bind(assert);
assert.pass('starting test');
set('devtools.debugger.log', true);
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
let transport = DebuggerServer.connectPipe();
gClient = new DebuggerClient(transport);
gClient.connect((aType, aTraits) => {
tabs.open({
url: TAB_URL,
onLoad: function(tab) {
assert.pass('tab loaded');
attachTabActorForUrl(gClient, TAB_URL).
then(_ => { assert.pass('attachTabActorForUrl called'); return _; }).
then(attachThread).
then(testDebuggerStatement).
then(_ => { assert.pass('testDebuggerStatement called') }).
then(closeConnection).
then(_ => { assert.pass('closeConnection called') }).
then(_ => { tab.close() }).
then(done).
then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
}
});
});
}
function attachThread([aGrip, aResponse]) {
let deferred = promise.defer();
// Now attach and resume...
gClient.request({ to: aResponse.threadActor, type: "attach" }, () => {
gClient.request({ to: aResponse.threadActor, type: "resume" }, () => {
ok(true, "Pause wasn't called before we've attached.");
deferred.resolve([aGrip, aResponse]);
});
});
return deferred.promise;
}
function testDebuggerStatement([aGrip, aResponse]) {
let deferred = promise.defer();
ok(aGrip, 'aGrip existss')
gClient.addListener("paused", (aEvent, aPacket) => {
ok(true, 'there was a pause event');
gClient.request({ to: aResponse.threadActor, type: "resume" }, () => {
ok(true, "The pause handler was triggered on a debugger statement.");
deferred.resolve();
});
});
mod = PageMod({
include: TAB_URL,
attachTo: ['existing', 'top', 'frame'],
contentScriptFile: data.url('script.js'),
onAttach: function(mod) {
ok(true, 'the page-mod was attached to ' + mod.tab.url);
require('sdk/timers').setTimeout(function() {
let debuggee = getMostRecentBrowserWindow().gBrowser.selectedBrowser.contentWindow.wrappedJSObject;
debuggee.runDebuggerStatement();
ok(true, 'called runDebuggerStatement');
}, 500)
}
});
ok(true, 'PageMod was created');
return deferred.promise;
}
function getTabActorForUrl(aClient, aUrl) {
let deferred = promise.defer();
aClient.listTabs(aResponse => {
let tabActor = aResponse.tabs.filter(aGrip => aGrip.url == aUrl).pop();
deferred.resolve(tabActor);
});
return deferred.promise;
}
function attachTabActorForUrl(aClient, aUrl) {
let deferred = promise.defer();
getTabActorForUrl(aClient, aUrl).then(aGrip => {
aClient.attachTab(aGrip.actor, aResponse => {
deferred.resolve([aGrip, aResponse]);
});
});
return deferred.promise;
}
function closeConnection() {
let deferred = promise.defer();
gClient.close(deferred.resolve);
return deferred.promise;
}
require('sdk/test/runner').runTestsFromModule(module);

View file

@ -0,0 +1,6 @@
{
"id": "test-page-mod-debugger@jetpack",
"author": "Erik Vold",
"main": "./main.js",
"version": "0.0.1"
}

View file

@ -0,0 +1,11 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html>
<head>
<meta charset="UTF-8">
<title>Page Mod Debugger Test</title>
</head>
<body></body>
</html>

View file

@ -0,0 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
function runDebuggerStatement () {
window.document.body.setAttribute('style', 'background-color: red');
debugger;
window.document.body.setAttribute('style', 'background-color: green');
}
exportFunction(
runDebuggerStatement,
document.defaultView,
{ defineAs: "runDebuggerStatement" }
);

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