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()
{
QByteArray canvasSelector = "#" + screen()->canvasId().toUtf8();
QByteArray canvasSelector = screen()->canvasTargetId().toUtf8();
emscripten_set_keydown_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()
{
QByteArray canvasSelector = "#" + screen()->canvasId().toUtf8();
QByteArray canvasSelector = screen()->canvasTargetId().toUtf8();
eventTranslator->g_usePlatformMacSpecifics
= (QWasmIntegration::get()->platform == QWasmIntegration::MacOSPlatform);

View File

@ -87,13 +87,12 @@ bool QWasmOpenGLContext::maybeCreateEmscriptenContext(QPlatformSurface *surface)
if (m_context)
return m_screen == screen;
QString canvasId = QWasmScreen::get(screen)->canvasId();
m_context = createEmscriptenContext(canvasId, m_requestedFormat);
m_context = createEmscriptenContext(QWasmScreen::get(screen)->canvasTargetId(), m_requestedFormat);
m_screen = screen;
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;
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.stencil = useDepthStencil;
QByteArray convasSelector = "#" + canvasId.toUtf8();
QByteArray convasSelector = canvasTargetId.toUtf8();
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(convasSelector.constData(), &attributes);
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
m_canvas = containerOrCanvas["ownerDocument"].call<emscripten::val>("createElement", std::string("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
emscripten::val style = m_canvas["style"];
@ -96,6 +97,16 @@ QWasmScreen::QWasmScreen(const emscripten::val &containerOrCanvas)
m_onContextMenu = std::make_unique<qstdweb::EventCallback>(m_canvas, "contextmenu", [](emscripten::val event){
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
// done after the canvas has been created above.
@ -107,6 +118,10 @@ QWasmScreen::QWasmScreen(const emscripten::val &containerOrCanvas)
QWasmScreen::~QWasmScreen()
{
EM_ASM({
specialHTMLTargets["!qtcanvas_" + $0] = undefined;
}, uint32_t(this));
m_canvas.set(m_canvasResizeObserverCallbackContextPropertyName, emscripten::val(intptr_t(0)));
destroy();
}
@ -146,11 +161,21 @@ emscripten::val QWasmScreen::canvas() const
return m_canvas;
}
// Returns the html element id for the screen's canvas.
QString QWasmScreen::canvasId() const
{
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
{
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
// rendering.
QByteArray canvasSelector = "#" + canvasId().toUtf8();
QByteArray canvasSelector = canvasTargetId().toUtf8();
double css_width;
double 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 canvas() const;
QString canvasId() const;
QString canvasTargetId() const;
QWasmCompositor *compositor();
QWasmEventTranslator *eventTranslator();