Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
AddonTestUtils.init(this);
const ECHO_BODY = String.raw`
import struct
import sys
stdin = getattr(sys.stdin, 'buffer', sys.stdin)
stdout = getattr(sys.stdout, 'buffer', sys.stdout)
while True:
rawlen = stdin.read(4)
if len(rawlen) == 0:
sys.exit(0)
msglen = struct.unpack('@I', rawlen)[0]
msg = stdin.read(msglen)
stdout.write(struct.pack('@I', msglen))
stdout.write(msg)
`;
const SCRIPTS = [
{
name: "echo",
description: "a native app that echoes back messages it receives",
script: ECHO_BODY.replace(/^ {2}/gm, ""),
},
];
function loadTestExtension({ background }) {
return ExtensionTestUtils.loadExtension({
background,
manifest: {
browser_specific_settings: { gecko: { id: ID } },
permissions: ["nativeMessaging"],
},
});
}
add_setup(async () => {
await ExtensionTestUtils.startAddonManager();
await setupHosts(SCRIPTS);
});
add_task(async function test_many_connectNative_calls() {
let extension = loadTestExtension({
async background() {
// Let's verify that things run smoothly even if we launch "many" more,
// e.g. 70 (arbitrarily chosen, higher than 64).
const NUMBER_OF_CONCURRENT_NATIVE_MESSAGING_APPS = 70;
const openPorts = [];
const remainingMsgs = new Set();
const firstMessagePromises = [];
for (let i = 0; i < NUMBER_OF_CONCURRENT_NATIVE_MESSAGING_APPS; ++i) {
const dummyMsg = `pingpong-${i}`;
const port = browser.runtime.connectNative("echo");
openPorts[i] = port;
remainingMsgs.add(i);
firstMessagePromises[i] = new Promise(resolve => {
port.onMessage.addListener(msg => {
browser.test.assertEq(dummyMsg, msg, `Echoed back message ${i}`);
remainingMsgs.delete(i);
browser.test.log(`Remaining: ${Array.from(remainingMsgs)}`);
resolve();
});
port.onDisconnect.addListener(() => {
if (remainingMsgs.delete(i)) {
// If the program somehow exits before it responded, note the
// failure and continue (do not stay stuck).
browser.test.fail(`onDisconnect fired before onMessage: ${i}`);
resolve();
} else {
// onDisconnect should not fire when we call port.disconnect()
// below.
browser.test.fail(`Unexpected port.onDisconnect: ${i}`);
}
});
});
port.postMessage(dummyMsg);
}
browser.test.log(`Awaiting replies to: ${Array.from(remainingMsgs)}`);
await Promise.all(firstMessagePromises);
browser.test.log("Now verifying sendNativeMessage behavior");
browser.test.assertEq(
await browser.runtime.sendNativeMessage("echo", "one_off_msg"),
"one_off_msg",
"sendNativeMessage can still roundtrip after so many connectNative"
);
for (const port of openPorts) {
port.disconnect();
}
browser.test.sendMessage("done");
},
});
await extension.startup();
await extension.awaitMessage("done");
info("Waiting for all echo processes to have exit");
await waitForSubprocessExit();
await extension.unload();
});