web: worker-route WBLOCK/SAVEALL/ARCHIVE; close the follow-up review

WBLOCK, SAVEALL, and web ARCHIVE now serialize through the Web Worker
like SAVE/AUTOSAVE: prepare on the main thread, writer pass on the
worker, inline fallback on any worker failure. SAVEALL clears each
tab's dirty flag under the same unchanged-tab race guard the single
save uses; ARCHIVE assembles the ZIP in the completion handler so the
transmittal matches the snapshot taken at command time.

The review of the previous fix commit found the collision refusal
still REPLACED the registry bytes — the exact silent retargeting it
was meant to prevent. The refusal now keeps the old registration, the
extraction settings picker reports refused picks in the wizard's error
slot (previously silent, and the stale collision flag could surface as
a wrong-file warning on an unrelated browse), and the xref registry's
same-name ambiguity is documented as a browser fundamental (native
distinguishes by folder): newest registration wins consistently for
resolution, ARCHIVE, and XRELOAD — matching what the drawing displays.
Also: the Quit pending-close survives a successful web tab-save
(continue the quit instead of dropping it), the last ZIP64 sentinel
(central size) is rejected like the other four, and the new behaviors
gained regression tests (collision keeps old bytes, registry eviction,
including the deadlock fix for the guard-scoping that test exposed).

(cherry picked from commit b635d56c)

Ported onto main (#1717 review): ARCHIVE and SAVEALL serialize
document_for_save() like a single web Save; the ZIP is built with the
zip crate; WBLOCK keeps its native background-task title.
This commit is contained in:
Afonsop authored and Hakan Seven committed 2026-10-12 12:02:04 +03:00
1 parent 8968e79942
commit 108ef045b8
6 files changed
+406 -81

No files matched your search

+46
View File
@@ -122,6 +122,11 @@ fn parsed_cache() -> &'static Mutex<HashMap<String, codec::ReadOutcome>> {
/// drawing's xref references will be matched against. Registering supersedes
/// any eager parse cached for the same file, so a re-pick (or a drop that
/// finally supplies a missing reference) reloads from the new bytes.
///
/// Names are bare on the web, so two DISTINCT same-named drawings cannot be
/// told apart: the newest registration wins for resolution, ARCHIVE, and
/// XRELOAD alike — consistent with what the drawing itself displays, but a
/// fundamental browser limit (native distinguishes them by folder).
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))] // mirrors image_model::register_source
pub fn register_source(name: &str, bytes: Arc<Vec<u8>>) {
{
@@ -3570,6 +3575,47 @@ mod web_registry_tests {
);
}
/// Beyond SOURCE_CAP registrations the oldest leave first, and a
/// re-registration of a live name neither evicts nor duplicates it.
#[test]
fn source_registry_evicts_oldest_beyond_the_cap() {
for i in 0..(SOURCE_CAP + 2) {
register_source(&format!("f{i}.dwg"), Arc::new(vec![i as u8]));
}
let (map_len, order_len, has_newest, has_oldest) = {
// Scoped: the guard must drop before register_source locks again.
let cache = source_cache()
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
(
cache.0.len(),
cache.1.len(),
cache.0.contains_key(&format!("f{}.dwg", SOURCE_CAP + 1)),
cache.0.contains_key("f0.dwg"),
)
};
assert_eq!(map_len, SOURCE_CAP);
assert_eq!(order_len, SOURCE_CAP);
assert!(has_newest);
assert!(!has_oldest);
// Re-registering a live name refreshes its bytes without reordering.
register_source(&format!("f{}.dwg", SOURCE_CAP + 1), Arc::new(vec![255]));
let (order_len, refreshed) = {
let cache = source_cache()
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
(
cache.1.len(),
cache
.0
.get(&format!("f{}.dwg", SOURCE_CAP + 1))
.map(|b| b.to_vec()),
)
};
assert_eq!(order_len, SOURCE_CAP, "no duplicate order entry");
assert_eq!(refreshed, Some(vec![255]));
}
#[test]
fn register_source_supersedes_a_cached_parse_of_the_same_file() {
let name = "Registry-Supersede-Plan.dwg";