fix: close the adversarial findings on the save/paste wave

Ported from da950460 (#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 commit da950460)
This commit is contained in:
Afonsop authored and Hakan Seven committed 2026-10-12 12:02:05 +03:00
1 parent 835e8d469f
commit 1c0963b68d
8 files changed
+382 -90

No files matched your search

+19 -2
View File
@@ -1405,12 +1405,25 @@ pub fn collect_entries_with_prev(
/// The filesystem stat is desktop-only by `cfg` (not a runtime check): the
/// wasm binary contains no `std::fs::metadata` call at all, so a web
/// session can never block on — or fail from — a host-filesystem probe.
/// Path-based web references resolve through the file-handle loader and
/// simply stay `NotFound` when unresolvable.
/// Path-based web references resolve through the file-handle loader: a
/// source present in that registry reports Loaded here, and only truly
/// unregistered ones stay `NotFound`.
fn stat_into(entry: &mut ReferenceEntry, raw: &str, base_dir: &Path) {
if raw.is_empty() {
return;
}
// Web (and the native tests exercising it): `resolve_path` probes the
// filesystem, which the browser does not have — consult the source
// registry instead. A registered drawing IS loaded; its byte length
// comes free from the in-memory bytes (no read), and there is no
// mtime to report.
#[cfg(any(target_arch = "wasm32", test))]
if let Some(key) = find_registered_source(raw) {
entry.size_bytes = registered_bytes(&key).map(|b| b.len() as u64);
entry.found_at = Some(key);
entry.status = RefStatus::Loaded;
return;
}
let Some(found) = resolve_path(raw, base_dir) else {
return;
};
@@ -2005,6 +2018,8 @@ fn bind_dwg(
.find(|b| b.handle == br_handle)
.map(|b| b.xref_path.clone())
.unwrap_or_default();
// Native-only path: `resolve_path` reads the real filesystem, so DWG
// bind stays desktop-only (the web has no fs to load a bind source from).
let Some(found) = resolve_path(&saved, base_dir) else {
return Err(bind_failed(name, "file not found"));
};
@@ -2181,6 +2196,8 @@ fn collect_bind_images(
let mut planned: HashMap<String, String> = HashMap::default();
let mut ops: Vec<(Handle, PathBuf, String)> = Vec::new();
for (handle, saved) in &defs {
// Native-only path: `resolve_path` stats the real filesystem (and
// the copy below reads it), so image COLLECT stays desktop-only.
let Some(resolved) = resolve_path(saved, source_dir) else {
return Err(bind_failed(ref_name, format!("image not found: {saved}")));
};