forked from 1b-agent/opencadstudio-upstream
fix: close the adversarial findings on the save/paste wave
Ported fromda950460(#1717). In-place save: - A stale retained handle could receive bytes serialized for a DIFFERENT format (Save As .dwg->.dxf whose re-pick failed wrote DXF bytes through the .dwg handle) or silently save in place while the user was told a copy was downloading. The serialized format now travels to completion (tab-keyed pending map), the write requires a matching handle extension AND a live tab, and a failed re-pick drops the old handle before the download fallback. - Discarding an unsaved tab leaked its handle, and a serialization still in flight could then overwrite the real file the user just declined to save. Discard drops the handle; a tab-gone completion downloads instead of writing. - Overlapping saves had no guard: a wasm in-flight slot refuses a second save with the same localized message, cleared on every exit path. Paste: pasted_image_name built its timestamp from Date::new(0) — a CONSTANT name, so every paste overwrote every earlier paste's image. Real clock, milliseconds, and a collision-suffixed unique name now. Capture: a web capture in a hidden tab, or one with nothing redrawing, could hold the automation slot forever. It fails fast when the page is hidden and gives up after 5 s, on main's frame-parked capture. The XREF manager reports registry-loaded xrefs with their size on the web instead of NotFound. Left out: the always-warm print snapshot (a frames subscription that emits a message on every frame keeps the app redrawing at the display rate), so the browser-menu print wiring from the previous commit goes too — its beforeprint readback printed blank pages on WebGL2. The merge-residue hunks (re-added XREF bind tests, snap endpoint map and 3D-snap gate) belong to code main already has in its own form. (cherry picked from commitda950460)
This commit is contained in:
1 parent
835e8d469f
commit
1c0963b68d
8 files changed
+382
-90
No files matched your search
+46
-20
@@ -131,6 +131,12 @@ pub(super) struct State {
|
||||
/// Whether the parked capture actually waited for a redraw (vs. the
|
||||
/// anti-hang timer). Reported in the capture result as `waited_for_frame`.
|
||||
pub(super) capture_after_redraw: bool,
|
||||
/// Web: when a parked capture gives up. Only a presented frame can be
|
||||
/// read back on wasm, and a hidden or idle tab may never present one, so
|
||||
/// the timer tick fails the capture past this instant instead of letting
|
||||
/// it hold the control slot forever.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(super) capture_deadline: Option<iced::time::Instant>,
|
||||
}
|
||||
|
||||
/// The pending half of an interactive `user_select` operation (`interactive`
|
||||
@@ -1212,6 +1218,11 @@ impl OpenCADStudio {
|
||||
// rendered frame (or the timer fallback) so geometry changes are
|
||||
// visible rather than returning a stale frame.
|
||||
self.control.capture_frame_pending = true;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
self.control.capture_deadline =
|
||||
Some(iced::time::Instant::now() + std::time::Duration::from_secs(5));
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
"stop" => {
|
||||
@@ -1648,30 +1659,45 @@ impl OpenCADStudio {
|
||||
// browser clears the WebGL buffer — so wait for a real frame.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
if !redrew {
|
||||
let expired = self
|
||||
.control
|
||||
.capture_deadline
|
||||
.is_some_and(|deadline| iced::time::Instant::now() >= deadline);
|
||||
if !redrew && !expired && crate::sys::page_visible() {
|
||||
return Task::none();
|
||||
}
|
||||
self.control.capture_frame_pending = false;
|
||||
self.control.capture_after_redraw = true;
|
||||
return Task::done(Message::ControlScreenshot(path, crate::sys::capture_canvas()));
|
||||
}
|
||||
self.control.capture_frame_pending = false;
|
||||
self.control.capture_after_redraw = redrew;
|
||||
let Some(window) = self.main_window else {
|
||||
return Task::done(Message::ControlScreenshot(path, None));
|
||||
};
|
||||
// A minimized window has a 0x0 surface and the renderer panics reading
|
||||
// it back, so report instead of capturing.
|
||||
iced::window::size(window).then(move |size| {
|
||||
let path = path.clone();
|
||||
if size.width <= 0.0 || size.height <= 0.0 {
|
||||
Task::done(Message::ControlScreenshot(path, None))
|
||||
self.control.capture_deadline = None;
|
||||
self.control.capture_after_redraw = redrew;
|
||||
// No frame to read (hidden tab or nothing redrawing): report the
|
||||
// capture as failed rather than hand back a blank canvas.
|
||||
let screenshot = if redrew {
|
||||
crate::sys::capture_canvas()
|
||||
} else {
|
||||
iced::window::screenshot(window).map(move |screenshot| {
|
||||
Message::ControlScreenshot(path.clone(), Some(screenshot))
|
||||
})
|
||||
}
|
||||
})
|
||||
None
|
||||
};
|
||||
Task::done(Message::ControlScreenshot(path, screenshot))
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
self.control.capture_frame_pending = false;
|
||||
self.control.capture_after_redraw = redrew;
|
||||
let Some(window) = self.main_window else {
|
||||
return Task::done(Message::ControlScreenshot(path, None));
|
||||
};
|
||||
// A minimized window has a 0x0 surface and the renderer panics
|
||||
// reading it back, so report instead of capturing.
|
||||
iced::window::size(window).then(move |size| {
|
||||
let path = path.clone();
|
||||
if size.width <= 0.0 || size.height <= 0.0 {
|
||||
Task::done(Message::ControlScreenshot(path, None))
|
||||
} else {
|
||||
iced::window::screenshot(window).map(move |screenshot| {
|
||||
Message::ControlScreenshot(path.clone(), Some(screenshot))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
mod actions;
|
||||
|
||||
Reference in new issue
Block a user