summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSaikari <[email protected]>2026-02-25 00:49:57 +0300
committerSaikari <[email protected]>2026-02-25 00:49:57 +0300
commitb54e05603dac61fd4cf226bba84121786a311c8e (patch)
tree47230fe29ce3d5e1878a4ecb9e9ad1ddeb6d4915
parent7c9dea1afe9d8b2345e61dcb36b21aee168435e8 (diff)
Refactor WASM app build logic into a separate function and enhance Emscripten JS handling
-rw-r--r--xmake/rules/qt/xmake.lua198
1 files changed, 101 insertions, 97 deletions
diff --git a/xmake/rules/qt/xmake.lua b/xmake/rules/qt/xmake.lua
index d492da1f8..eef1c9337 100644
--- a/xmake/rules/qt/xmake.lua
+++ b/xmake/rules/qt/xmake.lua
@@ -18,6 +18,106 @@
-- @file xmake.lua
--
+function _build_wasm_app(target, qt, pluginsdir)
+ local targetdir = target:targetdir()
+ local htmlfile = path.join(targetdir, target:basename() .. ".html")
+ if os.isfile(path.join(pluginsdir, "platforms/wasm_shell.html")) then
+ os.vcp(path.join(pluginsdir, "platforms/wasm_shell.html"), htmlfile)
+ io.gsub(htmlfile, "@APPNAME@", target:name())
+ import("core.base.semver")
+ local qt_sdkver = qt.sdkver or target:data("qt_sdkver")
+ if qt_sdkver and semver.new(qt_sdkver):ge("6.0") then
+ io.gsub(htmlfile, "@APPEXPORTNAME@", "createQtAppInstance")
+ local preload = ""
+ -- @see https://github.com/xmake-io/xmake/issues/6182
+ local preloadfiles = target:values("wasm.preloadfiles")
+ if preloadfiles then
+ local filelist = {}
+ for _, preloadfile in ipairs(preloadfiles) do
+ table.insert(filelist, string.format("'%s'", path.filename(preloadfile)))
+ end
+ if #filelist > 0 then
+ preload = string.format("preload: [%s],", table.concat(filelist, ", "))
+ end
+ end
+ -- Patch old containerElements (pre-Qt 6.5)
+ io.gsub(htmlfile, "containerElements: %[screen%],", function (w)
+ return w .. " " .. preload
+ end)
+ -- Patch new qtContainerElements (Qt 6.5+)
+ io.gsub(htmlfile, "qtContainerElements: %[screen%],", function (w)
+ return w .. " " .. preload
+ end)
+ io.gsub(htmlfile, "@PRELOAD@", "")
+ -- 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)")
+ -- 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"
+ .. "};\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)
+ os.vcp(path.join(pluginsdir, "platforms/qtlogo.svg"), targetdir)
+ end
+end
+
-- define rule: qt/wasm application
rule("qt._wasm_app")
add_deps("qt.env")
@@ -25,103 +125,7 @@ rule("qt._wasm_app")
local qt = target:data("qt")
local pluginsdir = qt and qt.pluginsdir
if pluginsdir then
- local targetdir = target:targetdir()
- local htmlfile = path.join(targetdir, target:basename() .. ".html")
- if os.isfile(path.join(pluginsdir, "platforms/wasm_shell.html")) then
- os.vcp(path.join(pluginsdir, "platforms/wasm_shell.html"), htmlfile)
- io.gsub(htmlfile, "@APPNAME@", target:name())
- import("core.base.semver")
- local qt_sdkver = qt.sdkver or target:data("qt_sdkver")
- if qt_sdkver and semver.new(qt_sdkver):ge("6.0") then
- io.gsub(htmlfile, "@APPEXPORTNAME@", "createQtAppInstance")
- local preload = ""
- -- @see https://github.com/xmake-io/xmake/issues/6182
- local preloadfiles = target:values("wasm.preloadfiles")
- if preloadfiles then
- local filelist = {}
- for _, preloadfile in ipairs(preloadfiles) do
- table.insert(filelist, string.format("'%s'", path.filename(preloadfile)))
- end
- if #filelist > 0 then
- preload = string.format("preload: [%s],", table.concat(filelist, ", "))
- end
- end
- -- Patch old containerElements (pre-Qt 6.5)
- io.gsub(htmlfile, "containerElements: %[screen%],", function (w)
- return w .. " " .. preload
- end)
- -- Patch new qtContainerElements (Qt 6.5+)
- io.gsub(htmlfile, "qtContainerElements: %[screen%],", function (w)
- return w .. " " .. preload
- end)
- io.gsub(htmlfile, "@PRELOAD@", "")
- -- 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)")
- -- 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)
- os.vcp(path.join(pluginsdir, "platforms/qtlogo.svg"), targetdir)
- end
+ _build_wasm_app(target, qt, pluginsdir)
end
end)