diff --git a/mobile/extraction/PyodideWebView.tsx b/mobile/extraction/PyodideWebView.tsx index 7b18f45..d69d7c9 100644 --- a/mobile/extraction/PyodideWebView.tsx +++ b/mobile/extraction/PyodideWebView.tsx @@ -77,40 +77,47 @@ var initError = null; try { _post({ type: 'progress', msg: 'Loading Python runtime…' }); - // Chrome <63 (e.g. Karoo WebView 61) cannot parse the dynamic import() - // syntax that Pyodide uses to load pyodide.asm.js. The parser rejects - // the whole file before it runs, so loadPyodide is never defined. - // - // Fix: fetch pyodide.js as text, replace every "import(" with a - // script-tag–based loader that Chrome 61 can execute, then inject the - // patched code via a Blob URL. The Blob script is never parsed by the - // module-aware pre-scanner, so the import keyword is invisible to it. - window.__loadScript = function(url) { - return new Promise(function(res, rej) { + // Chrome <63 cannot parse dynamic import() — a parse-time SyntaxError + // prevents loadPyodide from ever being defined. Detect this once and + // use a patched loader only for those old WebViews. + var chromeVer = (navigator.userAgent.match(/Chrome\\/([0-9]+)/) || [])[1]; + var needsPatch = chromeVer && parseInt(chromeVer) < 63; + + if (needsPatch) { + // Fetch pyodide.js as text and replace every "import(" with a + // script-tag shim before injecting via Blob URL. Using split/join + // avoids regex escape sequences, which template literals corrupt. + // Node.js import("path") calls are guarded by IN_NODE and never run. + window.__loadScript = function(url) { + return new Promise(function(res, rej) { + var s = document.createElement('script'); + s.src = url; + s.onload = res; + s.onerror = function() { rej(new Error('Failed to load ' + url)); }; + document.head.appendChild(s); + }); + }; + var pyResp = await fetch(_CDN + 'pyodide.js'); + if (!pyResp.ok) throw new Error('Could not fetch pyodide.js (' + pyResp.status + ')'); + var pyCode = await pyResp.text(); + pyCode = pyCode.split('import(').join('__loadScript('); + await new Promise(function(res, rej) { + var blob = new Blob([pyCode], { type: 'application/javascript' }); + var blobUrl = URL.createObjectURL(blob); var s = document.createElement('script'); - s.src = url; - s.onload = res; - s.onerror = function() { rej(new Error('Failed to load ' + url)); }; + s.src = blobUrl; + s.onload = function() { URL.revokeObjectURL(blobUrl); res(); }; + s.onerror = function() { URL.revokeObjectURL(blobUrl); rej(new Error('Failed to execute patched pyodide.js')); }; document.head.appendChild(s); }); - }; - - var pyResp = await fetch(_CDN + 'pyodide.js'); - if (!pyResp.ok) throw new Error('Could not fetch pyodide.js (' + pyResp.status + ')'); - var pyCode = await pyResp.text(); - // Replace all dynamic import() calls. Node.js-only paths (importing - // "path", "fs", etc.) are guarded by IN_NODE and never run in WebView. - pyCode = pyCode.replace(/\bimport\(/g, '__loadScript('); - - await new Promise(function(res, rej) { - var blob = new Blob([pyCode], { type: 'application/javascript' }); - var blobUrl = URL.createObjectURL(blob); - var s = document.createElement('script'); - s.src = blobUrl; - s.onload = function() { URL.revokeObjectURL(blobUrl); res(); }; - s.onerror = function() { URL.revokeObjectURL(blobUrl); rej(new Error('Failed to execute patched pyodide.js')); }; - document.head.appendChild(s); - }); + } else { + await new Promise(function(res, rej) { + var s = document.createElement('script'); + s.src = _CDN + 'pyodide.js'; + s.onload = res; s.onerror = rej; + document.head.appendChild(s); + }); + } pyodide = await loadPyodide({ indexURL: _CDN });