diff --git a/toolkit/components/extensions/NativeMessaging.jsm b/toolkit/components/extensions/NativeMessaging.jsm index 8a424609a5ac..da186fb44ad5 100644 --- a/toolkit/components/extensions/NativeMessaging.jsm +++ b/toolkit/components/extensions/NativeMessaging.jsm @@ -202,6 +202,7 @@ this.NativeApp = class extends EventEmitter { command: command, arguments: [hostInfo.path], workdir: OS.Path.dirname(command), + stderr: "pipe", }; return Subprocess.call(subprocessOpts); }).then(proc => { @@ -209,6 +210,7 @@ this.NativeApp = class extends EventEmitter { this.proc = proc; this._startRead(); this._startWrite(); + this._startStderrRead(); }).catch(err => { this.startupPromise = null; Cu.reportError(err.message); @@ -268,6 +270,32 @@ this.NativeApp = class extends EventEmitter { }); } + _startStderrRead() { + let proc = this.proc; + let app = this.name; + Task.spawn(function* () { + let partial = ""; + while (true) { + let data = yield proc.stderr.readString(); + if (data.length == 0) { + // We have hit EOF, just stop reading + if (partial) { + Services.console.logStringMessage(`stderr output from native app ${app}: ${partial}`); + } + break; + } + + let lines = data.split(/\r?\n/); + lines[0] = partial + lines[0]; + partial = lines.pop(); + + for (let line of lines) { + Services.console.logStringMessage(`stderr output from native app ${app}: ${line}`); + } + } + }); + } + send(msg) { if (this._isDisconnected) { throw new this.context.cloneScope.Error("Attempt to postMessage on disconnected port"); diff --git a/toolkit/components/extensions/test/mochitest/.eslintrc b/toolkit/components/extensions/test/mochitest/.eslintrc index 93c336db0f5b..47e982ecbd37 100644 --- a/toolkit/components/extensions/test/mochitest/.eslintrc +++ b/toolkit/components/extensions/test/mochitest/.eslintrc @@ -9,6 +9,7 @@ "sendAsyncMessage": false, "waitForLoad": true, + "promiseConsoleOutput": true, "ExtensionTestUtils": false, "NetUtil": true, diff --git a/toolkit/components/extensions/test/mochitest/chrome.ini b/toolkit/components/extensions/test/mochitest/chrome.ini index 916b520a8242..7e23f8bf80fe 100644 --- a/toolkit/components/extensions/test/mochitest/chrome.ini +++ b/toolkit/components/extensions/test/mochitest/chrome.ini @@ -1,5 +1,6 @@ [DEFAULT] support-files = + head.js file_download.html file_download.txt interruptible.sjs diff --git a/toolkit/components/extensions/test/mochitest/head.js b/toolkit/components/extensions/test/mochitest/head.js index c0fffa06a4d2..1b1a294726bb 100644 --- a/toolkit/components/extensions/test/mochitest/head.js +++ b/toolkit/components/extensions/test/mochitest/head.js @@ -10,3 +10,4 @@ function waitForLoad(win) { }, true); }); } + diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_native_messaging.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_native_messaging.html index 93fe499ed001..d3866c6ee1c9 100644 --- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_native_messaging.html +++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_native_messaging.html @@ -6,7 +6,6 @@ - @@ -23,11 +22,40 @@ Cu.import("resource://gre/modules/FileUtils.jsm"); Cu.import("resource://gre/modules/osfile.jsm"); Cu.import("resource://gre/modules/Services.jsm"); let {Subprocess, SubprocessImpl} = Cu.import("resource://gre/modules/Subprocess.jsm"); +Components.utils.import("resource://gre/modules/Task.jsm"); if (AppConstants.platform == "win") { Cu.import("resource://testing-common/MockRegistry.jsm"); } +var promiseConsoleOutput = Task.async(function* (task) { + const DONE = "=== extension test console listener done ==="; + + let listener; + let messages = []; + let awaitListener = new Promise(resolve => { + listener = msg => { + if (msg == DONE) { + resolve(); + } else if (msg instanceof Ci.nsIConsoleMessage) { + messages.push(msg.message); + } + }; + }); + + Services.console.registerListener(listener); + try { + let result = yield task(); + + Services.console.logStringMessage(DONE); + yield awaitListener; + + return {messages, result}; + } finally { + Services.console.unregisterListener(listener); + } +}); + const PREF_MAX_READ = "webextensions.native-messaging.max-input-message-bytes"; const PREF_MAX_WRITE = "webextensions.native-messaging.max-output-message-bytes"; @@ -96,6 +124,21 @@ while True: sys.stdout.write(msg) `; +const STDERR_LINES = ["hello stderr", "this should be a separate line"]; +let STDERR_MSG = STDERR_LINES.join("\\n"); + +// Python apparently line-buffers stderr even with the -u arg on +// Windows 7. Dealing with that is more hassle than its worth but +// on other platforms, we want to keep testing partial lines. +if (AppConstants.isPlatformAndVersionAtMost("win", "7")) { + STDERR_MSG += "\\n"; +} + +const STDERR_BODY = String.raw` +import sys +sys.stderr.write("${STDERR_MSG}") +`; + const SCRIPTS = [ { name: "echo", @@ -112,6 +155,11 @@ const SCRIPTS = [ description: "a native app that does not exit when stdin closes or on SIGTERM", script: WONTDIE_BODY, }, + { + name: "stderr", + description: "a native app that writes to stderr and then exits", + script: STDERR_BODY, + }, ]; add_task(function* setup() { @@ -610,6 +658,35 @@ add_task(function* test_unresponsive_native_app() { is(procCount, 0, "subprocess was succesfully killed"); }); +add_task(function* test_stderr() { + function background() { + let port = browser.runtime.connectNative("stderr"); + port.onDisconnect.addListener(() => { + browser.test.sendMessage("finished"); + }); + } + + let {messages} = yield promiseConsoleOutput(function* () { + let extension = ExtensionTestUtils.loadExtension({ + background: `(${background})()`, + manifest: { + permissions: ["nativeMessaging"], + }, + }, ID); + + yield extension.startup(); + yield extension.awaitMessage("finished"); + yield extension.unload(); + }); + + let lines = STDERR_LINES.map(line => messages.findIndex(msg => msg.includes(line))); + isnot(lines[0], -1, "Saw first line of stderr output on the console"); + isnot(lines[1], -1, "Saw second line of stderr output on the console"); + isnot(lines[0], lines[1], "Stderr output lines are separated in the console"); + + yield waitForSubprocessExit(); +}); +