M12: a portfolio's initial document waits for a prompt, picker or front-bound open in progress, so a password asked for a file opened with it is kept (#1652)

This commit is contained in:
Eric Kaden
2026-10-29 04:08:31 -04:00
parent d9127da2e0
commit a9c3d84273
2 changed files with 51 additions and 1 deletions
+10 -1
View File
@@ -430,7 +430,16 @@ impl PdfCraftApp {
/// Leaving the tab gives back the panel the list replaced, unless the reader changed panels
/// meanwhile. Called every frame.
pub(crate) fn present_portfolios(&mut self) {
if let Some((doc, index)) = self.portfolio_initial.take()
// A prompt or picker over the window belongs to the document it was raised for (the
// password of a file opened together with the portfolio, say): the initial document
// waits for its answer, and opening it then would close the prompt. A file still
// opening in the background comes to the front when it arrives, so it is waited for too.
#[cfg(not(target_arch = "wasm32"))]
let busy = self.modal_open() || self.pickers.pending() || self.pending_opens.iter().any(|p| p.activate);
#[cfg(target_arch = "wasm32")]
let busy = self.modal_open();
if !busy
&& let Some((doc, index)) = self.portfolio_initial.take()
&& self.active_ids().is_some_and(|(_, id)| id == doc)
{
self.attachment_action(doc, index, true);
+41
View File
@@ -22,6 +22,21 @@ const NOTED: &[u8] = b"%PDF-1.7
trailer << /Root 1 0 R >>
%%EOF";
/// [`PAGE`] encrypted, opening with the password `pw`.
fn locked() -> Vec<u8> {
let mut doc = pdfcraft_cos::Document::open(std::sync::Arc::new(PAGE.to_vec())).unwrap();
doc.set_encryption(&pdfcraft_cos::NewEncryption {
algorithm: pdfcraft_cos::Algorithm::Aes256,
user_password: "pw",
owner_password: "owner",
permissions: -1,
encrypt_metadata: true,
seed: [4; 32],
})
.unwrap();
pdfcraft_cos::write_full(&doc, &Default::default()).unwrap()
}
/// A one-page portfolio of `files` (name, bytes), with `catalog` (its `/Collection` and any other
/// entries) in its catalog.
fn portfolio(catalog: &str, files: &[(&str, &[u8])]) -> Vec<u8> {
@@ -142,6 +157,32 @@ fn a_file_opened_after_the_portfolio_keeps_the_front() {
assert_eq!(h.state().right, Some(RightPanel::Attachments));
}
/// A password asked for a file opened together with the portfolio stays asked: the initial
/// document waits for the answer, then gives way to that file, or opens once the prompt is
/// cancelled.
#[test]
fn a_password_asked_for_a_file_opened_with_the_portfolio_stays_asked() {
for answer in [Some("pw".to_string()), None] {
let pdf = portfolio("/Collection << /D (form.pdf) >>", &[("form.pdf", PAGE)]);
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(move |_cc| {
let mut app = PdfCraftApp::new();
app.set_option("language", "en").unwrap();
app.open_bytes("portfolio.pdf", None, pdf.clone()).unwrap();
app.open_bytes("locked.pdf", None, locked()).unwrap();
app
});
h.run_steps(4);
assert!(h.state().password_prompt.is_some(), "the prompt for locked.pdf is still up");
assert_eq!(tabs(&h).0, ["portfolio.pdf"]);
let opened = if answer.is_some() { "locked.pdf" } else { "form.pdf" };
// The answered file opens in the background worker.
h.state_mut().submit_password(answer);
crate::support::wait_until(&mut h, "a second tab", |app| app.views.len() == 2);
h.run_steps(4);
assert_eq!(tabs(&h), (vec!["portfolio.pdf".into(), opened.into()], Some(opened.into())));
}
}
/// `/View /H` presents the initial document but leaves the file list to the reader.
#[test]
fn a_portfolio_that_keeps_its_list_hidden_still_presents_its_initial_document() {