ElyMark: the print page is staged off the window thread (M-14)
paper::stage takes the background executor and inlines images, reads the style sheet and writes the page there. A gpui test checks nothing is written before the executor runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ca5ac5bb45
commit
6247771f24
5 files changed
+62
-15
No files matched your search
+10
-14
@@ -15,7 +15,7 @@ use ely_ui::{commands, i18n::text};
|
||||
use ely_web_content::{Event, WebView};
|
||||
use gpui::{
|
||||
AnyElement, App, AppContext as _, Context, Entity, IntoElement as _, ParentElement as _,
|
||||
Subscription, Window,
|
||||
Subscription, Task, Window,
|
||||
};
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -62,7 +62,7 @@ pub struct Lost {
|
||||
}
|
||||
|
||||
impl Lost {
|
||||
fn of(images: Vec<String>, rendered: &Value) -> Lost {
|
||||
pub fn of(images: Vec<String>, rendered: &Value) -> Lost {
|
||||
let diagrams = rendered["failed"].as_array().into_iter().flatten();
|
||||
let formulas = rendered["formulas"].as_array().into_iter().flatten();
|
||||
Lost {
|
||||
@@ -280,19 +280,15 @@ impl Desk {
|
||||
"html",
|
||||
move |rendered, cx: &mut App| {
|
||||
let staged = work.join(format!("print-{n}.html"));
|
||||
let writing = staged.clone();
|
||||
let page = match rendered {
|
||||
Ok(rendered) => {
|
||||
let print = paper::Print { folder, style, name, page: staged.clone() };
|
||||
paper::stage(cx.background_executor(), rendered, print)
|
||||
}
|
||||
Err(why) => Task::ready(Err(io::Error::other(why))),
|
||||
};
|
||||
cx.spawn(async move |cx| {
|
||||
// Images inline and the page writes off the window thread.
|
||||
let page = cx
|
||||
.background_spawn(async move {
|
||||
rendered.map_err(io::Error::other).and_then(|rendered| {
|
||||
let (html, images) = paper::Marked::read(&rendered)?.inline(&folder);
|
||||
let style = format!("<style>{}</style>", std::fs::read_to_string(&style)?);
|
||||
std::fs::write(&writing, paper::page(&name, &style, &html))?;
|
||||
Ok(Lost::of(images, &rendered))
|
||||
})
|
||||
})
|
||||
.await;
|
||||
let page = page.await;
|
||||
in_window(&desk, cx, |desk, window, cx| match page {
|
||||
Ok(lost) => {
|
||||
let job = Job {
|
||||
|
||||
@@ -85,6 +85,28 @@ fn kind(path: &Path) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
/// What the print page is made of.
|
||||
pub struct Print {
|
||||
pub folder: std::path::PathBuf,
|
||||
pub style: std::path::PathBuf,
|
||||
pub name: String,
|
||||
pub page: std::path::PathBuf,
|
||||
}
|
||||
|
||||
/// Writes the print page off the window thread, images inline.
|
||||
pub fn stage(
|
||||
background: &gpui::BackgroundExecutor,
|
||||
rendered: serde_json::Value,
|
||||
print: Print,
|
||||
) -> gpui::Task<std::io::Result<crate::export::Lost>> {
|
||||
background.spawn(async move {
|
||||
let (html, images) = Marked::read(&rendered)?.inline(&print.folder);
|
||||
let style = format!("<style>{}</style>", std::fs::read_to_string(&print.style)?);
|
||||
std::fs::write(&print.page, page(&print.name, &style, &html))?;
|
||||
Ok(crate::export::Lost::of(images, &rendered))
|
||||
})
|
||||
}
|
||||
|
||||
/// The page's HTML; image `src` values replaced by numbered tokens.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Marked {
|
||||
@@ -222,6 +244,31 @@ mod tests {
|
||||
assert!(Marked::read(&serde_json::json!({ "html": "<p>" })).is_err());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_print_page_is_written_off_the_window_thread(cx: &mut gpui::TestAppContext) {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
std::fs::write(dir.path().join("a.png"), b"png").expect("an image");
|
||||
std::fs::write(dir.path().join("export.css"), "p {}").expect("a style");
|
||||
let rendered = serde_json::json!({
|
||||
"html": "<p>hi</p><img src=\"T0\"><img src=\"T1\">",
|
||||
"token": "T",
|
||||
"images": ["a.png", "gone.png"],
|
||||
"failed": [3],
|
||||
});
|
||||
let print = Print {
|
||||
folder: dir.path().into(),
|
||||
style: dir.path().join("export.css"),
|
||||
name: "notes".into(),
|
||||
page: dir.path().join("print-1.html"),
|
||||
};
|
||||
let staging = stage(&cx.executor(), rendered, print);
|
||||
assert!(!dir.path().join("print-1.html").exists(), "staged on the window thread");
|
||||
let lost = cx.foreground_executor().block_test(staging).expect("staged");
|
||||
let written = std::fs::read_to_string(dir.path().join("print-1.html")).expect("the page");
|
||||
assert!(written.contains("<style>p {}</style>") && written.contains("data:image/png;base64,cG5n"));
|
||||
assert_eq!(format!("{lost:?}"), r#"Lost { images: ["gone.png"], diagrams: [3], formulas: [] }"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn titles_escape() {
|
||||
assert!(page("a<b>&", "", "").contains("<title>a<b>&</title>"));
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@
|
||||
- [x] M-11 页面把搜索词写进应用日志 — 控制台的文字与来源都可由文档伪造,只记行号;页面自己的诊断经 elyQuery;R1 前缀可伪造、R2 data 来源、R3 sourceURL、R4 编码路径与目录、R5 缺回归测试:已补并经变异核对(五轮上限,未再审)
|
||||
- [x] M-12 浏览器创建完成前 `WebView` 已释放,浏览器泄漏 — 浏览器在泵里同步建好即登记,视图要么在建前已走、要么有浏览器可关;webprobe 加建前丢弃与 0–27 毫秒丢弃,35 页都在保持前释放(审查 4 轮)
|
||||
- [x] M-13 编辑器构建失败时页面空白且不上报 — 可视建不成退回源码并告知,两者都建不成即报失败、留下文字、模式请求重建、不留容器;新测 mark-broken.mjs(审查 2 轮)
|
||||
- [ ] M-14 `render_pdf` 在界面线程内嵌图片并写文件
|
||||
- [x] M-14 `render_pdf` 在界面线程内嵌图片并写文件 — 打印页的内嵌、读样式与写入经 paper::stage 在后台执行,GPUI 单测证明执行器运行前未写(审查 2 轮)
|
||||
- [ ] M-15 打印与导出 PDF 的暂存页执行文档里的脚本
|
||||
- [ ] G-01 打开“文件历史”或“逐行追溯”后,中间区不再响应任何别的选择
|
||||
- [ ] G-02 进行中操作读取失败被吞掉;`git am` 冲突无提示条无错误
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
FAIL
|
||||
|
||||
apps/ely-mark/src/export.rs:287 — 缺少 M-14 的后台暂存回归测试。具体失败场景:把此处 `background_spawn(...).await` 改回前台直接执行,图片内嵌、样式读取及暂存页写入重新阻塞界面,现有测试仍全绿。本轮在隔离副本中编译并运行该变异:Windows 的 22 项适用测试、WSL 的全部 24 项测试均通过;现有 `mark-output.sh` 只检查输出及打印作业,没有线程或界面响应断言。额外的 GPUI 暂存片段探针在当前代码上通过六种成功、失败场景,在该变异上明确报出 `staging ran on foreground`,证明现有测试漏掉了本项针对的行为(UX-05、PERF-02)。修复方向:补充执行实际暂存调度路径的 GPUI 回归测试,验证耗时准备在后台执行、结果交回前台处理,撤掉后台调度时必须失败。证据:`.work/M-14-r1/{windows,linux}-foreground-mutant-tests.log`、`{windows,linux}-staging-{current,foreground-mutant}-tests.log`。探针副本及其独立构建目录已删除;未运行 macOS 窗口测试。
|
||||
@@ -0,0 +1 @@
|
||||
PASS
|
||||
Reference in new issue
Block a user