wasm: use “specialHtmlTargets” for canvas lookup

We’d like to support use cases which require using
multiple html documents, for example when displaying
application content using more than one browser window.

Normally Emscripten uses the primary html document when
looking up elements by id, which means that targeting
elements on secondary documents is not possible.

Emscripten does provide a workaround: the application
can create custom id mappings to any html element on
the “specialHtmlTargets” object, and then use the
“!id” syntax instead of normal “#id” lookup.

Change-Id: I4dda920868cfbc6f8991425daf8933144c0ffad8
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
Morten Johan Sørvig 2021-11-27 23:56:40 +01:00
parent 4bc85b9850
commit 08733dff58
4 changed files with 33 additions and 8 deletions

View File

@ -115,7 +115,7 @@ QWasmCompositor::~QWasmCompositor()
void QWasmCompositor::deregisterEventHandlers() void QWasmCompositor::deregisterEventHandlers()
{ {
QByteArray canvasSelector = "#" + screen()->canvasId().toUtf8(); QByteArray canvasSelector = screen()->canvasTargetId().toUtf8();
emscripten_set_keydown_callback(canvasSelector.constData(), 0, 0, NULL); emscripten_set_keydown_callback(canvasSelector.constData(), 0, 0, NULL);
emscripten_set_keyup_callback(canvasSelector.constData(), 0, 0, NULL); emscripten_set_keyup_callback(canvasSelector.constData(), 0, 0, NULL);
@ -156,7 +156,7 @@ void QWasmCompositor::destroy()
void QWasmCompositor::initEventHandlers() void QWasmCompositor::initEventHandlers()
{ {
QByteArray canvasSelector = "#" + screen()->canvasId().toUtf8(); QByteArray canvasSelector = screen()->canvasTargetId().toUtf8();
eventTranslator->g_usePlatformMacSpecifics eventTranslator->g_usePlatformMacSpecifics
= (QWasmIntegration::get()->platform == QWasmIntegration::MacOSPlatform); = (QWasmIntegration::get()->platform == QWasmIntegration::MacOSPlatform);

View File

@ -87,13 +87,12 @@ bool QWasmOpenGLContext::maybeCreateEmscriptenContext(QPlatformSurface *surface)
if (m_context) if (m_context)
return m_screen == screen; return m_screen == screen;
QString canvasId = QWasmScreen::get(screen)->canvasId(); m_context = createEmscriptenContext(QWasmScreen::get(screen)->canvasTargetId(), m_requestedFormat);
m_context = createEmscriptenContext(canvasId, m_requestedFormat);
m_screen = screen; m_screen = screen;
return true; return true;
} }
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE QWasmOpenGLContext::createEmscriptenContext(const QString &canvasId, QSurfaceFormat format) EMSCRIPTEN_WEBGL_CONTEXT_HANDLE QWasmOpenGLContext::createEmscriptenContext(const QString &canvasTargetId, QSurfaceFormat format)
{ {
EmscriptenWebGLContextAttributes attributes; EmscriptenWebGLContextAttributes attributes;
emscripten_webgl_init_context_attributes(&attributes); // Populate with default attributes emscripten_webgl_init_context_attributes(&attributes); // Populate with default attributes
@ -114,7 +113,7 @@ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE QWasmOpenGLContext::createEmscriptenContext(cons
attributes.depth = useDepthStencil; attributes.depth = useDepthStencil;
attributes.stencil = useDepthStencil; attributes.stencil = useDepthStencil;
QByteArray convasSelector = "#" + canvasId.toUtf8(); QByteArray convasSelector = canvasTargetId.toUtf8();
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(convasSelector.constData(), &attributes); EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(convasSelector.constData(), &attributes);
return context; return context;

View File

@ -68,7 +68,8 @@ QWasmScreen::QWasmScreen(const emscripten::val &containerOrCanvas)
// Create the canvas (for the correct document) as a child of the container // Create the canvas (for the correct document) as a child of the container
m_canvas = containerOrCanvas["ownerDocument"].call<emscripten::val>("createElement", std::string("canvas")); m_canvas = containerOrCanvas["ownerDocument"].call<emscripten::val>("createElement", std::string("canvas"));
containerOrCanvas.call<void>("appendChild", m_canvas); containerOrCanvas.call<void>("appendChild", m_canvas);
m_canvas.set("id", std::string("qtcanvas_") + std::to_string(uint32_t(this))); std::string screenId = std::string("qtcanvas_") + std::to_string(uint32_t(this));
m_canvas.set("id", screenId);
// Make the canvas occupy 100% of parent // Make the canvas occupy 100% of parent
emscripten::val style = m_canvas["style"]; emscripten::val style = m_canvas["style"];
@ -97,6 +98,16 @@ QWasmScreen::QWasmScreen(const emscripten::val &containerOrCanvas)
event.call<void>("preventDefault"); 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.
EM_ASM({
specialHTMLTargets["!qtcanvas_" + $0] = Emval.toValue($1);
}, uint32_t(this), m_canvas.as_handle());
// Install event handlers on the container/canvas. This must be // Install event handlers on the container/canvas. This must be
// done after the canvas has been created above. // done after the canvas has been created above.
m_compositor->initEventHandlers(); m_compositor->initEventHandlers();
@ -107,6 +118,10 @@ QWasmScreen::QWasmScreen(const emscripten::val &containerOrCanvas)
QWasmScreen::~QWasmScreen() QWasmScreen::~QWasmScreen()
{ {
EM_ASM({
specialHTMLTargets["!qtcanvas_" + $0] = undefined;
}, uint32_t(this));
m_canvas.set(m_canvasResizeObserverCallbackContextPropertyName, emscripten::val(intptr_t(0))); m_canvas.set(m_canvasResizeObserverCallbackContextPropertyName, emscripten::val(intptr_t(0)));
destroy(); destroy();
} }
@ -146,11 +161,21 @@ emscripten::val QWasmScreen::canvas() const
return m_canvas; return m_canvas;
} }
// Returns the html element id for the screen's canvas.
QString QWasmScreen::canvasId() const QString QWasmScreen::canvasId() const
{ {
return QWasmString::toQString(m_canvas["id"]); return QWasmString::toQString(m_canvas["id"]);
} }
// Returns the canvas _target_ id, for use with Emscripten's
// event registration functions. The target id is a globally
// unique id, unlike the html element id which is only unique
// within one html document. See specialHtmlTargets.
QString QWasmScreen::canvasTargetId() const
{
return QStringLiteral("!qtcanvas_") + QString::number(int32_t(this));
}
QRect QWasmScreen::geometry() const QRect QWasmScreen::geometry() const
{ {
return m_geometry; return m_geometry;
@ -246,7 +271,7 @@ void QWasmScreen::updateQScreenAndCanvasRenderSize()
// Setting the render size to a value larger than the CSS size enables high-dpi // Setting the render size to a value larger than the CSS size enables high-dpi
// rendering. // rendering.
QByteArray canvasSelector = "#" + canvasId().toUtf8(); QByteArray canvasSelector = canvasTargetId().toUtf8();
double css_width; double css_width;
double css_height; double css_height;
emscripten_get_element_css_size(canvasSelector.constData(), &css_width, &css_height); emscripten_get_element_css_size(canvasSelector.constData(), &css_width, &css_height);

View File

@ -62,6 +62,7 @@ public:
emscripten::val container() const; emscripten::val container() const;
emscripten::val canvas() const; emscripten::val canvas() const;
QString canvasId() const; QString canvasId() const;
QString canvasTargetId() const;
QWasmCompositor *compositor(); QWasmCompositor *compositor();
QWasmEventTranslator *eventTranslator(); QWasmEventTranslator *eventTranslator();