gecko-dev/toolkit/components/extensions/test/xpcshell/test_native_messaging.js
Kris Maglione 600b6a2dff Bug 1356546: Part 2 - Use StructuredCloneHolder as transport for MessageManager messages. r=aswan
MozReview-Commit-ID: 3z1uAAbsgTj

--HG--
extra : rebase_source : 42dd1c12709705b0e6fae996ddc7f8bc56240bb0
2017-06-04 20:46:38 -07:00

302 lines
10 KiB
JavaScript

"use strict";
/* global OS, HostManifestManager, NativeApp */
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/AsyncShutdown.jsm");
Cu.import("resource://gre/modules/ExtensionCommon.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/Schemas.jsm");
Cu.import("resource://gre/modules/Services.jsm");
const {Subprocess, SubprocessImpl} = Cu.import("resource://gre/modules/Subprocess.jsm", {});
Cu.import("resource://gre/modules/NativeMessaging.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
let registry = null;
if (AppConstants.platform == "win") {
Cu.import("resource://testing-common/MockRegistry.jsm");
registry = new MockRegistry();
do_register_cleanup(() => {
registry.shutdown();
});
}
const REGPATH = "Software\\Mozilla\\NativeMessagingHosts";
const BASE_SCHEMA = "chrome://extensions/content/schemas/manifest.json";
let dir = FileUtils.getDir("TmpD", ["NativeMessaging"]);
dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
let userDir = dir.clone();
userDir.append("user");
userDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
let globalDir = dir.clone();
globalDir.append("global");
globalDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
let dirProvider = {
getFile(property) {
if (property == "XREUserNativeMessaging") {
return userDir.clone();
} else if (property == "XRESysNativeMessaging") {
return globalDir.clone();
}
return null;
},
};
Services.dirsvc.registerProvider(dirProvider);
do_register_cleanup(() => {
Services.dirsvc.unregisterProvider(dirProvider);
dir.remove(true);
});
function writeManifest(path, manifest) {
if (typeof manifest != "string") {
manifest = JSON.stringify(manifest);
}
return OS.File.writeAtomic(path, manifest);
}
let PYTHON;
add_task(async function setup() {
await Schemas.load(BASE_SCHEMA);
PYTHON = await Subprocess.pathSearch("python2.7");
if (PYTHON == null) {
PYTHON = await Subprocess.pathSearch("python");
}
notEqual(PYTHON, null, "Found a suitable python interpreter");
});
let global = this;
// Test of HostManifestManager.lookupApplication() begin here...
let context = {
url: null,
jsonStringify(...args) { return JSON.stringify(...args); },
cloneScope: global,
logError() {},
preprocessors: {},
callOnClose: () => {},
forgetOnClose: () => {},
};
class MockContext extends ExtensionCommon.BaseContext {
constructor(extensionId) {
let fakeExtension = {id: extensionId};
super("testEnv", fakeExtension);
this.sandbox = Cu.Sandbox(global);
}
get cloneScope() {
return global;
}
get principal() {
return Cu.getObjectPrincipal(this.sandbox);
}
}
let templateManifest = {
name: "test",
description: "this is only a test",
path: "/bin/cat",
type: "stdio",
allowed_extensions: ["extension@tests.mozilla.org"],
};
add_task(async function test_nonexistent_manifest() {
let result = await HostManifestManager.lookupApplication("test", context);
equal(result, null, "lookupApplication returns null for non-existent application");
});
const USER_TEST_JSON = OS.Path.join(userDir.path, "test.json");
add_task(async function test_good_manifest() {
await writeManifest(USER_TEST_JSON, templateManifest);
if (registry) {
registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
`${REGPATH}\\test`, "", USER_TEST_JSON);
}
let result = await HostManifestManager.lookupApplication("test", context);
notEqual(result, null, "lookupApplication finds a good manifest");
equal(result.path, USER_TEST_JSON, "lookupApplication returns the correct path");
deepEqual(result.manifest, templateManifest, "lookupApplication returns the manifest contents");
});
add_task(async function test_invalid_json() {
await writeManifest(USER_TEST_JSON, "this is not valid json");
let result = await HostManifestManager.lookupApplication("test", context);
equal(result, null, "lookupApplication ignores bad json");
});
add_task(async function test_invalid_name() {
let manifest = Object.assign({}, templateManifest);
manifest.name = "../test";
await writeManifest(USER_TEST_JSON, manifest);
let result = await HostManifestManager.lookupApplication("test", context);
equal(result, null, "lookupApplication ignores an invalid name");
});
add_task(async function test_name_mismatch() {
let manifest = Object.assign({}, templateManifest);
manifest.name = "not test";
await writeManifest(USER_TEST_JSON, manifest);
let result = await HostManifestManager.lookupApplication("test", context);
let what = (AppConstants.platform == "win") ? "registry key" : "json filename";
equal(result, null, `lookupApplication ignores mistmatch between ${what} and name property`);
});
add_task(async function test_missing_props() {
const PROPS = [
"name",
"description",
"path",
"type",
"allowed_extensions",
];
for (let prop of PROPS) {
let manifest = Object.assign({}, templateManifest);
delete manifest[prop];
await writeManifest(USER_TEST_JSON, manifest);
let result = await HostManifestManager.lookupApplication("test", context);
equal(result, null, `lookupApplication ignores missing ${prop}`);
}
});
add_task(async function test_invalid_type() {
let manifest = Object.assign({}, templateManifest);
manifest.type = "bogus";
await writeManifest(USER_TEST_JSON, manifest);
let result = await HostManifestManager.lookupApplication("test", context);
equal(result, null, "lookupApplication ignores invalid type");
});
add_task(async function test_no_allowed_extensions() {
let manifest = Object.assign({}, templateManifest);
manifest.allowed_extensions = [];
await writeManifest(USER_TEST_JSON, manifest);
let result = await HostManifestManager.lookupApplication("test", context);
equal(result, null, "lookupApplication ignores manifest with no allowed_extensions");
});
const GLOBAL_TEST_JSON = OS.Path.join(globalDir.path, "test.json");
let globalManifest = Object.assign({}, templateManifest);
globalManifest.description = "This manifest is from the systemwide directory";
add_task(async function good_manifest_system_dir() {
await OS.File.remove(USER_TEST_JSON);
await writeManifest(GLOBAL_TEST_JSON, globalManifest);
if (registry) {
registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
`${REGPATH}\\test`, "", null);
registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
`${REGPATH}\\test`, "", GLOBAL_TEST_JSON);
}
let where = (AppConstants.platform == "win") ? "registry location" : "directory";
let result = await HostManifestManager.lookupApplication("test", context);
notEqual(result, null, `lookupApplication finds a manifest in the system-wide ${where}`);
equal(result.path, GLOBAL_TEST_JSON, `lookupApplication returns path in the system-wide ${where}`);
deepEqual(result.manifest, globalManifest, `lookupApplication returns manifest contents from the system-wide ${where}`);
});
add_task(async function test_user_dir_precedence() {
await writeManifest(USER_TEST_JSON, templateManifest);
if (registry) {
registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
`${REGPATH}\\test`, "", USER_TEST_JSON);
}
// global test.json and LOCAL_MACHINE registry key on windows are
// still present from the previous test
let result = await HostManifestManager.lookupApplication("test", context);
notEqual(result, null, "lookupApplication finds a manifest when entries exist in both user-specific and system-wide locations");
equal(result.path, USER_TEST_JSON, "lookupApplication returns the user-specific path when user-specific and system-wide entries both exist");
deepEqual(result.manifest, templateManifest, "lookupApplication returns user-specific manifest contents with user-specific and system-wide entries both exist");
});
// Test shutdown handling in NativeApp
add_task(async function test_native_app_shutdown() {
const SCRIPT = String.raw`
import signal
import struct
import sys
signal.signal(signal.SIGTERM, signal.SIG_IGN)
while True:
rawlen = sys.stdin.read(4)
if len(rawlen) == 0:
signal.pause()
msglen = struct.unpack('@I', rawlen)[0]
msg = sys.stdin.read(msglen)
sys.stdout.write(struct.pack('@I', msglen))
sys.stdout.write(msg)
`;
let scriptPath = OS.Path.join(userDir.path, "wontdie.py");
let manifestPath = OS.Path.join(userDir.path, "wontdie.json");
const ID = "native@tests.mozilla.org";
let manifest = {
name: "wontdie",
description: "test async shutdown of native apps",
type: "stdio",
allowed_extensions: [ID],
};
if (AppConstants.platform == "win") {
await OS.File.writeAtomic(scriptPath, SCRIPT);
let batPath = OS.Path.join(userDir.path, "wontdie.bat");
let batBody = `@ECHO OFF\n${PYTHON} -u "${scriptPath}" %*\n`;
await OS.File.writeAtomic(batPath, batBody);
await OS.File.setPermissions(batPath, {unixMode: 0o755});
manifest.path = batPath;
await writeManifest(manifestPath, manifest);
registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
`${REGPATH}\\wontdie`, "", manifestPath);
} else {
await OS.File.writeAtomic(scriptPath, `#!${PYTHON} -u\n${SCRIPT}`);
await OS.File.setPermissions(scriptPath, {unixMode: 0o755});
manifest.path = scriptPath;
await writeManifest(manifestPath, manifest);
}
let mockContext = new MockContext(ID);
let app = new NativeApp(mockContext, "wontdie");
// send a message and wait for the reply to make sure the app is running
let MSG = "test";
let recvPromise = new Promise(resolve => {
let listener = (what, msg) => {
equal(msg, MSG, "Received test message");
app.off("message", listener);
resolve();
};
app.on("message", listener);
});
let buffer = NativeApp.encodeMessage(mockContext, MSG);
app.send(new StructuredCloneHolder(buffer));
await recvPromise;
app._cleanup();
do_print("waiting for async shutdown");
Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true);
AsyncShutdown.profileBeforeChange._trigger();
Services.prefs.clearUserPref("toolkit.asyncshutdown.testing");
let procs = await SubprocessImpl.Process.getWorker().call("getProcesses", []);
equal(procs.size, 0, "native process exited");
});