Always export and link in JSEvents and specialHTMLTargets

The two symbols are linked in & exported by using a publicly
visible symbol emscripten_set_wheel_callback. To actually link it,
but avoid calling it, a volatile false boolean flag is used in a
guarding if.

This should ideally be done by setting DEFAULT_LIBRARY_FUNCS_TO_INCLUDE,
but a cmake bug (see QTBUG-108444) is preventing this.

Fixes: QTBUG-108423
Task-number: QTBUG-108444
Change-Id: I6b0406d1059dcec63b3942468e210c53292ffe90
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
Mikolaj Boc 2022-11-14 15:23:22 +01:00
parent ea7a3105de
commit c56bd31f23
4 changed files with 25 additions and 84 deletions

View File

@ -86,7 +86,7 @@ endfunction()
function(_qt_internal_add_wasm_extra_exported_methods target)
get_target_property(wasm_extra_exported_methods "${target}" QT_WASM_EXTRA_EXPORTED_METHODS)
set(wasm_default_exported_methods "UTF16ToString,stringToUTF16,JSEvents")
set(wasm_default_exported_methods "UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets")
if(NOT wasm_extra_exported_methods)
set(wasm_extra_exported_methods ${QT_WASM_EXTRA_EXPORTED_METHODS})

View File

@ -7,6 +7,7 @@
#include <QtCore/qfile.h>
#include <emscripten/bind.h>
#include <emscripten/emscripten.h>
#include <emscripten/html5.h>
#include <cstdint>
#include <iostream>
@ -16,6 +17,21 @@ QT_BEGIN_NAMESPACE
namespace qstdweb {
static void usePotentialyUnusedSymbols()
{
// Using this adds a reference on JSEvents and specialHTMLTargets which are always exported.
// This hack is needed as it is currently impossible to specify a dollar sign in
// target_link_options. The following is impossible:
// DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$JSEvents
// TODO(mikolajboc): QTBUG-108444, review this when cmake gets fixed.
// Volatile is to make this unoptimizable, so that the function is referenced, but is not
// called at runtime.
volatile bool doIt = false;
if (doIt)
emscripten_set_wheel_callback(NULL, 0, 0, NULL);
}
Q_CONSTRUCTOR_FUNCTION(usePotentialyUnusedSymbols)
typedef double uint53_t; // see Number.MAX_SAFE_INTEGER
namespace {
enum class CallbackType {

View File

@ -77,20 +77,11 @@ QWasmScreen::QWasmScreen(const emscripten::val &containerOrCanvas)
event.call<void>("preventDefault");
});
// Create "specialHTMLTargets" mapping for the canvas. Normally, Emscripten
// uses the html element id when targeting elements, for example when registering
// event callbacks. However, this approach is limited to supporting single-document
// apps/ages only, since Emscripten uses the main document to look up the element.
// As a workaround for this, Emscripten supports registering custom mappings in the
// "specialHTMLTargets" object. Add a mapping for the canvas for this screen.
//
// This functionality is gated on "specialHTMLTargets" being available as a module
// property. One way to ensure this is the case is to add it to EXPORTED_RUNTIME_METHODS.
// Qt does not currently do this by default since if added it _must_ be used in order
// to avoid an undefined reference error at startup, and there are cases when Qt won't use
// it, for example if QGuiApplication is not usded.
if (hasSpecialHtmlTargets())
emscripten::val::module_property("specialHTMLTargets").set(canvasSpecialHtmlTargetId(), m_canvas);
// Create "specialHTMLTargets" mapping for the canvas - the element might be unreachable based
// on its id only under some conditions, like the target being embedded in a shadow DOM or a
// subframe.
emscripten::val::module_property("specialHTMLTargets")
.set(canvasTargetId().toStdString(), m_canvas);
// Install event handlers on the container/canvas. This must be
// done after the canvas has been created above.
@ -104,9 +95,8 @@ QWasmScreen::~QWasmScreen()
{
Q_ASSERT(!m_compositor); // deleteScreen should have been called to remove this screen
if (hasSpecialHtmlTargets())
emscripten::val::module_property("specialHTMLTargets")
.set(canvasSpecialHtmlTargetId(), emscripten::val::undefined());
emscripten::val::module_property("specialHTMLTargets")
.set(canvasTargetId().toStdString(), emscripten::val::undefined());
m_canvas.set(m_canvasResizeObserverCallbackContextPropertyName, emscripten::val(intptr_t(0)));
}
@ -158,73 +148,11 @@ QString QWasmScreen::canvasId() const
return QWasmString::toQString(m_canvas["id"]);
}
// Returns the canvas _target_ id, for use with Emscripten's event registration
// functions. This either based on the id registered in specialHtmlTargets, or
// on the canvas id.
QString QWasmScreen::canvasTargetId() const
{
if (hasSpecialHtmlTargets())
return QString::fromStdString(canvasSpecialHtmlTargetId());
else
return QStringLiteral("#") + canvasId();
}
std::string QWasmScreen::canvasSpecialHtmlTargetId() const
{
// Return a globally unique id for the canvas. We can choose any string,
// as long as it starts with a "!".
return std::string("!qtcanvas_") + std::to_string(uintptr_t(this));
}
namespace {
// Compare Emscripten versions, returns > 0 if a is greater than b.
int compareVersionComponents(int a, int b)
{
return a >= 0 && b >= 0 ? a - b : 0;
}
int compareEmscriptenVersions(std::tuple<int, int, int> a, std::tuple<int, int, int> b)
{
if (std::get<0>(a) == std::get<0>(b)) {
if (std::get<1>(a) == std::get<1>(b)) {
return compareVersionComponents(std::get<2>(a), std::get<2>(b));
}
return compareVersionComponents(std::get<1>(a), std::get<1>(b));
}
return compareVersionComponents(std::get<0>(a), std::get<0>(b));
}
bool isEmsdkVersionGreaterThan(std::tuple<int, int, int> test)
{
return compareEmscriptenVersions(
std::make_tuple(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__), test) > 0;
}
} // namespace
bool QWasmScreen::hasSpecialHtmlTargets() const
{
static bool gotIt = []{
// Enable use of specialHTMLTargets, if available
// On Emscripten > 3.1.14 (exact version not known), emscripten::val::module_property()
// aborts instead of returning undefined when attempting to resolve the specialHTMLTargets
// property, in the case where it is not defined. Disable the availability test in this case.
// FIXME: Add alternative way to enable.
if (isEmsdkVersionGreaterThan(std::make_tuple(3, 1, 14)))
return false;
emscripten::val htmlTargets = emscripten::val::module_property("specialHTMLTargets");
if (htmlTargets.isUndefined())
return false;
// Check that the object has the expected type - it can also be
// defined as an abort() function which prints an error on usage.
return htmlTargets["constructor"]["name"].as<std::string>() == std::string("Array");
}();
return gotIt;
return QString("!qtcanvas_%1").arg(uintptr_t(this));
}
QRect QWasmScreen::geometry() const

View File

@ -64,9 +64,6 @@ public slots:
void setGeometry(const QRect &rect);
private:
std::string canvasSpecialHtmlTargetId() const;
bool hasSpecialHtmlTargets() const;
emscripten::val m_container;
emscripten::val m_canvas;
std::unique_ptr<QWasmCompositor> m_compositor;