mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-28 22:17:48 +02:00
HTML attachment previews mount the document inside a sandboxed `<iframe srcdoc>` deliberately WITHOUT `allow-same-origin` — uploads are untrusted user content. Chromium treats fragment-link clicks inside such an opaque-origin srcdoc iframe as cross-origin frame navigation and silently rejects them, so clicking a TOC entry never scrolls. Append a tiny shim script to the srcdoc that intercepts `<a href="#...">` clicks inside the iframe and calls `scrollIntoView` directly. The shim runs in the iframe's own opaque origin under `allow-scripts` — no new capabilities, no sandbox token changes; it cannot reach parent / cookies / localStorage. All three HTML attachment surfaces share the same helper: - inline 480px card (html-attachment-preview.tsx) - full-screen modal (attachment-preview-modal.tsx) - full-page route (attachment-preview-page.tsx) References: whatwg/html#3537, crbug 40191760. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>
53 lines
2.3 KiB
TypeScript
53 lines
2.3 KiB
TypeScript
/**
|
|
* Fragment-navigation shim for sandboxed HTML attachment iframes.
|
|
*
|
|
* HTML attachment previews mount the user-supplied document inside a
|
|
* `<iframe sandbox="allow-scripts" srcdoc={...}>` — deliberately WITHOUT
|
|
* `allow-same-origin`, because the source is untrusted user upload and
|
|
* same-origin would let it reach cookies / localStorage / parent.document.
|
|
*
|
|
* That security posture has a side effect: in Chromium, a sandboxed srcdoc
|
|
* iframe sits in an opaque origin, and the browser treats clicks on
|
|
* `<a href="#section">` as cross-origin frame navigation — silently rejected,
|
|
* no scroll, no error. See whatwg/html#3537 and crbug 40191760; it's a spec +
|
|
* implementation consensus, not a bug we can wait out.
|
|
*
|
|
* The fix is in-iframe: append a tiny script to the document that listens for
|
|
* fragment-link clicks and calls `scrollIntoView` itself. The script runs in
|
|
* the iframe's own opaque origin — same capabilities the user's HTML already
|
|
* has under `allow-scripts`; it cannot reach the parent. The shim only
|
|
* intercepts `href="#..."` clicks, defers to any preventDefault handler the
|
|
* user's HTML installed, and stays out of the way when the target id is
|
|
* missing (so SPA / tab-style routers in the document can still handle it).
|
|
*/
|
|
const FRAGMENT_NAV_SHIM = `<script>
|
|
(function(){
|
|
document.addEventListener('click', function(e) {
|
|
if (e.defaultPrevented) return;
|
|
var t = e.target;
|
|
if (!t || typeof t.closest !== 'function') return;
|
|
var a = t.closest('a[href]');
|
|
if (!a) return;
|
|
var href = a.getAttribute('href');
|
|
if (!href || href.charAt(0) !== '#' || href === '#') return;
|
|
var id;
|
|
try { id = decodeURIComponent(href.slice(1)); } catch (_) { return; }
|
|
if (!id) return;
|
|
var dest = document.getElementById(id);
|
|
if (!dest && typeof CSS !== 'undefined' && CSS.escape) {
|
|
dest = document.querySelector('a[name="' + CSS.escape(id) + '"]');
|
|
}
|
|
if (!dest) return;
|
|
e.preventDefault();
|
|
dest.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
});
|
|
})();
|
|
</script>`;
|
|
|
|
export function withFragmentNavShim(html: string | undefined): string {
|
|
return (html ?? "") + FRAGMENT_NAV_SHIM;
|
|
}
|
|
|
|
/** Exposed for unit tests so they can assert the shim was appended verbatim. */
|
|
export const __FRAGMENT_NAV_SHIM__ = FRAGMENT_NAV_SHIM;
|