Queued tab activations keep the file access scope they were asked in

A document asked for again while a dialog was open was queued by path
only, and opened later in the scope of the frame that ran the queue. A
request confined to automation roots could then read a file outside
them, if the document was closed meanwhile and its path became a link
out of the roots. Each queued activation now carries its ScopeHandle
and runs in it, as background opens already do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ruchirguitar
2026-11-04 08:13:04 +07:00
committed by bflatastic
co-authored by Claude Opus 5.5
parent 8f66e2b694
commit 72cf1a9b3c
3 changed files with 36 additions and 6 deletions
+3 -2
View File
@@ -282,8 +282,9 @@ pub(crate) fn open_file(app: &mut VectorcraftApp, path: &str, background: bool)
// a drag acts on the active document, its tab comes to the front once that ends
// ([`crate::opening::poll`]), as a file that finishes opening then does.
if document_index_for_path(app, path).is_some() && !crate::opening::may_switch(app) {
if !app.opening.activate.iter().any(|p| same_open_path(p, path)) {
app.opening.activate.push(path.to_string());
if !app.opening.activate.iter().any(|(p, _)| same_open_path(p, path)) {
let scope = vectorcraft_engine::file_access::ScopeHandle::capture();
app.opening.activate.push((path.to_string(), scope));
}
return Ok(Value::Null);
}
+4 -4
View File
@@ -30,8 +30,8 @@ pub struct Pending {
pub struct Opening {
pub pending: Vec<Pending>,
/// Open documents asked for again while the active one couldn't change: their tabs come to
/// the front once it can ([`may_switch`]).
pub activate: Vec<String>,
/// the front once it can ([`may_switch`]), each in the file access scope it was asked in.
pub activate: Vec<(String, ScopeHandle)>,
}
/// Is a long operation running that should show the busy cursor and status spinner (background
@@ -106,9 +106,9 @@ pub fn poll(app: &mut VectorcraftApp) {
if !may_switch(app) {
return;
}
for path in std::mem::take(&mut app.opening.activate) {
for (path, scope) in std::mem::take(&mut app.opening.activate) {
// Closed meanwhile, it opens again; a failure is reported to the user there.
let _ = crate::io::open_reporting(app, &path);
let _ = scope.run(|| crate::io::open_reporting(app, &path));
}
let mut i = 0;
while let Some(p) = app.opening.pending.get(i) {
+29
View File
@@ -409,3 +409,32 @@ fn reopening_while_a_dialog_is_open_waits_for_it_to_close() {
assert_eq!((app.session.documents().len(), app.session.active_index()), (2, Some(0)));
let _ = std::fs::remove_dir_all(d);
}
/// A document asked for under automation roots while a dialog is open comes to the front, or
/// opens again, in those roots once the dialog closes, not in the scope of the frame that runs it
/// (#5369): closed meanwhile and now a link to a file outside the roots, it is not read.
#[cfg(unix)]
#[test]
fn a_queued_reopen_keeps_the_roots_it_was_asked_in() {
use vectorcraft_engine::file_access;
let (mut app, d, _) = saved_and_another("queued-roots");
let (inside, outside) = (d.join("inside"), d.join("outside"));
std::fs::create_dir_all(&inside).unwrap();
std::fs::create_dir_all(&outside).unwrap();
let path = inside.join("doc.vectorcraft").to_string_lossy().to_string();
app.run("document.activate", json!({"index": 0})).unwrap();
app.run("file.saveAs", json!({"path": path})).unwrap();
app.run("document.activate", json!({"index": 1})).unwrap();
app.ui.dialog = Some(crate::state::Dialog::new(crate::dialogs::save_options::KIND, json!({})));
let roots = file_access::AutomationRoots::new(&[inside.as_path()], &[]).unwrap();
assert_eq!(file_access::confine(roots.as_ref(), || crate::io::open_reporting(&mut app, &path)).unwrap(), serde_json::Value::Null);
// Closed, and its file swapped for a link to one outside the roots.
app.session.execute("file.close", &json!({"index": 0})).unwrap();
let away = outside.join("doc.vectorcraft");
std::fs::rename(&path, &away).unwrap();
std::os::unix::fs::symlink(&away, &path).unwrap();
app.ui.dialog = None;
crate::opening::poll(&mut app);
assert_eq!(app.session.documents().len(), 1, "the file outside the roots is not opened");
let _ = std::fs::remove_dir_all(d);
}