mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-15 20:34:36 +00:00
fix(web): open document links with noopener and send COOP same-origin (#5091)
## What this changes On the web build, `PdfCraftApp::open_url` now opens links with `window.open(url, "_blank", "noopener,noreferrer")` via `web-sys`, instead of `egui::OpenUrl::new_tab`. eframe opens new tabs without features, which gave the opened page a `window.opener` handle it could use to navigate the PdfCraft tab. Every app-level open goes through `open_url`: document links, button URI actions, `app.launchURL`, help links and the releases page. Desktop builds are unchanged. The web packaging now sends `Cross-Origin-Opener-Policy: same-origin`: `_headers`, `.htaccess`, and the nginx example in `packaging/web/README.md` (`HOSTING.md`), whose old "doesn't need COOP" note is rewritten. Fixes #5084 ## How you verified it `cargo fmt --check` and `cargo clippy --all-targets -- -D warnings` are clean on `pdfcraft-ui-egui`, including the review fixes. Tests not run yet; CI runs them. `cargo check -p pdfcraft-ui-egui --target wasm32-unknown-unknown` passes too, with no warnings in `links.rs`. - [x] `cargo fmt --check` - [x] clippy with `-D warnings` on the crates I changed - [ ] tests pass on the crates I changed ## Anything a reviewer should look at first - COOP `same-origin` has no effect inside an iframe, so the `postMessage` embedding bridge is unaffected. A page on another origin that opens PdfCraft with `window.open` does lose its window handle. Nothing in the repo does that. - Not covered: `dialogs/credits.rs` uses `ui.hyperlink_to` for fixed GitHub URLs, which on the web may open in the PdfCraft tab itself.
This commit is contained in:
@@ -138,11 +138,25 @@ impl PdfCraftApp {
|
||||
}
|
||||
}
|
||||
|
||||
/// Open a web link in the system browser (a new tab on the web). Only for PdfCraft's own
|
||||
/// links; an address that came from a document goes through [`Self::request_document_url`].
|
||||
/// Open a web link in the system browser (a new `noopener` tab on the web). Only for
|
||||
/// PdfCraft's own links; an address that came from a document goes through
|
||||
/// [`Self::request_document_url`].
|
||||
pub fn open_url(&mut self, url: &str) {
|
||||
if let Some(ctx) = &self.ctx {
|
||||
ctx.open_url(egui::OpenUrl::new_tab(url));
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
if let Some(ctx) = &self.ctx {
|
||||
ctx.open_url(egui::OpenUrl::new_tab(url));
|
||||
}
|
||||
}
|
||||
// eframe's web runner opens new tabs with `window.open(url, "_blank")` and no features,
|
||||
// which hands the opened page `window.opener` and lets it navigate this tab (#5084).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
if let Some(window) = web_sys::window() {
|
||||
// With `noopener` the browser answers `null` instead of the new window, and a
|
||||
// blocked pop-up is `null` or an error: there is nothing to keep or retry.
|
||||
let _ = window.open_with_url_and_target_and_features(url, "_blank", "noopener,noreferrer");
|
||||
}
|
||||
}
|
||||
self.last_opened_url = Some(url.to_string());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Apache (see HOSTING.md). Needs mod_mime; mod_deflate and mod_headers are optional.
|
||||
# Apache (see HOSTING.md). Needs mod_mime and mod_headers (for COOP); mod_deflate is optional.
|
||||
AddType application/wasm .wasm
|
||||
AddType text/javascript .js
|
||||
|
||||
@@ -7,6 +7,7 @@ AddType text/javascript .js
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_headers.c>
|
||||
Header set Cross-Origin-Opener-Policy "same-origin"
|
||||
<FilesMatch "\.(wasm|js)$">
|
||||
Header set Cache-Control "public, max-age=31536000, immutable"
|
||||
</FilesMatch>
|
||||
|
||||
+14
-5
@@ -38,10 +38,17 @@ workspace and Save target stay selected. Click the downloaded file's tab to swit
|
||||
`.js` files, and `no-cache` on `index.html`.
|
||||
- **HTTPS:** WebGPU (and the clipboard) only work in a secure context, which means `https://`
|
||||
or `http://localhost`. Over plain HTTP elsewhere, the app falls back to WebGL2.
|
||||
- **No special isolation headers:** PdfCraft doesn't use `SharedArrayBuffer`, so it doesn't
|
||||
need `Cross-Origin-Opener-Policy` or `Cross-Origin-Embedder-Policy`. If your site already sends
|
||||
COEP `require-corp`, also send `Cross-Origin-Resource-Policy: same-origin` (or `cross-origin`
|
||||
when the files live on a CDN) on the app's files.
|
||||
- **Opener isolation:** send `Cross-Origin-Opener-Policy: same-origin` (the sample `_headers`
|
||||
and `.htaccess` do; Apache needs `mod_headers` for it). Pages opened from a document's links
|
||||
then can't reach or navigate the PdfCraft tab. The app opens links with `noopener` already;
|
||||
the header also cuts the opener for pages on another origin opened by any other means. A page
|
||||
on the same origin that sends the same policy keeps its opener. The header has no effect
|
||||
inside an iframe, so the embedding bridge below is unaffected, but a page on another origin
|
||||
that opens PdfCraft with `window.open` loses its handle to that window.
|
||||
- **No cross-origin isolation:** PdfCraft doesn't use `SharedArrayBuffer`, so it doesn't need
|
||||
`Cross-Origin-Embedder-Policy`. If your site already sends COEP `require-corp`, also send
|
||||
`Cross-Origin-Resource-Policy: same-origin` (or `cross-origin` when the files live on a CDN) on
|
||||
the app's files.
|
||||
|
||||
nginx example:
|
||||
|
||||
@@ -50,8 +57,10 @@ location /pdfcraft/ {
|
||||
types { application/wasm wasm; text/javascript js; text/html html; }
|
||||
gzip on;
|
||||
gzip_types application/wasm text/javascript text/html;
|
||||
add_header Cross-Origin-Opener-Policy "same-origin";
|
||||
location ~* \.(wasm|js)$ { add_header Cache-Control "public, max-age=31536000, immutable"; }
|
||||
location ~* index\.html$ { add_header Cache-Control "no-cache"; }
|
||||
# A location with its own add_header drops the outer ones, so the page repeats COOP.
|
||||
location ~* index\.html$ { add_header Cache-Control "no-cache"; add_header Cross-Origin-Opener-Policy "same-origin"; }
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Netlify / Cloudflare Pages header rules (see HOSTING.md). Other hosts: set the same headers.
|
||||
/*
|
||||
X-Content-Type-Options: nosniff
|
||||
Cross-Origin-Opener-Policy: same-origin
|
||||
|
||||
/*.wasm
|
||||
Content-Type: application/wasm
|
||||
|
||||
Reference in New Issue
Block a user