Files
Genarrative/apps/ai-game-creator-shell/src-tauri/src/browser/process.rs
T
kdletters bdf7ed288a 放宽直连游戏验收并增加运行时素材证据
- 将固定四切片与固定 drawImage 降为推荐路径

- 在导航前注入 Canvas/WebGL 平台素材运行时观测

- 对缺少核心素材渲染证据的 desktop/mobile 视口回灌同一 Codex thread 整改

- 补充直连验收、浏览器脚本与真实 Chrome fixture 测试

- 同步 AGC 直连 Runtime 实施计划
2026-08-18 13:14:45 +08:00

121 lines
4.3 KiB
Rust

use std::fs;
use std::path::PathBuf;
use std::time::Duration;
use chromiumoxide::browser::{Browser, BrowserConfig};
use futures::StreamExt;
use tempfile::{Builder as TempDirBuilder, TempDir};
use super::cdp::run_browser_validation;
use super::discovery::discover_chrome_or_edge;
use super::evidence::{
browser_validation_result_for_report, prepare_evidence_root, unix_time_ms, write_json_report,
};
use super::model::{BrowserValidationInput, BrowserValidationResult, BROWSER_TIMEOUT};
use super::network_policy::{preview_proxy_bypass_list, validate_input};
fn browser_process_temp_root() -> PathBuf {
#[cfg(unix)]
{
PathBuf::from("/tmp")
}
#[cfg(not(unix))]
{
std::env::temp_dir()
}
}
pub(super) fn create_browser_process_temp_dir() -> Result<TempDir, String> {
TempDirBuilder::new()
.prefix("ga-browser-")
.tempdir_in(browser_process_temp_root())
.map_err(|error| format!("创建浏览器临时目录失败:{error}"))
}
pub async fn validate_local_preview_in_browser(
input: BrowserValidationInput,
) -> Result<BrowserValidationResult, String> {
validate_local_preview_in_browser_with_interaction(input, false).await
}
/// Runs the same restricted Chromium validation, optionally adding one bounded,
/// advisory click on a visible enabled control. The interaction is evidence for
/// the direct Codex reviewer; it is not a fixed gameplay contract and never
/// changes the deterministic browser safety checks.
pub async fn validate_local_preview_in_browser_with_interaction(
input: BrowserValidationInput,
advisory_interaction: bool,
) -> Result<BrowserValidationResult, String> {
let preview_url = validate_input(&input)?;
prepare_evidence_root(&input.evidence_root)?;
let browser_executable = discover_chrome_or_edge()?;
let browser_temp = create_browser_process_temp_dir()?;
let profile_path = browser_temp.path().join("profile");
fs::create_dir(&profile_path)
.map_err(|error| format!("创建浏览器临时 Profile 失败:{error}"))?;
let browser_temp_path = browser_temp.path().to_string_lossy().into_owned();
let proxy_bypass_list = preview_proxy_bypass_list(&preview_url);
let config = BrowserConfig::builder()
.chrome_executable(&browser_executable.executable_path)
.user_data_dir(profile_path)
.env("TMPDIR", browser_temp_path)
.new_headless_mode()
.enable_request_intercept()
.disable_cache()
.disable_https_first()
.request_timeout(BROWSER_TIMEOUT)
.launch_timeout(BROWSER_TIMEOUT)
.window_size(1280, 720)
.arg(("proxy-server", "http://127.0.0.1:9"))
.arg(("proxy-bypass-list", proxy_bypass_list.as_str()))
.arg("block-new-web-contents")
.arg("deny-permission-prompts")
.arg("disable-notifications")
.arg("disable-service-worker")
.build()
.map_err(|error| format!("构建浏览器配置失败:{error}"))?;
let (mut browser, mut handler) = tokio::time::timeout(BROWSER_TIMEOUT, Browser::launch(config))
.await
.map_err(|_| "启动浏览器超时".to_string())?
.map_err(|error| format!("启动浏览器失败:{error}"))?;
let handler_task = tokio::spawn(async move {
while let Some(message) = handler.next().await {
if message.is_err() {
break;
}
}
});
let validation = run_browser_validation(
&browser,
&browser_executable,
&preview_url,
&input,
advisory_interaction,
)
.await;
let close_result = browser
.close()
.await
.map_err(|error| format!("关闭浏览器失败:{error}"));
let wait_result = tokio::time::timeout(Duration::from_secs(5), browser.wait()).await;
handler_task.abort();
let _ = handler_task.await;
drop(browser_temp);
let mut result = validation?;
close_result?;
match wait_result {
Ok(Ok(_)) => {}
Ok(Err(error)) => return Err(format!("等待浏览器退出失败:{error}")),
Err(_) => return Err("等待浏览器退出超时".to_string()),
}
result.completed_at_unix_ms = unix_time_ms();
let persisted_result = browser_validation_result_for_report(&result)?;
write_json_report(&result.evidence.report_path, &persisted_result)?;
Ok(result)
}