summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephan Bergmann <stephan.bergmann@allotropia.de>2024-01-26 14:12:31 +0100
committerStephan Bergmann <stephan.bergmann@allotropia.de>2024-01-29 19:51:57 +0100
commitb476dccd11a1f9d7f39876ad23dd3e14c6671909 (patch)
tree6e0f3e4f71ab24997b72db4cf7e26d7d659c69f7
parent289afffaecf18b2cc5b032fed169a9131dc4c1ac (diff)
Experimental support for latest Emscripten (and Qt6)
What I'm after in the context of our Embind-related code is the claim at <https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#memory-management>: "JavaScript only gained support for finalizers in ECMAScript 2021, or ECMA-262 Edition 12. The new API is called FinalizationRegistry and it still does not offer any guarantees that the provided finalization callback will be called. Embind uses this for cleanup if available, but only for smart pointers, and only as a last resort." However, with the recommended emsdk 2.0.31 my tests did not show any use of that finalization support. So I wanted to try with the latest emsdk 3.1.51 instead. But then, linking vcldemo failed with > wasm-ld: error: ~/allotropia-qt5/lib/libQt5Core.a(qlogging.o): undefined symbol: std::__2::__vector_base_common<true>::__throw_length_error() const etc., so I gave it a try to rather build against latest Qt6.7, with > --with-distro=LibreOfficeWASM32 > --disable-qt5 > --enable-qt6 > QT6DIR=... TODO: The result is highly experimental, and it uses ENABLE_QT5 (i.e., the recommended setup with emsdk 2.0.31 and the <https://github.com/allotropia/qt5.git> v5.15.2+wasm branch) vs. ENABLE_QT6 (i.e., my experimental setup with emsdk 3.1.51 and the <https://github.com/qt/qt5.git> 6.7 branch) not only to distinguish between Qt5- vs. Qt6-specific behavior, but also to distinguish between old- vs. new-Emscripten-specific behavior. Of note: * The startup code appears to have changed substantially (and required setting -s MODULARIZE=1 and -s EXPORT_NAME=soffice_entry now, and telling emcc to build just soffice.js and not the wrapper soffice.html. TODO: This also required hacking into qt_soffice.html: (1) The command-line arguments (--norestore, --nologo, --writer, and the exammple.odt; all of which we should eventually get rid of, anyway). (2) Saving the generated instance as window.Module (and adapting the embindmaker-generated code, cf. the changes to static/README.wasm.md). * There were some symbol clashes between external/argon2 and libQt6Core.a(qcryptographichash.cpp.o, so renamed the problematic symbols in external/argon2. Change-Id: I5420ab566d560b11954ac6613249bfc53d8acb31 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/162695 Tested-by: Jenkins Reviewed-by: Stephan Bergmann <stephan.bergmann@allotropia.de>
-rw-r--r--RepositoryFixes.mk4
-rw-r--r--configure.ac3
-rw-r--r--desktop/Executable_soffice_bin.mk8
-rw-r--r--external/argon2/UnpackedTarball_argon2.mk1
-rw-r--r--external/argon2/private-symbols.patch.0192
-rw-r--r--solenv/gbuild/platform/EMSCRIPTEN_INTEL_GCC.mk2
-rw-r--r--solenv/gbuild/platform/unxgcc.mk5
-rw-r--r--static/README.wasm.md4
-rw-r--r--static/source/embindmaker/embindmaker.cxx8
9 files changed, 218 insertions, 9 deletions
diff --git a/RepositoryFixes.mk b/RepositoryFixes.mk
index 0cbb5ad93e6c..2139b2f823ff 100644
--- a/RepositoryFixes.mk
+++ b/RepositoryFixes.mk
@@ -35,7 +35,11 @@ ifeq ($(OS),MACOSX)
gb_Executable_FILENAMES := $(patsubst soffice_bin:soffice_bin,soffice_bin:soffice,$(gb_Executable_FILENAMES))
else
ifeq ($(OS),EMSCRIPTEN)
+ifeq ($(ENABLE_QT6),TRUE)
+gb_Executable_FILENAMES := $(patsubst soffice_bin:soffice_bin%,soffice_bin:soffice.js,$(gb_Executable_FILENAMES))
+else
gb_Executable_FILENAMES := $(patsubst soffice_bin:soffice_bin%,soffice_bin:soffice.html,$(gb_Executable_FILENAMES))
+endif
else
gb_Executable_FILENAMES := $(patsubst soffice_bin:soffice_bin%,soffice_bin:soffice.bin,$(gb_Executable_FILENAMES))
endif
diff --git a/configure.ac b/configure.ac
index 6a85728e2248..3387786c2f89 100644
--- a/configure.ac
+++ b/configure.ac
@@ -5540,6 +5540,7 @@ if test "$using_x11" = yes; then
disable_x11_tests
if test "$DISABLE_DYNLOADING" = TRUE; then
test_qt5=yes
+ test_qt6=yes
fi
fi
else
@@ -13322,7 +13323,7 @@ then
QT6_CFLAGS=$(printf '%s' "$QT6_CFLAGS" | sed -e "s/-I/${ISYSTEM?}/g")
QT6_LIBS="-L$qt6_libdir -lQt6Core -lQt6Gui -lQt6Widgets -lQt6Network"
if test "$_os" = "Emscripten"; then
- QT6_LIBS="$QT6_LIBS -lqtpcre2 -lQt6EventDispatcherSupport -lQt6FontDatabaseSupport -L${qt6_platformsdir} -lqwasm"
+ QT6_LIBS="$QT6_LIBS -lQt6BundledPcre2 -lQt6BundledZLIB -L${qt6_platformsdir} -lqwasm -sGL_ENABLE_GET_PROC_ADDRESS"
fi
if test "$USING_X11" = TRUE; then
diff --git a/desktop/Executable_soffice_bin.mk b/desktop/Executable_soffice_bin.mk
index fbdf07a5e531..40042651a4e9 100644
--- a/desktop/Executable_soffice_bin.mk
+++ b/desktop/Executable_soffice_bin.mk
@@ -27,7 +27,9 @@ $(eval $(call gb_Executable_add_cobjects,soffice_bin,\
desktop/source/app/main \
))
+ifeq ($(OS)-$(ENABLE_QT5),EMSCRIPTEN-TRUE)
$(eval $(call gb_Executable_add_prejs,soffice_bin,$(SRCDIR)/static/emscripten/soffice_args.js))
+endif
ifeq ($(OS),WNT)
@@ -55,6 +57,12 @@ $(call gb_LinkTarget__static_lib_dummy_depend,unoembind)
$(eval $(call gb_Executable_add_ldflags,soffice_bin,\
-s EXPORTED_FUNCTIONS=["_main"$(COMMA)"_libreofficekit_hook"$(COMMA)"_libreofficekit_hook_2"$(COMMA)"_lok_preinit"$(COMMA)"_lok_preinit_2"] -Wl$(COMMA)--whole-archive $(call gb_StaticLibrary_get_target,unoembind) -Wl$(COMMA)--no-whole-archive \
))
+ifeq ($(ENABLE_QT6),TRUE)
+$(eval $(call gb_Executable_add_ldflags,soffice_bin, \
+ -s MODULARIZE=1 \
+ -s EXPORT_NAME=soffice_entry \
+))
+endif
endif
diff --git a/external/argon2/UnpackedTarball_argon2.mk b/external/argon2/UnpackedTarball_argon2.mk
index a91d3d6651c4..c795a1562974 100644
--- a/external/argon2/UnpackedTarball_argon2.mk
+++ b/external/argon2/UnpackedTarball_argon2.mk
@@ -16,6 +16,7 @@ $(eval $(call gb_UnpackedTarball_set_patchlevel,argon2,1))
$(eval $(call gb_UnpackedTarball_add_patches,argon2,\
external/argon2/0001-Fix-possible-compiler-error-due-to-undefined-_MSC_VE.patch \
$(if $(filter WNT_AARCH64,$(OS)_$(CPUNAME)),external/argon2/0002-Add-WinARM64-vcxproj-config.patch) \
+ external/argon2/private-symbols.patch.0 \
))
# vim: set noet sw=4 ts=4:
diff --git a/external/argon2/private-symbols.patch.0 b/external/argon2/private-symbols.patch.0
new file mode 100644
index 000000000000..5b4985e510af
--- /dev/null
+++ b/external/argon2/private-symbols.patch.0
@@ -0,0 +1,192 @@
+--- src/blake2/blake2.h
++++ src/blake2/blake2.h
+@@ -67,15 +67,15 @@
+ };
+
+ /* Streaming API */
+-ARGON2_LOCAL int blake2b_init(blake2b_state *S, size_t outlen);
+-ARGON2_LOCAL int blake2b_init_key(blake2b_state *S, size_t outlen, const void *key,
++ARGON2_LOCAL int argon2_blake2b_init(blake2b_state *S, size_t outlen);
++static int blake2b_init_key(blake2b_state *S, size_t outlen, const void *key,
+ size_t keylen);
+-ARGON2_LOCAL int blake2b_init_param(blake2b_state *S, const blake2b_param *P);
+-ARGON2_LOCAL int blake2b_update(blake2b_state *S, const void *in, size_t inlen);
+-ARGON2_LOCAL int blake2b_final(blake2b_state *S, void *out, size_t outlen);
++static int blake2b_init_param(blake2b_state *S, const blake2b_param *P);
++ARGON2_LOCAL int argon2_blake2b_update(blake2b_state *S, const void *in, size_t inlen);
++ARGON2_LOCAL int argon2_blake2b_final(blake2b_state *S, void *out, size_t outlen);
+
+ /* Simple API */
+-ARGON2_LOCAL int blake2b(void *out, size_t outlen, const void *in, size_t inlen,
++static int blake2b(void *out, size_t outlen, const void *in, size_t inlen,
+ const void *key, size_t keylen);
+
+ /* Argon2 Team - Begin Code */
+--- src/blake2/blake2b.c
++++ src/blake2/blake2b.c
+@@ -88,7 +88,7 @@
+ }
+
+ /* Sequential blake2b initialization */
+-int blake2b_init(blake2b_state *S, size_t outlen) {
++int argon2_blake2b_init(blake2b_state *S, size_t outlen) {
+ blake2b_param P;
+
+ if (S == NULL) {
+@@ -156,7 +156,7 @@
+ uint8_t block[BLAKE2B_BLOCKBYTES];
+ memset(block, 0, BLAKE2B_BLOCKBYTES);
+ memcpy(block, key, keylen);
+- blake2b_update(S, block, BLAKE2B_BLOCKBYTES);
++ argon2_blake2b_update(S, block, BLAKE2B_BLOCKBYTES);
+ /* Burn the key from stack */
+ clear_internal_memory(block, BLAKE2B_BLOCKBYTES);
+ }
+@@ -221,7 +221,7 @@
+ #undef ROUND
+ }
+
+-int blake2b_update(blake2b_state *S, const void *in, size_t inlen) {
++int argon2_blake2b_update(blake2b_state *S, const void *in, size_t inlen) {
+ const uint8_t *pin = (const uint8_t *)in;
+
+ if (inlen == 0) {
+@@ -261,7 +261,7 @@
+ return 0;
+ }
+
+-int blake2b_final(blake2b_state *S, void *out, size_t outlen) {
++int argon2_blake2b_final(blake2b_state *S, void *out, size_t outlen) {
+ uint8_t buffer[BLAKE2B_OUTBYTES] = {0};
+ unsigned int i;
+
+@@ -314,15 +314,15 @@
+ goto fail;
+ }
+ } else {
+- if (blake2b_init(&S, outlen) < 0) {
++ if (argon2_blake2b_init(&S, outlen) < 0) {
+ goto fail;
+ }
+ }
+
+- if (blake2b_update(&S, in, inlen) < 0) {
++ if (argon2_blake2b_update(&S, in, inlen) < 0) {
+ goto fail;
+ }
+- ret = blake2b_final(&S, out, outlen);
++ ret = argon2_blake2b_final(&S, out, outlen);
+
+ fail:
+ clear_internal_memory(&S, sizeof(S));
+@@ -352,18 +352,18 @@
+ } while ((void)0, 0)
+
+ if (outlen <= BLAKE2B_OUTBYTES) {
+- TRY(blake2b_init(&blake_state, outlen));
+- TRY(blake2b_update(&blake_state, outlen_bytes, sizeof(outlen_bytes)));
+- TRY(blake2b_update(&blake_state, in, inlen));
+- TRY(blake2b_final(&blake_state, out, outlen));
++ TRY(argon2_blake2b_init(&blake_state, outlen));
++ TRY(argon2_blake2b_update(&blake_state, outlen_bytes, sizeof(outlen_bytes)));
++ TRY(argon2_blake2b_update(&blake_state, in, inlen));
++ TRY(argon2_blake2b_final(&blake_state, out, outlen));
+ } else {
+ uint32_t toproduce;
+ uint8_t out_buffer[BLAKE2B_OUTBYTES];
+ uint8_t in_buffer[BLAKE2B_OUTBYTES];
+- TRY(blake2b_init(&blake_state, BLAKE2B_OUTBYTES));
+- TRY(blake2b_update(&blake_state, outlen_bytes, sizeof(outlen_bytes)));
+- TRY(blake2b_update(&blake_state, in, inlen));
+- TRY(blake2b_final(&blake_state, out_buffer, BLAKE2B_OUTBYTES));
++ TRY(argon2_blake2b_init(&blake_state, BLAKE2B_OUTBYTES));
++ TRY(argon2_blake2b_update(&blake_state, outlen_bytes, sizeof(outlen_bytes)));
++ TRY(argon2_blake2b_update(&blake_state, in, inlen));
++ TRY(argon2_blake2b_final(&blake_state, out_buffer, BLAKE2B_OUTBYTES));
+ memcpy(out, out_buffer, BLAKE2B_OUTBYTES / 2);
+ out += BLAKE2B_OUTBYTES / 2;
+ toproduce = (uint32_t)outlen - BLAKE2B_OUTBYTES / 2;
+--- src/core.c
++++ src/core.c
+@@ -544,31 +544,31 @@
+ return;
+ }
+
+- blake2b_init(&BlakeHash, ARGON2_PREHASH_DIGEST_LENGTH);
++ argon2_blake2b_init(&BlakeHash, ARGON2_PREHASH_DIGEST_LENGTH);
+
+ store32(&value, context->lanes);
+- blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
++ argon2_blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+ store32(&value, context->outlen);
+- blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
++ argon2_blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+ store32(&value, context->m_cost);
+- blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
++ argon2_blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+ store32(&value, context->t_cost);
+- blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
++ argon2_blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+ store32(&value, context->version);
+- blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
++ argon2_blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+ store32(&value, (uint32_t)type);
+- blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
++ argon2_blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+ store32(&value, context->pwdlen);
+- blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
++ argon2_blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+ if (context->pwd != NULL) {
+- blake2b_update(&BlakeHash, (const uint8_t *)context->pwd,
++ argon2_blake2b_update(&BlakeHash, (const uint8_t *)context->pwd,
+ context->pwdlen);
+
+ if (context->flags & ARGON2_FLAG_CLEAR_PASSWORD) {
+@@ -578,18 +578,18 @@
+ }
+
+ store32(&value, context->saltlen);
+- blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
++ argon2_blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+ if (context->salt != NULL) {
+- blake2b_update(&BlakeHash, (const uint8_t *)context->salt,
++ argon2_blake2b_update(&BlakeHash, (const uint8_t *)context->salt,
+ context->saltlen);
+ }
+
+ store32(&value, context->secretlen);
+- blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
++ argon2_blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+ if (context->secret != NULL) {
+- blake2b_update(&BlakeHash, (const uint8_t *)context->secret,
++ argon2_blake2b_update(&BlakeHash, (const uint8_t *)context->secret,
+ context->secretlen);
+
+ if (context->flags & ARGON2_FLAG_CLEAR_SECRET) {
+@@ -599,14 +599,14 @@
+ }
+
+ store32(&value, context->adlen);
+- blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
++ argon2_blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+ if (context->ad != NULL) {
+- blake2b_update(&BlakeHash, (const uint8_t *)context->ad,
++ argon2_blake2b_update(&BlakeHash, (const uint8_t *)context->ad,
+ context->adlen);
+ }
+
+- blake2b_final(&BlakeHash, blockhash, ARGON2_PREHASH_DIGEST_LENGTH);
++ argon2_blake2b_final(&BlakeHash, blockhash, ARGON2_PREHASH_DIGEST_LENGTH);
+ }
+
+ int initialize(argon2_instance_t *instance, argon2_context *context) {
diff --git a/solenv/gbuild/platform/EMSCRIPTEN_INTEL_GCC.mk b/solenv/gbuild/platform/EMSCRIPTEN_INTEL_GCC.mk
index a69539b65403..a363ac7abe37 100644
--- a/solenv/gbuild/platform/EMSCRIPTEN_INTEL_GCC.mk
+++ b/solenv/gbuild/platform/EMSCRIPTEN_INTEL_GCC.mk
@@ -27,7 +27,7 @@ gb_EMSCRIPTEN_LDFLAGS += -s TOTAL_MEMORY=1GB -s PTHREAD_POOL_SIZE=4
# To keep the link time (and memory) down, prevent all rewriting options from wasm-emscripten-finalize
# See emscripten.py, finalize_wasm, modify_wasm = True
# So we need WASM_BIGINT=1 and ASSERTIONS=1 (2 implies STACK_OVERFLOW_CHECK)
-gb_EMSCRIPTEN_LDFLAGS += --bind -s FORCE_FILESYSTEM=1 -s WASM_BIGINT=1 -s ERROR_ON_UNDEFINED_SYMBOLS=1 -s FETCH=1 -s ASSERTIONS=1 -s EXIT_RUNTIME=0 -s EXPORTED_RUNTIME_METHODS=["UTF16ToString","stringToUTF16","UTF8ToString","allocateUTF8","printErr","ccall","cwrap"]
+gb_EMSCRIPTEN_LDFLAGS += --bind -s FORCE_FILESYSTEM=1 -s WASM_BIGINT=1 -s ERROR_ON_UNDEFINED_SYMBOLS=1 -s FETCH=1 -s ASSERTIONS=1 -s EXIT_RUNTIME=0 -s EXPORTED_RUNTIME_METHODS=["UTF16ToString","stringToUTF16","UTF8ToString","allocateUTF8","printErr","ccall","cwrap"$(if $(ENABLE_QT6),$(COMMA)"callMain"$(COMMA)"specialHTMLTargets")]
gb_EMSCRIPTEN_QTDEFS := -DQT_NO_LINKED_LIST -DQT_NO_JAVA_STYLE_ITERATORS -DQT_NO_EXCEPTIONS -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB
gb_Executable_EXT := .html
diff --git a/solenv/gbuild/platform/unxgcc.mk b/solenv/gbuild/platform/unxgcc.mk
index 58bca1f247cc..2cc3b2c24de6 100644
--- a/solenv/gbuild/platform/unxgcc.mk
+++ b/solenv/gbuild/platform/unxgcc.mk
@@ -182,7 +182,10 @@ $(if $(and $(filter CppunitTest Executable,$(TARGETTYPE)),$(filter EMSCRIPTEN,$(
$(if $(filter TRUE,$(ENABLE_QT5)), \
sed -e 's/@APPNAME@/$(subst $(gb_Executable_EXT),,$(notdir $(1)))/' $(QT5_PLATFORMS_SRCDIR)/wasm_shell.html > $(dir $(1))qt_$(notdir $(1)) && \
cp $(QT5_PLATFORMS_SRCDIR)/qtlogo.svg $(QT5_PLATFORMS_SRCDIR)/qtloader.js $(dir $(1)) && \
-) \
+,$(if $(filter TRUE,$(ENABLE_QT6)), \
+ sed -e 's/@APPNAME@/$(basename $(notdir $(1)))/g' -e 's/@APPEXPORTNAME@/$(basename $(notdir $(1)))_entry/g' -e 's/@PRELOAD@//g' -e 's|const instance = await qtLoad$(OPEN_PAREN){|const instance = await qtLoad$(OPEN_PAREN){ "arguments": ["--norestore"$(COMMA) "--nologo"$(COMMA) "--writer"$(COMMA) "/android/default-document/example.odt"]$(COMMA)|' -e 's/}$(CLOSE_PAREN);$$/}$(CLOSE_PAREN); window.Module = instance;/' $(QT6_PLATFORMS_SRCDIR)/wasm_shell.html > $(dir $(1))qt_$(basename $(notdir $(1))).html && \
+ cp $(QT6_PLATFORMS_SRCDIR)/qtlogo.svg $(QT6_PLATFORMS_SRCDIR)/qtloader.js $(dir $(1)) && \
+)) \
cp $(call gb_CustomTarget_get_workdir,static/emscripten_fs_image)/soffice.data $(dir $(1))/soffice.data && \
cp $(call gb_CustomTarget_get_workdir,static/emscripten_fs_image)/soffice.data.js.metadata $(dir $(1))/soffice.data.js.metadata \
)
diff --git a/static/README.wasm.md b/static/README.wasm.md
index 2bfecb404a7e..da5d80309bf0 100644
--- a/static/README.wasm.md
+++ b/static/README.wasm.md
@@ -223,7 +223,7 @@ improvement! ;)
Some usage examples through javascript of the current implementation:
```js
// inserts a string at the start of the Writer document.
-Module.init_unoembind_uno();
+init_unoembind_uno(Module);
let css = Module.unoembind_uno.com.sun.star;
xModel = Module.getCurrentModelFromViewSh();
xTextDocument = new css.text.XTextDocument(xModel, Module.UnoReference_Query.UNO_QUERY);
@@ -237,7 +237,7 @@ xModel.delete(); xTextDocument.delete(); xText.delete(); xSimpleText.delete(); x
```js
// changes each paragraph of the Writer document to a random color.
-Module.init_unoembind_uno();
+init_unoembind_uno(Module);
let css = Module.unoembind_uno.com.sun.star;
xModel = Module.getCurrentModelFromViewSh();
xTextDocument = new css.text.XTextDocument(xModel, Module.UnoReference_Query.UNO_QUERY);
diff --git a/static/source/embindmaker/embindmaker.cxx b/static/source/embindmaker/embindmaker.cxx
index e987a5ccab70..0eb3bc684db5 100644
--- a/static/source/embindmaker/embindmaker.cxx
+++ b/static/source/embindmaker/embindmaker.cxx
@@ -455,7 +455,7 @@ void writeJsMap(std::ostream& out, Module const& module, std::string const& pref
{
out << ",\n";
}
- out << prefix << "'" << ifc.copy(ifc.lastIndexOf('.') + 1) << "': Module." << jsName(ifc)
+ out << prefix << "'" << ifc.copy(ifc.lastIndexOf('.') + 1) << "': instance." << jsName(ifc)
<< "Ref";
comma = true;
}
@@ -597,9 +597,9 @@ SAL_IMPLEMENT_MAIN()
std::cerr << "Cannot open \"" << jsPathname << "\" for writing\n";
std::exit(EXIT_FAILURE);
}
- jsOut << "Module.init_unoembind_" << name
- << " = function() {\n"
- " Module.unoembind_"
+ jsOut << "function init_unoembind_" << name
+ << "(instance) {\n"
+ " instance.unoembind_"
<< name << " = {\n";
writeJsMap(jsOut, *module, " ");
jsOut << " };\n"