emscripten-core / emscripten

Emscripten: An LLVM-to-WebAssembly Compiler
Other
25.91k stars 3.32k forks source link

EM_ASYNC_JS didn't end as expected when listen dom event #23036

Open Perter-Zhang opened 5 days ago

Perter-Zhang commented 5 days ago

I am currently simulating a modal dialog.

In CPP code,I will create a H5

element. There is a close button on the dialog. and i bind event listeners for close button by embind style.

When click "close" button, the dialog will be close and continue subsequent follow.

To achieve the blocking effect, I used the macro EM_ASYNC_JS to implement it.

In EM_ASYNC_JS Function, I will create a Promise and listen "close" event for dialog element. If the "close" event be triggered, The function will return a result.

But when actually executing, The close event is triggered but it will not be returned to CPP

Version of emscripten/emsdk: 3.1.69

emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.25 (febd44b21ecaca86e2cb2a25ef3ed4a0a2076365)
clang version 16.0.0 (https://github.com/llvm/llvm-project effd75bda4b1a9b26e554c1cda3e3b4c72fa0aa8)
Target: wasm32-unknown-emscripten

Full link command and output with -v appended: emcc example.cpp exampleexport.cpp -o a.js -g -s ASYNCIFY --bind

// example.h
#pragma once
#include <emscripten.h>
#include <emscripten/val.h>

class example
{
private:
public:
  example();
  ~example();

  void DoModal();
  static void OnEvent(emscripten::val event);
  void HandEvent(emscripten::val event);
};
// example.cpp
#include "example.h"
#include <emscripten/html5.h>
#include <emscripten/bind.h>
#include <stdio.h>
#include <iostream>
#include <string>

example::example()
{
}

example::~example()
{
}

EM_ASYNC_JS(int, WaitingClose, (), {
  console.log("waiting close begin");
  var p = new Promise((resolve, reject) => {
    console.log("addEventListener for dialog");
    var dialogElem = document.getElementById("modal-dialog");
    dialogElem.addEventListener("close", ()=> {
      resolve(1);
    });
  });

  let obj = await p;
  console.log("waiting close end");
  return obj;
});

void example::DoModal()
{
  emscripten::val documentElem = emscripten::val::global("document");
  emscripten::val newDialogelem = documentElem.call<emscripten::val>("createElement", std::string("dialog"));
  newDialogelem.set("id", std::string("modal-dialog"));
  newDialogelem["style"].set("width", "600px");
  newDialogelem["style"].set("height", "400px");
  newDialogelem["style"].set("background-color", "#a0a0a0");

  emscripten::val newCloseElem = documentElem.call<emscripten::val>("createElement", std::string("div"));
  newCloseElem.set("id", std::string("modal-dialog-close"));
  newCloseElem.set("data-context", (uintptr_t)this);
  newCloseElem["style"].set("width", "25px");
  newCloseElem["style"].set("height", "25px");
  newCloseElem["style"].set("float", "right");
  newCloseElem["style"].set("background-color", "#ffffff");

  newDialogelem.call<void>("appendChild", newCloseElem);
  documentElem["body"].call<void>("appendChild", newDialogelem);

  newCloseElem.call<void>("addEventListener", emscripten::val("click"), emscripten::val::module_property("eventlistener"), false);

  emscripten::val dialogElem = documentElem.call<emscripten::val>("getElementById", std::string("modal-dialog"));
  if (false == dialogElem.isNull() || false == dialogElem.isUndefined())
  {
    dialogElem.call<void>("showModal");
  }

  std::cout << "before" << std::endl;
  int result = WaitingClose();
  std::cout << "close result: " << result << std::endl;
  std::cout << "after" << std::endl;
}

void example::OnEvent(emscripten::val event)
{
  uintptr_t uDataContext = event["target"]["data-context"].as<uintptr_t>();
  example* pExample = (example*)uDataContext;
  if (pExample)
  {
    pExample->HandEvent(event);
  }
}

void example::HandEvent(emscripten::val event)
{
  std::string strEvent = event["type"].as<std::string>();
  if (strEvent == "click")
  {
    emscripten::val documentElem = emscripten::val::global("document");
    emscripten::val dialogElem = documentElem.call<emscripten::val>("getElementById", std::string("modal-dialog"));
    if (false == dialogElem.isNull() || false == dialogElem.isUndefined())
    {
      dialogElem.call<void>("close");
    }
  }

  event.call<void>("preventDefault");
  event.call<void>("stopPropagation");

}

EMSCRIPTEN_BINDINGS(DialogContentContextItemEvent)
{
  emscripten::function("eventlistener", &example::OnEvent);
}
//exampleexport.cpp
#include "example.h"
#include <emscripten/emscripten.h>
#include <emscripten/bind.h>

EMSCRIPTEN_BINDINGS(exampleWrapper)
{
    emscripten::class_<example>("example")
        .constructor<>()
        .function("DoModal", &example::DoModal);
}
<!DOCTYPE html>
<html lang="utf-8">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Test Demo</title>
</head>

<body>
</body>
<style>
  html,
  body {
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
  }

</style>
<script>
  var webReportCtrl = undefined;
  Module = {};
  Module.onRuntimeInitialized = async function () {
    webReportCtrl = new Module.example();
    webReportCtrl.DoModal();
  };

</script>
<script src="./a.js"></script>

</html>

After check the output result, I found it will output warning and error in terminal. The output result as following:

before
waiting close begin
addEventListener for dialog
a.js:941 Aborted(Assertion failed: Cannot have multiple async operations in flight at once)
a.js:964 Uncaught RuntimeError: Aborted(Assertion failed: Cannot have multiple async operations in flight at once)
waiting close end

If I not use embind to bind event listeners for "close" button


EM_JS(void, BindFun, (), {
  var elem = document.getElementById("modal-dialog-close");
  elem.addEventListener("click", ()=> {
      var dialogElem = document.getElementById("modal-dialog");
      dialogElem.close();
  });
});

 //newCloseElem.call<void>("addEventListener", emscripten::val("click"), emscripten::val::module_property("eventlistener"), false);
 BindFun();

The result is right. The output result as following:

before
waiting close begin
addEventListener for dialog
waiting close end
 close result: 1
after

Does anyone have any idea about this issue? If I use embind to bind events, how do I adjust my EM_ASYNC_JS function?

I am a beginner in emscripten and may not know much about it. If there is anything wrong in my description, please forgive me.

brendandahl commented 1 day ago

I don't see anything immediately wrong with the example, but I do notice your output for emcc -v shows emscripten 3.1.25 which is very old. Maybe try a more recent version/ensure you're actually building with 3.1.69 like you mention.