From b1c31831a8b60ac1d5fb050bf8f23468a3c45f93 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sat, 7 Nov 2020 01:04:03 +0100 Subject: [PATCH] EmscriptenApplication: get canvas target from Module['canvas'] and properly account for -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR Module['canvas'] can be read even from code compiled with -s MODULARIZE so it's a better option than hardcoding it in Configuration. The target strings in Emscripten depend on whether we're compiled with DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR (see https://github.com/emscripten-core/emscripten/pull/7977). This is now detected and handled at runtime to prepend element IDs with # and use the correct window and document magic targets. --- src/Magnum/Platform/EmscriptenApplication.cpp | 123 ++++++++++++------ src/Magnum/Platform/EmscriptenApplication.h | 24 +--- 2 files changed, 88 insertions(+), 59 deletions(-) diff --git a/src/Magnum/Platform/EmscriptenApplication.cpp b/src/Magnum/Platform/EmscriptenApplication.cpp index 9ed5c6438..fdedb1e3f 100644 --- a/src/Magnum/Platform/EmscriptenApplication.cpp +++ b/src/Magnum/Platform/EmscriptenApplication.cpp @@ -266,12 +266,19 @@ bool EmscriptenApplication::tryCreate(const Configuration& configuration) { } #endif - _canvasTarget = configuration.canvasTarget(); + std::ostream* verbose = _verboseLog ? Debug::output() : nullptr; + + _deprecatedTargetBehavior = checkForDeprecatedEmscriptenTargetBehavior(); + if(_deprecatedTargetBehavior) { + Debug{verbose} << "Platform::EmscriptenApplication::tryCreate(): using old Emscripten target behavior"; + } + + _canvasTarget = (_deprecatedTargetBehavior ? "" : "#") + canvasId(); _dpiScaling = dpiScaling(configuration); if(!configuration.size().isZero()) { const Vector2i scaledCanvasSize = configuration.size()*_dpiScaling; - emscripten_set_canvas_element_size(_canvasTarget.c_str(), scaledCanvasSize.x(), scaledCanvasSize.y()); + emscripten_set_canvas_element_size(_canvasTarget.data(), scaledCanvasSize.x(), scaledCanvasSize.y()); } setupCallbacks(!!(configuration.windowFlags() & Configuration::WindowFlag::Resizable)); @@ -329,7 +336,16 @@ bool EmscriptenApplication::tryCreate(const Configuration& configuration, const _devicePixelRatio = Vector2{Float(emscripten_get_device_pixel_ratio())}; Debug{verbose} << "Platform::EmscriptenApplication: device pixel ratio" << _devicePixelRatio.x(); - _canvasTarget = configuration.canvasTarget(); + /* Find out which elemen target strings Emscripten expects. This depends on + the DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR compiler option. */ + _deprecatedTargetBehavior = checkForDeprecatedEmscriptenTargetBehavior(); + if(_deprecatedTargetBehavior) { + Debug{verbose} << "Platform::EmscriptenApplication::tryCreate(): using old Emscripten target behavior"; + } + + /* Get the canvas ID from Module.canvas, either set by EmscriptenApplication.js + or overridden/manually set by the user. */ + _canvasTarget = (_deprecatedTargetBehavior ? "" : "#") + canvasId(); /* Get CSS canvas size and cache it. This is used later to detect canvas resizes in emscripten_set_resize_callback() and fire viewport events, @@ -350,10 +366,10 @@ bool EmscriptenApplication::tryCreate(const Configuration& configuration, const } _dpiScaling = dpiScaling(configuration); const Vector2i scaledCanvasSize = canvasSize*_dpiScaling*_devicePixelRatio; - emscripten_set_canvas_element_size(_canvasTarget.c_str(), scaledCanvasSize.x(), scaledCanvasSize.y()); + emscripten_set_canvas_element_size(_canvasTarget.data(), scaledCanvasSize.x(), scaledCanvasSize.y()); /* Create WebGL context */ - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(_canvasTarget.c_str(), &attrs); + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(_canvasTarget.data(), &attrs); if(!context) { /* When context creation fails, `context` is a negative integer matching EMSCRIPTEN_RESULT_* defines */ @@ -375,14 +391,14 @@ bool EmscriptenApplication::tryCreate(const Configuration& configuration, const Vector2i EmscriptenApplication::windowSize() const { Vector2d size; - emscripten_get_element_css_size(_canvasTarget.c_str(), &size.x(), &size.y()); + emscripten_get_element_css_size(_canvasTarget.data(), &size.x(), &size.y()); return Vector2i{Math::round(size)}; } #ifdef MAGNUM_TARGET_GL Vector2i EmscriptenApplication::framebufferSize() const { Vector2i size; - emscripten_get_canvas_element_size(_canvasTarget.c_str(), &size.x(), &size.y()); + emscripten_get_canvas_element_size(_canvasTarget.data(), &size.x(), &size.y()); return size; } #endif @@ -422,7 +438,7 @@ void EmscriptenApplication::handleCanvasResize(const EmscriptenUiEvent* event) { if(canvasSize != _lastKnownCanvasSize) { _lastKnownCanvasSize = canvasSize; const Vector2i size = canvasSize*_dpiScaling*_devicePixelRatio; - emscripten_set_canvas_element_size(_canvasTarget.c_str(), size.x(), size.y()); + emscripten_set_canvas_element_size(_canvasTarget.data(), size.x(), size.y()); ViewportEvent e{event, canvasSize, #ifdef MAGNUM_TARGET_GL framebufferSize(), @@ -448,11 +464,11 @@ void EmscriptenApplication::setupCallbacks(bool resizable) { changes. Better than polling for this change in every frame like Sdl2Application does, but still not ideal. */ if(resizable) { + const char* target = #ifdef EMSCRIPTEN_EVENT_TARGET_WINDOW - const char* target = EMSCRIPTEN_EVENT_TARGET_WINDOW; - #else - const char* target = "#window"; + !_deprecatedTargetBehavior ? EMSCRIPTEN_EVENT_TARGET_WINDOW : #endif + "#window"; auto cb = [](int, const EmscriptenUiEvent* event, void* userData) -> Int { static_cast(userData)->handleCanvasResize(event); return false; /** @todo what does ignoring a resize event mean? */ @@ -460,21 +476,21 @@ void EmscriptenApplication::setupCallbacks(bool resizable) { emscripten_set_resize_callback(target, this, false, cb); } - emscripten_set_mousedown_callback(_canvasTarget.c_str(), this, false, + emscripten_set_mousedown_callback(_canvasTarget.data(), this, false, ([](int, const EmscriptenMouseEvent* event, void* userData) -> Int { MouseEvent e{*event}; static_cast(userData)->mousePressEvent(e); return e.isAccepted(); })); - emscripten_set_mouseup_callback(_canvasTarget.c_str(), this, false, + emscripten_set_mouseup_callback(_canvasTarget.data(), this, false, ([](int, const EmscriptenMouseEvent* event, void* userData) -> Int { MouseEvent e{*event}; static_cast(userData)->mouseReleaseEvent(e); return e.isAccepted(); })); - emscripten_set_mousemove_callback(_canvasTarget.c_str(), this, false, + emscripten_set_mousemove_callback(_canvasTarget.data(), this, false, ([](int, const EmscriptenMouseEvent* event, void* userData) -> Int { auto& app = *static_cast(userData); /* With DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR, canvasX/Y is @@ -491,7 +507,7 @@ void EmscriptenApplication::setupCallbacks(bool resizable) { return e.isAccepted(); })); - emscripten_set_wheel_callback(_canvasTarget.c_str(), this, false, + emscripten_set_wheel_callback(_canvasTarget.data(), this, false, ([](int, const EmscriptenWheelEvent* event, void* userData) -> Int { MouseScrollEvent e{*event}; static_cast(userData)->mouseScrollEvent(e); @@ -508,8 +524,7 @@ void EmscriptenApplication::setupCallbacks(bool resizable) { 1.38.27 depending on -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 but we don't want to force this flag on the users so the behavior handles both. */ - #ifdef EMSCRIPTEN_EVENT_TARGET_DOCUMENT - char* const keyboardListeningElement = reinterpret_cast(EM_ASM_INT({ + char* keyboardListeningElement = reinterpret_cast(EM_ASM_INT({ var element = Module['keyboardListeningElement'] || document; if(element === document) return 1; @@ -519,29 +534,31 @@ void EmscriptenApplication::setupCallbacks(bool resizable) { return 0; })); - #else - char* const keyboardListeningElement = reinterpret_cast(EM_ASM_INT({ - var element = Module['keyboardListeningElement'] || document; - - if(element === document) element = {id: '#document'}; - if(element === window) element = {id: '#window'}; - if('id' in element) - return allocate(intArrayFromString(element.id), 'i8', ALLOC_NORMAL); - - return 0; - })); - #endif #pragma GCC diagnostic pop + std::string keyboardListeningElementString; + const char* keyboardListeningTarget = keyboardListeningElement; + if(_deprecatedTargetBehavior && keyboardListeningElement == reinterpret_cast(1)) { + keyboardListeningTarget = "#document"; + } else if(_deprecatedTargetBehavior && keyboardListeningElement == reinterpret_cast(2)) { + keyboardListeningTarget = "#window"; + } else if(keyboardListeningElement) { + if(!_deprecatedTargetBehavior) + keyboardListeningElementString = "#"; + keyboardListeningElementString += keyboardListeningElement; + std::free(keyboardListeningElement); + keyboardListeningTarget = keyboardListeningElementString.data(); + } + /* Happens only if keyboardListeningElement was set, but did not have an `id` attribute. Instead it should be either null or undefined, a DOM element, `window` or `document`. */ - CORRADE_ASSERT(keyboardListeningElement, + CORRADE_ASSERT(keyboardListeningTarget, "EmscriptenApplication::setupCallbacks(): invalid value for Module['keyboardListeningElement']", ); /* keypress_callback does not fire for most of the keys and the modifiers don't seem to work, keydown on the other hand works fine for all */ - emscripten_set_keydown_callback(keyboardListeningElement, this, false, + emscripten_set_keydown_callback(keyboardListeningTarget, this, false, ([](int, const EmscriptenKeyboardEvent* event, void* userData) -> Int { EmscriptenApplication& app = *static_cast(userData); const std::size_t keyLen = std::strlen(event->key); @@ -557,20 +574,12 @@ void EmscriptenApplication::setupCallbacks(bool resizable) { return e.isAccepted(); })); - emscripten_set_keyup_callback(keyboardListeningElement, this, false, + emscripten_set_keyup_callback(keyboardListeningTarget, this, false, ([](int, const EmscriptenKeyboardEvent* event, void* userData) -> Int { KeyEvent e{*event}; static_cast(userData)->keyReleaseEvent(e); return e.isAccepted(); })); - - #ifdef EMSCRIPTEN_EVENT_TARGET_DOCUMENT - if(keyboardListeningElement != EMSCRIPTEN_EVENT_TARGET_DOCUMENT && - keyboardListeningElement != EMSCRIPTEN_EVENT_TARGET_WINDOW) - #endif - { - std::free(keyboardListeningElement); - } } void EmscriptenApplication::setupAnimationFrame(bool forceAnimationFrame) { @@ -662,7 +671,7 @@ void EmscriptenApplication::setCursor(Cursor cursor) { CORRADE_INTERNAL_ASSERT(UnsignedInt(cursor) < Containers::arraySize(CursorMap)); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdollar-in-identifier-extension" - EM_ASM_({document.querySelector(UTF8ToString($0)).style.cursor = AsciiToString($1);}, _canvasTarget.c_str(), CursorMap[UnsignedInt(cursor)]); + EM_ASM_({document.querySelector(AsciiToString($0)).style.cursor = AsciiToString($1);}, _canvasTarget.data(), CursorMap[UnsignedInt(cursor)]); #pragma GCC diagnostic pop } @@ -739,6 +748,38 @@ void EmscriptenApplication::exit(int) { _flags |= Flag::ExitRequested; } +bool EmscriptenApplication::checkForDeprecatedEmscriptenTargetBehavior() { + /* Emscripten 1.38.27 changed to generic CSS selectors from element IDs + depending on -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 being + set. + https://github.com/emscripten-core/emscripten/pull/7977 + There is no simple way to check for compiler options so check + whether the new CSS selectors are being used. If so, it should find + canvas#[id] which is any canvas with the ID of Module.canvas. + The old target behavior will look for an element with id="canvas#[id]" + which could theoretically exist but that's highly unlikely. */ + bool deprecated = true; + #ifdef EMSCRIPTEN_EVENT_TARGET_WINDOW + Vector2d tempSize; + if(emscripten_get_element_css_size(("canvas#" + canvasId()).data(), &tempSize.x(), &tempSize.y()) >= 0) { + deprecated = false; + } + #endif + return deprecated; +} + +std::string EmscriptenApplication::canvasId() { + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" + char* id = reinterpret_cast(EM_ASM_INT({ + return allocate(intArrayFromString(Module['canvas'].id), 'i8', ALLOC_NORMAL); + })); + #pragma GCC diagnostic pop + std::string str = id; + std::free(id); + return str; +} + EmscriptenApplication::MouseEvent::Button EmscriptenApplication::MouseEvent::button() const { return Button(_event.button); } diff --git a/src/Magnum/Platform/EmscriptenApplication.h b/src/Magnum/Platform/EmscriptenApplication.h index fc9f2d85c..399223876 100644 --- a/src/Magnum/Platform/EmscriptenApplication.h +++ b/src/Magnum/Platform/EmscriptenApplication.h @@ -874,6 +874,10 @@ class EmscriptenApplication { * @} */ + private: + static bool checkForDeprecatedEmscriptenTargetBehavior(); + static std::string canvasId(); + private: enum class Flag: UnsignedByte { Redraw = 1 << 0, @@ -896,6 +900,7 @@ class EmscriptenApplication { Flags _flags; Cursor _cursor; + bool _deprecatedTargetBehavior{}; std::string _canvasTarget; #ifdef MAGNUM_TARGET_GL @@ -1175,7 +1180,7 @@ class EmscriptenApplication::Configuration { */ typedef Containers::EnumSet WindowFlags; - /*implicit*/ Configuration() {} + constexpr /*implicit*/ Configuration() {} /** * @brief Set window title @@ -1234,27 +1239,10 @@ class EmscriptenApplication::Configuration { return *this; } - std::string canvasTarget() const { - /* Emscripten 1.38.27 changed to generic CSS selectors from element IDs - depending on -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 being - set (which we can't detect at compile time). Fortunately, using #canvas - works the same way both in the previous versions and the current one. - Unfortunately, this is also the only value that works the same way for - both. Further details at - https://github.com/emscripten-core/emscripten/pull/7977 */ - return _canvasTarget.empty() ? "#canvas" : _canvasTarget; - } - - Configuration& setCanvasTarget(const std::string& canvasTarget) { - _canvasTarget = canvasTarget; - return *this; - } - private: Vector2i _size; Vector2 _dpiScaling; WindowFlags _windowFlags; - std::string _canvasTarget; }; CORRADE_ENUMSET_OPERATORS(EmscriptenApplication::Configuration::WindowFlags)