← Commit history

Viewer: reassemble split bundle as an inline script; CSP blocks blob: URLs

John Lauer ·ad0895f6a2 ·2mo ago ·parent e2a8d89
1 file changed +37−18
viewer/index.html+37−18
@@ -1296,37 +1296,56 @@ function loadAlphabet() { }  function loadAdom3D() {-  // The wiki refuses to render any file over ~4 MB ("File too large to-  // render", HTTP 413) and this bundle is 13 MB, so the static export also-  // ships it as 3.5 MB parts and reassembles them here. Falls back to the-  // whole file when the parts are absent (local dev / `adom-tsci start`).+  // Two wiki constraints shape this.+  //+  // 1. The render endpoint 413s ("File too large to render") on files over+  //    ~4 MB and this bundle is 13 MB, so it also ships as 4 byte-exact+  //    parts. Reassemble as BYTES, never as text: a multi-byte character can+  //    straddle a split boundary and decoding per part corrupts it.+  //+  // 2. The page CSP is `script-src 'unsafe-inline' https:`. That permits an+  //    INLINE script but blocks `blob:`, so URL.createObjectURL + script.src+  //    fails with "Loading the script 'blob:null/...' violates ... Content+  //    Security Policy". Setting textContent runs it under 'unsafe-inline'+  //    instead, and an inline script executes synchronously on append, so+  //    there is nothing to await once it is in the DOM.+  //+  // Falls back to the single 13 MB file when the parts are absent, which is+  // the local-dev case (`adom-tsci start`) where no CSP or size cap applies.   return (async () => {-    const PARTS = 4;-    let bytes = null;+    const N = 4;+    let all = null;     try {       const bufs = [];-      for (let i = 0; i < PARTS; i++) {+      for (let i = 0; i < N; i++) {         const r = await fetch('js/adom-3d-viewer.min.js.part' + i);         if (!r.ok) throw new Error('viewer bundle part ' + i + ' -> HTTP ' + r.status);         bufs.push(new Uint8Array(await r.arrayBuffer()));       }       let total = 0;       for (const b of bufs) total += b.length;-      bytes = new Uint8Array(total);-      let off = 0;-      for (const b of bufs) { bytes.set(b, off); off += b.length; }+      all = new Uint8Array(total);+      let o = 0;+      for (const b of bufs) { all.set(b, o); o += b.length; }     } catch (e) {-      bytes = null;  // parts not present -> single-file path below+      all = null;     }-    await new Promise((resolve, reject) => {++    if (all) {       const s = document.createElement('script');-      s.src = bytes-        ? URL.createObjectURL(new Blob([bytes], { type: 'text/javascript' }))-        : 'js/adom-3d-viewer.min.js';-      s.onload = () => resolve();-      s.onerror = () => reject(new Error('Failed to load adom-3d-viewer.min.js'));+      s.textContent = new TextDecoder('utf-8').decode(all);       document.head.appendChild(s);-    });+    } else {+      await new Promise((resolve, reject) => {+        const s = document.createElement('script');+        s.src = 'js/adom-3d-viewer.min.js';+        s.onload = () => resolve();+        s.onerror = () => reject(new Error('Failed to load adom-3d-viewer.min.js'));+        document.head.appendChild(s);+      });+    }++    if (!window.Adom3DViewer) throw new Error('viewer bundle ran but exposed no Adom3DViewer');   })(); }