diff options
| author | Saikari <[email protected]> | 2026-02-24 01:41:11 +0300 |
|---|---|---|
| committer | Saikari <[email protected]> | 2026-02-24 01:41:11 +0300 |
| commit | b8bcc5f0d62d310050ebd9e2437b3e499e1a2177 (patch) | |
| tree | 4f050e5b25e2c48a02135bb0f87c78a261abd60e | |
| parent | c9f0816b6cf1633ba5dadc3f0fe84993a7d5311b (diff) | |
Fix Emscripten JS issues for Qt WASM builds and enhance canvas event target handling
| -rw-r--r-- | xmake/rules/qt/load.lua | 9 | ||||
| -rw-r--r-- | xmake/rules/qt/xmake.lua | 60 |
2 files changed, 62 insertions, 7 deletions
diff --git a/xmake/rules/qt/load.lua b/xmake/rules/qt/load.lua index 38618553f..c09294a70 100644 --- a/xmake/rules/qt/load.lua +++ b/xmake/rules/qt/load.lua @@ -522,17 +522,18 @@ function main(target, opt) target:add("shflags", "-s FETCH=1", "-s ERROR_ON_UNDEFINED_SYMBOLS=1", "-s ALLOW_MEMORY_GROWTH=1", "--bind") if qt_sdkver:ge("6.0") then -- @see https://github.com/xmake-io/xmake/issues/4137 - target:add("ldflags", "-s MAX_WEBGL_VERSION=2", "-s WASM_BIGINT=1", "-s ASSERTIONS=0") + -- @see QtWasmHelpers.cmake: qt_internal_setup_wasm_target_properties + target:add("ldflags", "-s MAX_WEBGL_VERSION=2", "-s WASM_BIGINT=1", "-s STACK_SIZE=5MB") target:add("ldflags", "-sASYNCIFY_IMPORTS=qt_asyncify_suspend_js,qt_asyncify_resume_js") -- FS is required for qtloader.js even if we don't use preload files target:add("ldflags", "-s EXPORTED_RUNTIME_METHODS=UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets,FS,callMain,cwrap,ccall,setValue,getValue,UTF8ToString,stringToUTF8") -- @see https://github.com/emscripten-core/emscripten/issues/21844 Export main and emscripten_thread_crashed for qtloader.js - target:add("ldflags", "-s EXPORTED_FUNCTIONS=_main,_malloc,_free") + target:add("ldflags", "-s EXPORTED_FUNCTIONS=_main,_malloc,_free,__embind_initialize_bindings", {force = true}) target:add("ldflags", "-s MODULARIZE=1", "-s EXPORT_NAME=createQtAppInstance") - target:add("shflags", "-s MAX_WEBGL_VERSION=2", "-s WASM_BIGINT=1", "-s ASSERTIONS=0") + target:add("shflags", "-s MAX_WEBGL_VERSION=2", "-s WASM_BIGINT=1", "-s STACK_SIZE=5MB") target:add("shflags", "-sASYNCIFY_IMPORTS=qt_asyncify_suspend_js,qt_asyncify_resume_js") target:add("shflags", "-s EXPORTED_RUNTIME_METHODS=UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets,FS,callMain,cwrap,ccall,setValue,getValue,UTF8ToString,stringToUTF8") - target:add("shflags", "-s EXPORTED_FUNCTIONS=_main,_malloc,_free") + target:add("shflags", "-s EXPORTED_FUNCTIONS=_main,_malloc,_free,__embind_initialize_bindings", {force = true}) target:add("shflags", "-s MODULARIZE=1", "-s EXPORT_NAME=createQtAppInstance") target:set("extension", ".js") else diff --git a/xmake/rules/qt/xmake.lua b/xmake/rules/qt/xmake.lua index f8ad260f7..048f163e4 100644 --- a/xmake/rules/qt/xmake.lua +++ b/xmake/rules/qt/xmake.lua @@ -55,14 +55,68 @@ rule("qt._wasm_app") return w .. " " .. preload end) io.gsub(htmlfile, "@PRELOAD@", "") - -- Fix qt-app.js errors + -- Fix Emscripten JS issues for Qt WASM builds local jsfile = path.join(targetdir, target:basename() .. ".js") if os.isfile(jsfile) then -- Remove "use strict"; to avoid issues with `this` being undefined in strict mode io.gsub(jsfile, "\"use strict\";", "") io.gsub(jsfile, "'use strict';", "") - -- Patch visualViewport access issue if present (undefined this context) - io.gsub(jsfile, "this%.visualViewport", "(typeof window !== 'undefined' ? window.visualViewport : null)") + -- Patch visualViewport access issue if present (undefined this context) + io.gsub(jsfile, "this%.visualViewport", + "(typeof window !== 'undefined' ? window.visualViewport : null)") + -- Guard all document.querySelector(target) calls with try-catch. + -- Qt WASM uses "!" prefixed selectors that are not valid CSS selectors, + -- and corrupted strings from SharedArrayBuffer can also cause SyntaxErrors. + io.gsub(jsfile, "document%.querySelector%(target%)", + "(function(){try{return document.querySelector(target)}catch(e){return null}})()") + -- Replace findCanvasEventTarget with a robust version. + -- Qt WASM registers canvases in Module.specialHTMLTargets with "!" prefixed + -- keys (e.g. "!qtwindow1", "!qtoffscreen_xxx"). With pthreads + + -- ALLOW_MEMORY_GROWTH, Emscripten's UTF8ToString may read corrupted strings + -- from SharedArrayBuffer memory, causing the specialHTMLTargets lookup to fail. + -- Additionally, QWasmOffscreenSurface passes an empty string when + -- OffscreenCanvas is unavailable. The replacement adds a fallback that + -- iterates specialHTMLTargets to find any Qt-registered canvas element. + -- @see https://code.qt.io/cgit/qt/qtbase.git/tree/src/plugins/platforms/wasm/qwasmwindow.cpp + -- @see https://code.qt.io/cgit/qt/qtbase.git/tree/src/plugins/platforms/wasm/qwasmoffscreensurface.cpp + local content = io.readfile(jsfile) + local marker = "var findCanvasEventTarget = target => {" + local pos = content:find(marker, 1, true) + if pos then + -- Find the end of the function by counting matched braces + local depth = 1 + local i = pos + #marker + while i <= #content and depth > 0 do + local c = content:sub(i, i) + if c == "{" then depth = depth + 1 + elseif c == "}" then depth = depth - 1 end + i = i + 1 + end + if content:sub(i, i) == ";" then i = i + 1 end + local new_func = "var findCanvasEventTarget = target => {\n" + .. " target = maybeCStringToJsString(target);\n" + .. " if (specialHTMLTargets[target]) return specialHTMLTargets[target];\n" + .. " if (GL.offscreenCanvases[target]) return GL.offscreenCanvases[target];\n" + .. " if (typeof target === 'string' && target.length > 0) {\n" + .. " var s = target.substr(1);\n" + .. " if (GL.offscreenCanvases[s]) return GL.offscreenCanvases[s];\n" + .. " }\n" + .. " if (target === 'canvas') {\n" + .. " var k = Object.keys(GL.offscreenCanvases);\n" + .. " if (k.length) return GL.offscreenCanvases[k[0]];\n" + .. " }\n" + .. " for (var key in specialHTMLTargets) {\n" + .. " if (typeof key === 'string' && key.charAt(0) === '!') {\n" + .. " var el = specialHTMLTargets[key];\n" + .. " if (el && (el.tagName === 'CANVAS' || (typeof OffscreenCanvas !== 'undefined' && el instanceof OffscreenCanvas))) return el;\n" + .. " }\n" + .. " }\n" + .. " try { return typeof document !== 'undefined' ? document.querySelector(target) : undefined; }\n" + .. " catch(e) { return undefined; }\n" + .. "};" + content = content:sub(1, pos - 1) .. new_func .. content:sub(i) + io.writefile(jsfile, content) + end end end os.vcp(path.join(pluginsdir, "platforms/qtloader.js"), targetdir) |
