Firefox扩展Native Messaging关联Python脚本subprocess.Popen执行失败求助
问题描述
我开发了一个测试Firefox Native Messaging功能的简单扩展,扩展端运行正常:访问《HTML basics》页面时,会通过stdin向本地Python脚本发送“ping”消息,期望脚本回复消息并通过subprocess.Popen执行进程。但测试发现,Python脚本能正常回复消息,但subprocess.Popen(["explorer", "C:/Temp"])无论放在脚本何处都无法执行。单独运行仅含该Popen代码的脚本可正常打开资源管理器窗口。我尝试重构Python代码并关闭stdin流,但问题依旧。Python脚本无控制台窗口,难以排查阻塞位置,单独测试发现脚本会卡在sys.stdin.buffer.read(4)处。
扩展代码
Manifest.json
{ "name": "test", "manifest_version": 2, "version": "1.0", "browser_action": {"default_icon": "icons/message.svg"}, "browser_specific_settings": {"gecko": {"id": "test@example.org","strict_min_version": "50.0"}}, "background": {"scripts": ["background.js"]}, "permissions": ["tabs","activeTab", "webRequest", "<all_urls>", "nativeMessaging"] }
background.js
browser.webRequest.onCompleted.addListener(sendNativeMsg, {urls:["https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/HTML_basics"]}); function onResponse(response) {console.log(`INCOMING MSG: ${response}`);} function sendNativeMsg(activeTab) { let thisMsg = "ping" console.log(`OUTGOING MSG: "${thisMsg}"`); let sending = browser.runtime.sendNativeMessage("test", thisMsg); sending.then(onResponse); }
Python脚本代码
import sys import json import struct import subprocess # Read a message from stdin and decode it. def getMessage(): rawLength = sys.stdin.buffer.read(4) if len(rawLength) == 0: sys.exit(0) messageLength = struct.unpack('@I', rawLength)[0] message = sys.stdin.buffer.read(messageLength).decode('utf-8') return json.loads(message) # Encode a message for transmission, def encodeMessage(messageContent): encodedContent = json.dumps(messageContent, separators=(',', ':')).encode('utf-8') encodedLength = struct.pack('@I', len(encodedContent)) return {'length': encodedLength, 'content': encodedContent} # Send an encoded message to stdout def sendMessage(encodedMessage): sys.stdout.buffer.write(encodedMessage['length']) sys.stdout.buffer.write(encodedMessage['content']) sys.stdout.buffer.flush() while True: subprocess.Popen(["explorer", "C:/Temp"]) #This line is never executed. The lines after here are executed. receivedMessage = getMessage() if receivedMessage == "ping": sendMessage(encodeMessage('stdin was "' + receivedMessage + '", Task is done'))
解决方案
问题根源
脚本卡在sys.stdin.buffer.read(4)导致subprocess.Popen无法执行——while True循环启动后,会先进入getMessage()的阻塞读取,直到收到扩展的消息才会继续向下执行。加上Firefox调用的Native脚本是后台无控制台进程,即使Popen执行也可能因环境问题无法正常弹出窗口。
修复步骤
调整执行顺序,确保Popen在收到消息后触发
将Popen代码移到收到“ping”消息的分支内,避免因阻塞读取导致代码无法执行:while True: receivedMessage = getMessage() if receivedMessage == "ping": # 先执行Popen再回复消息 subprocess.Popen(["explorer", "C:/Temp"], creationflags=subprocess.CREATE_NO_WINDOW) sendMessage(encodeMessage('stdin was "' + receivedMessage + '", Task is done'))添加Windows环境专属参数
在Windows下,后台进程启动GUI程序需要指定creationflags=subprocess.CREATE_NO_WINDOW,避免子进程继承父进程的后台属性,确保资源管理器窗口正常弹出。调试辅助技巧
可以临时添加日志输出到文件,排查代码执行情况:import logging logging.basicConfig(filename='native_msg_debug.log', level=logging.DEBUG) # 在关键位置插入日志 if receivedMessage == "ping": logging.debug("准备执行Popen") subprocess.Popen(["explorer", "C:/Temp"], creationflags=subprocess.CREATE_NO_WINDOW) logging.debug("Popen执行完成") sendMessage(encodeMessage('stdin was "' + receivedMessage + '", Task is done'))
关于stdin的说明
Native Messaging要求脚本保持stdin打开以持续监听扩展消息,关闭stdin会导致脚本直接退出,不符合长连接规范。你遇到的阻塞是正常的监听逻辑,只需确保业务代码在收到消息后执行即可。
内容的提问来源于stack exchange,提问作者Ralf_Reddings

