mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-06 07:48:38 +09:00
Rename Toolkit's webextensions component directory to better reflect what it is.
This commit is contained in:
parent
6955450f7a
commit
d7d16e7fbb
242 changed files with 4 additions and 2 deletions
|
|
@ -0,0 +1,514 @@
|
|||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
"use strict";
|
||||
|
||||
/* globals chrome */
|
||||
|
||||
const PREF_MAX_READ = "webextensions.native-messaging.max-input-message-bytes";
|
||||
const PREF_MAX_WRITE = "webextensions.native-messaging.max-output-message-bytes";
|
||||
|
||||
const ECHO_BODY = String.raw`
|
||||
import struct
|
||||
import sys
|
||||
|
||||
while True:
|
||||
rawlen = sys.stdin.read(4)
|
||||
if len(rawlen) == 0:
|
||||
sys.exit(0)
|
||||
msglen = struct.unpack('@I', rawlen)[0]
|
||||
msg = sys.stdin.read(msglen)
|
||||
|
||||
sys.stdout.write(struct.pack('@I', msglen))
|
||||
sys.stdout.write(msg)
|
||||
`;
|
||||
|
||||
const INFO_BODY = String.raw`
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
msg = json.dumps({"args": sys.argv, "cwd": os.getcwd()})
|
||||
sys.stdout.write(struct.pack('@I', len(msg)))
|
||||
sys.stdout.write(msg)
|
||||
sys.exit(0)
|
||||
`;
|
||||
|
||||
const STDERR_LINES = ["hello stderr", "this should be a separate line"];
|
||||
let STDERR_MSG = STDERR_LINES.join("\\n");
|
||||
|
||||
const STDERR_BODY = String.raw`
|
||||
import sys
|
||||
sys.stderr.write("${STDERR_MSG}")
|
||||
`;
|
||||
|
||||
const SCRIPTS = [
|
||||
{
|
||||
name: "echo",
|
||||
description: "a native app that echoes back messages it receives",
|
||||
script: ECHO_BODY.replace(/^ {2}/gm, ""),
|
||||
},
|
||||
{
|
||||
name: "info",
|
||||
description: "a native app that gives some info about how it was started",
|
||||
script: INFO_BODY.replace(/^ {2}/gm, ""),
|
||||
},
|
||||
{
|
||||
name: "stderr",
|
||||
description: "a native app that writes to stderr and then exits",
|
||||
script: STDERR_BODY.replace(/^ {2}/gm, ""),
|
||||
},
|
||||
];
|
||||
|
||||
add_task(function* setup() {
|
||||
yield setupHosts(SCRIPTS);
|
||||
});
|
||||
|
||||
// Test the basic operation of native messaging with a simple
|
||||
// script that echoes back whatever message is sent to it.
|
||||
add_task(function* test_happy_path() {
|
||||
function background() {
|
||||
let port = browser.runtime.connectNative("echo");
|
||||
port.onMessage.addListener(msg => {
|
||||
browser.test.sendMessage("message", msg);
|
||||
});
|
||||
browser.test.onMessage.addListener((what, payload) => {
|
||||
if (what == "send") {
|
||||
if (payload._json) {
|
||||
let json = payload._json;
|
||||
payload.toJSON = () => json;
|
||||
delete payload._json;
|
||||
}
|
||||
port.postMessage(payload);
|
||||
}
|
||||
});
|
||||
browser.test.sendMessage("ready");
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background,
|
||||
manifest: {
|
||||
applications: {gecko: {id: ID}},
|
||||
permissions: ["nativeMessaging"],
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitMessage("ready");
|
||||
const tests = [
|
||||
{
|
||||
data: "this is a string",
|
||||
what: "simple string",
|
||||
},
|
||||
{
|
||||
data: "Это юникода",
|
||||
what: "unicode string",
|
||||
},
|
||||
{
|
||||
data: {test: "hello"},
|
||||
what: "simple object",
|
||||
},
|
||||
{
|
||||
data: {
|
||||
what: "An object with a few properties",
|
||||
number: 123,
|
||||
bool: true,
|
||||
nested: {what: "another object"},
|
||||
},
|
||||
what: "object with several properties",
|
||||
},
|
||||
|
||||
{
|
||||
data: {
|
||||
ignoreme: true,
|
||||
_json: {data: "i have a tojson method"},
|
||||
},
|
||||
expected: {data: "i have a tojson method"},
|
||||
what: "object with toJSON() method",
|
||||
},
|
||||
];
|
||||
for (let test of tests) {
|
||||
extension.sendMessage("send", test.data);
|
||||
let response = yield extension.awaitMessage("message");
|
||||
let expected = test.expected || test.data;
|
||||
deepEqual(response, expected, `Echoed a message of type ${test.what}`);
|
||||
}
|
||||
|
||||
let procCount = yield getSubprocessCount();
|
||||
equal(procCount, 1, "subprocess is still running");
|
||||
let exitPromise = waitForSubprocessExit();
|
||||
yield extension.unload();
|
||||
yield exitPromise;
|
||||
});
|
||||
|
||||
if (AppConstants.platform == "win") {
|
||||
// "relative.echo" has a relative path in the host manifest.
|
||||
add_task(function* test_relative_path() {
|
||||
function background() {
|
||||
let port = browser.runtime.connectNative("relative.echo");
|
||||
let MSG = "test relative echo path";
|
||||
port.onMessage.addListener(msg => {
|
||||
browser.test.assertEq(MSG, msg, "Got expected message back");
|
||||
browser.test.sendMessage("done");
|
||||
});
|
||||
port.postMessage(MSG);
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background,
|
||||
manifest: {
|
||||
applications: {gecko: {id: ID}},
|
||||
permissions: ["nativeMessaging"],
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitMessage("done");
|
||||
|
||||
let procCount = yield getSubprocessCount();
|
||||
equal(procCount, 1, "subprocess is still running");
|
||||
let exitPromise = waitForSubprocessExit();
|
||||
yield extension.unload();
|
||||
yield exitPromise;
|
||||
});
|
||||
}
|
||||
|
||||
// Test sendNativeMessage()
|
||||
add_task(function* test_sendNativeMessage() {
|
||||
async function background() {
|
||||
let MSG = {test: "hello world"};
|
||||
|
||||
// Check error handling
|
||||
await browser.test.assertRejects(
|
||||
browser.runtime.sendNativeMessage("nonexistent", MSG),
|
||||
/Attempt to postMessage on disconnected port/,
|
||||
"sendNativeMessage() to a nonexistent app failed");
|
||||
|
||||
// Check regular message exchange
|
||||
let reply = await browser.runtime.sendNativeMessage("echo", MSG);
|
||||
|
||||
let expected = JSON.stringify(MSG);
|
||||
let received = JSON.stringify(reply);
|
||||
browser.test.assertEq(expected, received, "Received echoed native message");
|
||||
|
||||
browser.test.sendMessage("finished");
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background,
|
||||
manifest: {
|
||||
applications: {gecko: {id: ID}},
|
||||
permissions: ["nativeMessaging"],
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitMessage("finished");
|
||||
|
||||
// With sendNativeMessage(), the subprocess should be disconnected
|
||||
// after exchanging a single message.
|
||||
yield waitForSubprocessExit();
|
||||
|
||||
yield extension.unload();
|
||||
});
|
||||
|
||||
// Test calling Port.disconnect()
|
||||
add_task(function* test_disconnect() {
|
||||
function background() {
|
||||
let port = browser.runtime.connectNative("echo");
|
||||
port.onMessage.addListener((msg, msgPort) => {
|
||||
browser.test.assertEq(port, msgPort, "onMessage handler should receive the port as the second argument");
|
||||
browser.test.sendMessage("message", msg);
|
||||
});
|
||||
port.onDisconnect.addListener(msgPort => {
|
||||
browser.test.fail("onDisconnect should not be called for disconnect()");
|
||||
});
|
||||
browser.test.onMessage.addListener((what, payload) => {
|
||||
if (what == "send") {
|
||||
if (payload._json) {
|
||||
let json = payload._json;
|
||||
payload.toJSON = () => json;
|
||||
delete payload._json;
|
||||
}
|
||||
port.postMessage(payload);
|
||||
} else if (what == "disconnect") {
|
||||
try {
|
||||
port.disconnect();
|
||||
browser.test.sendMessage("disconnect-result", {success: true});
|
||||
} catch (err) {
|
||||
browser.test.sendMessage("disconnect-result", {
|
||||
success: false,
|
||||
errmsg: err.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
browser.test.sendMessage("ready");
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background,
|
||||
manifest: {
|
||||
applications: {gecko: {id: ID}},
|
||||
permissions: ["nativeMessaging"],
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitMessage("ready");
|
||||
|
||||
extension.sendMessage("send", "test");
|
||||
let response = yield extension.awaitMessage("message");
|
||||
equal(response, "test", "Echoed a string");
|
||||
|
||||
let procCount = yield getSubprocessCount();
|
||||
equal(procCount, 1, "subprocess is running");
|
||||
|
||||
extension.sendMessage("disconnect");
|
||||
response = yield extension.awaitMessage("disconnect-result");
|
||||
equal(response.success, true, "disconnect succeeded");
|
||||
|
||||
do_print("waiting for subprocess to exit");
|
||||
yield waitForSubprocessExit();
|
||||
procCount = yield getSubprocessCount();
|
||||
equal(procCount, 0, "subprocess is no longer running");
|
||||
|
||||
extension.sendMessage("disconnect");
|
||||
response = yield extension.awaitMessage("disconnect-result");
|
||||
equal(response.success, true, "second call to disconnect silently ignored");
|
||||
|
||||
yield extension.unload();
|
||||
});
|
||||
|
||||
// Test the limit on message size for writing
|
||||
add_task(function* test_write_limit() {
|
||||
Services.prefs.setIntPref(PREF_MAX_WRITE, 10);
|
||||
function clearPref() {
|
||||
Services.prefs.clearUserPref(PREF_MAX_WRITE);
|
||||
}
|
||||
do_register_cleanup(clearPref);
|
||||
|
||||
function background() {
|
||||
const PAYLOAD = "0123456789A";
|
||||
let port = browser.runtime.connectNative("echo");
|
||||
try {
|
||||
port.postMessage(PAYLOAD);
|
||||
browser.test.sendMessage("result", null);
|
||||
} catch (ex) {
|
||||
browser.test.sendMessage("result", ex.message);
|
||||
}
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background,
|
||||
manifest: {
|
||||
applications: {gecko: {id: ID}},
|
||||
permissions: ["nativeMessaging"],
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
|
||||
let errmsg = yield extension.awaitMessage("result");
|
||||
notEqual(errmsg, null, "native postMessage() failed for overly large message");
|
||||
|
||||
yield extension.unload();
|
||||
yield waitForSubprocessExit();
|
||||
|
||||
clearPref();
|
||||
});
|
||||
|
||||
// Test the limit on message size for reading
|
||||
add_task(function* test_read_limit() {
|
||||
Services.prefs.setIntPref(PREF_MAX_READ, 10);
|
||||
function clearPref() {
|
||||
Services.prefs.clearUserPref(PREF_MAX_READ);
|
||||
}
|
||||
do_register_cleanup(clearPref);
|
||||
|
||||
function background() {
|
||||
const PAYLOAD = "0123456789A";
|
||||
let port = browser.runtime.connectNative("echo");
|
||||
port.onDisconnect.addListener(msgPort => {
|
||||
browser.test.assertEq(port, msgPort, "onDisconnect handler should receive the port as the first argument");
|
||||
browser.test.assertEq("Native application tried to send a message of 13 bytes, which exceeds the limit of 10 bytes.", port.error && port.error.message);
|
||||
browser.test.sendMessage("result", "disconnected");
|
||||
});
|
||||
port.onMessage.addListener(msg => {
|
||||
browser.test.sendMessage("result", "message");
|
||||
});
|
||||
port.postMessage(PAYLOAD);
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background,
|
||||
manifest: {
|
||||
applications: {gecko: {id: ID}},
|
||||
permissions: ["nativeMessaging"],
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
|
||||
let result = yield extension.awaitMessage("result");
|
||||
equal(result, "disconnected", "native port disconnected on receiving large message");
|
||||
|
||||
yield extension.unload();
|
||||
yield waitForSubprocessExit();
|
||||
|
||||
clearPref();
|
||||
});
|
||||
|
||||
// Test that an extension without the nativeMessaging permission cannot
|
||||
// use native messaging.
|
||||
add_task(function* test_ext_permission() {
|
||||
function background() {
|
||||
browser.test.assertFalse("connectNative" in chrome.runtime, "chrome.runtime.connectNative does not exist without nativeMessaging permission");
|
||||
browser.test.assertFalse("connectNative" in browser.runtime, "browser.runtime.connectNative does not exist without nativeMessaging permission");
|
||||
browser.test.assertFalse("sendNativeMessage" in chrome.runtime, "chrome.runtime.sendNativeMessage does not exist without nativeMessaging permission");
|
||||
browser.test.assertFalse("sendNativeMessage" in browser.runtime, "browser.runtime.sendNativeMessage does not exist without nativeMessaging permission");
|
||||
browser.test.sendMessage("finished");
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background,
|
||||
manifest: {},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitMessage("finished");
|
||||
yield extension.unload();
|
||||
});
|
||||
|
||||
// Test that an extension that is not listed in allowed_extensions for
|
||||
// a native application cannot use that application.
|
||||
add_task(function* test_app_permission() {
|
||||
function background() {
|
||||
let port = browser.runtime.connectNative("echo");
|
||||
port.onDisconnect.addListener(msgPort => {
|
||||
browser.test.assertEq(port, msgPort, "onDisconnect handler should receive the port as the first argument");
|
||||
browser.test.assertEq("This extension does not have permission to use native application echo (or the application is not installed)", port.error && port.error.message);
|
||||
browser.test.sendMessage("result", "disconnected");
|
||||
});
|
||||
port.onMessage.addListener(msg => {
|
||||
browser.test.sendMessage("result", "message");
|
||||
});
|
||||
port.postMessage({test: "test"});
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background,
|
||||
manifest: {
|
||||
permissions: ["nativeMessaging"],
|
||||
},
|
||||
}, "somethingelse@tests.mozilla.org");
|
||||
|
||||
yield extension.startup();
|
||||
|
||||
let result = yield extension.awaitMessage("result");
|
||||
equal(result, "disconnected", "connectNative() failed without native app permission");
|
||||
|
||||
yield extension.unload();
|
||||
|
||||
let procCount = yield getSubprocessCount();
|
||||
equal(procCount, 0, "No child process was started");
|
||||
});
|
||||
|
||||
// Test that the command-line arguments and working directory for the
|
||||
// native application are as expected.
|
||||
add_task(function* test_child_process() {
|
||||
function background() {
|
||||
let port = browser.runtime.connectNative("info");
|
||||
port.onMessage.addListener(msg => {
|
||||
browser.test.sendMessage("result", msg);
|
||||
});
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background,
|
||||
manifest: {
|
||||
applications: {gecko: {id: ID}},
|
||||
permissions: ["nativeMessaging"],
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
|
||||
let msg = yield extension.awaitMessage("result");
|
||||
equal(msg.args.length, 2, "Received one command line argument");
|
||||
equal(msg.args[1], getPath("info.json"), "Command line argument is the path to the native host manifest");
|
||||
equal(msg.cwd.replace(/^\/private\//, "/"), tmpDir.path,
|
||||
"Working directory is the directory containing the native appliation");
|
||||
|
||||
let exitPromise = waitForSubprocessExit();
|
||||
yield extension.unload();
|
||||
yield exitPromise;
|
||||
});
|
||||
|
||||
add_task(function* test_stderr() {
|
||||
function background() {
|
||||
let port = browser.runtime.connectNative("stderr");
|
||||
port.onDisconnect.addListener(msgPort => {
|
||||
browser.test.assertEq(port, msgPort, "onDisconnect handler should receive the port as the first argument");
|
||||
browser.test.assertEq(null, port.error, "Normal application exit is not an error");
|
||||
browser.test.sendMessage("finished");
|
||||
});
|
||||
}
|
||||
|
||||
let {messages} = yield promiseConsoleOutput(function* () {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background,
|
||||
manifest: {
|
||||
applications: {gecko: {id: ID}},
|
||||
permissions: ["nativeMessaging"],
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitMessage("finished");
|
||||
yield extension.unload();
|
||||
|
||||
yield waitForSubprocessExit();
|
||||
});
|
||||
|
||||
let lines = STDERR_LINES.map(line => messages.findIndex(msg => msg.message.includes(line)));
|
||||
notEqual(lines[0], -1, "Saw first line of stderr output on the console");
|
||||
notEqual(lines[1], -1, "Saw second line of stderr output on the console");
|
||||
notEqual(lines[0], lines[1], "Stderr output lines are separated in the console");
|
||||
});
|
||||
|
||||
// Test that calling connectNative() multiple times works
|
||||
// (bug 1313980 was a previous regression in this area)
|
||||
add_task(function* test_multiple_connects() {
|
||||
async function background() {
|
||||
function once() {
|
||||
return new Promise(resolve => {
|
||||
let MSG = "hello";
|
||||
let port = browser.runtime.connectNative("echo");
|
||||
|
||||
port.onMessage.addListener(msg => {
|
||||
browser.test.assertEq(MSG, msg, "Got expected message back");
|
||||
port.disconnect();
|
||||
resolve();
|
||||
});
|
||||
port.postMessage(MSG);
|
||||
});
|
||||
}
|
||||
|
||||
await once();
|
||||
await once();
|
||||
browser.test.notifyPass("multiple-connect");
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background,
|
||||
manifest: {
|
||||
applications: {gecko: {id: ID}},
|
||||
permissions: ["nativeMessaging"],
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish("multiple-connect");
|
||||
yield extension.unload();
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue