浏览器下载插件

This commit is contained in:
zhongluofeng
2026-07-21 17:40:55 +08:00
parent 548b022426
commit 141547acb6
9 changed files with 1322 additions and 129 deletions
+130 -1
View File
@@ -11,6 +11,101 @@ use tauri::{AppHandle, Emitter, Manager};
#[cfg(windows)]
pub const CREATE_NO_WINDOW: u32 = 0x08000000;
// Windows Job Object 相关常量,用于异常退出时自动清理子进程
#[cfg(windows)]
#[allow(non_snake_case, non_upper_case_globals, non_camel_case_types)]
mod winapi {
pub const JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE: u32 = 0x2000;
pub type HANDLE = *mut std::ffi::c_void;
pub type BOOL = i32;
pub type DWORD = u32;
pub type ULONG_PTR = usize;
#[repr(C)]
pub struct IO_COUNTERS {
pub ReadOperationCount: ULONG_PTR,
pub WriteOperationCount: ULONG_PTR,
pub OtherOperationCount: ULONG_PTR,
pub ReadTransferCount: ULONG_PTR,
pub WriteTransferCount: ULONG_PTR,
pub OtherTransferCount: ULONG_PTR,
}
#[repr(C)]
pub struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION {
pub BasicLimitInformation: JOBOBJECT_BASIC_LIMIT_INFORMATION,
pub IoInfo: IO_COUNTERS,
pub ProcessMemoryLimit: ULONG_PTR,
pub JobMemoryLimit: ULONG_PTR,
pub PeakProcessMemoryUsed: ULONG_PTR,
pub PeakJobMemoryUsed: ULONG_PTR,
}
#[repr(C)]
pub struct JOBOBJECT_BASIC_LIMIT_INFORMATION {
pub PerProcessUserTimeLimit: i64,
pub PerJobUserTimeLimit: i64,
pub LimitFlags: DWORD,
pub MinimumWorkingSetSize: ULONG_PTR,
pub MaximumWorkingSetSize: ULONG_PTR,
pub ActiveProcessLimit: DWORD,
pub Affinity: ULONG_PTR,
pub PriorityClass: DWORD,
pub SchedulingClass: DWORD,
}
pub const JobObjectExtendedLimitInformation: DWORD = 9;
extern "system" {
pub fn CreateJobObjectW(lpJobAttributes: *mut std::ffi::c_void, lpName: *const u16) -> HANDLE;
pub fn SetInformationJobObject(
hJob: HANDLE,
JobObjectInformationClass: DWORD,
lpJobObjectInformation: *mut std::ffi::c_void,
cbJobObjectInformationLength: DWORD,
) -> BOOL;
pub fn AssignProcessToJobObject(hJob: HANDLE, hProcess: HANDLE) -> BOOL;
}
}
/// 全局 Job Object 句柄(lazy 初始化,所有子进程都加入此 job)
/// 当主进程退出(包括崩溃)时,OS 自动终止 job 内所有子进程
/// 用 usize 存储指针以绕过 Send 约束(句柄本身是进程级资源,线程间共享安全)
#[cfg(windows)]
static JOB_HANDLE: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
#[cfg(windows)]
fn get_job_handle() -> Option<winapi::HANDLE> {
let addr = *JOB_HANDLE.get_or_init(|| {
unsafe {
let h = winapi::CreateJobObjectW(std::ptr::null_mut(), std::ptr::null());
if h.is_null() {
eprintln!("[ProcessManager] CreateJobObjectW 失败,异常退出时子进程可能残留");
return 0;
}
// 设置 KILL_ON_JOB_CLOSE:主进程退出时自动终止所有子进程
let mut info: winapi::JOBOBJECT_EXTENDED_LIMIT_INFORMATION = std::mem::zeroed();
info.BasicLimitInformation.LimitFlags = winapi::JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
let ok = winapi::SetInformationJobObject(
h,
winapi::JobObjectExtendedLimitInformation,
&mut info as *mut _ as *mut std::ffi::c_void,
std::mem::size_of::<winapi::JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
);
if ok == 0 {
eprintln!("[ProcessManager] SetInformationJobObject 失败");
return 0;
}
h as usize
}
});
if addr == 0 {
None
} else {
Some(addr as winapi::HANDLE)
}
}
/// 为 Command 设置平台特定的创建标志(Windows 上隐藏控制台窗口)
/// 公开以便其他模块(如 mihomo_manager 调用 mihomo -v 查询版本)复用
#[cfg(windows)]
@@ -24,6 +119,22 @@ pub fn setup_creation_flags(_cmd: &mut Command) {
// 非 Windows 平台无需处理
}
/// 将已启动的子进程加入 Job Object(异常退出时自动清理)
/// 在 Windows 上调用,非 Windows 平台为空操作
#[cfg(windows)]
fn assign_to_job(child: &Child) {
use std::os::windows::io::AsRawHandle;
if let Some(job) = get_job_handle() {
let child_handle = child.as_raw_handle() as winapi::HANDLE;
unsafe {
let _ = winapi::AssignProcessToJobObject(job, child_handle);
}
}
}
#[cfg(not(windows))]
fn assign_to_job(_child: &Child) {}
/// 进程状态枚举
#[derive(Serialize, Clone, Debug)]
#[serde(rename_all = "lowercase")]
@@ -119,6 +230,8 @@ impl ProcessManager {
.spawn()
.map_err(|e| format!("启动进程 '{}' 失败: {}", params.id, e))?;
let pid = child.id();
// 将子进程加入 Job Object,主进程异常退出时由 OS 自动清理
assign_to_job(&child);
let entry = ProcessEntry {
child,
@@ -165,11 +278,27 @@ impl ProcessManager {
}
/// 停止所有进程(应用退出时调用)
/// 同步 kill + 带超时的 wait,确保子进程在主进程退出前真正终止
pub fn stop_all(&self) {
if let Ok(mut processes) = self.processes.lock() {
for (id, mut entry) in processes.drain() {
let _ = entry.child.kill();
let _ = entry.child.wait();
// 带超时的 wait,避免子进程卡住导致主进程无法退出
// 最长等 3 秒,超时则放弃等待(Job Object 兜底会清理)
let deadline = std::time::Instant::now() + Duration::from_secs(3);
loop {
match entry.child.try_wait() {
Ok(Some(_)) => break,
Ok(None) => {
if std::time::Instant::now() >= deadline {
println!("[ProcessManager] 进程 {} 等待退出超时(3s),放弃等待", id);
break;
}
std::thread::sleep(Duration::from_millis(50));
}
Err(_) => break,
}
}
println!("[ProcessManager] 已停止进程: {}", id);
}
}