From 067f7453fe64ef416ff1afe9b42a7208004169b0 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 27 Nov 2023 13:05:51 +0100 Subject: [PATCH] JS: a better fix for wrapping/unwrapping JS objects implemented in Java --- .../org/teavm/jso/impl/JSAliasRenderer.java | 29 ++++++++++--------- .../java/org/teavm/jso/impl/JSOPlugin.java | 2 ++ .../java/org/teavm/jso/impl/JSWrapper.java | 10 +++++-- .../teavm/jso/impl/JSWrapperGenerator.java | 11 +++++++ 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSAliasRenderer.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSAliasRenderer.java index 27b5902bc..fa7e1dd17 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSAliasRenderer.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSAliasRenderer.java @@ -35,11 +35,13 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor { private static String variableChars = "abcdefghijklmnopqrstuvwxyz"; private SourceWriter writer; private ListableClassReaderSource classSource; + private JSTypeHelper typeHelper; @Override public void begin(RenderingManager context, BuildTarget buildTarget) { writer = context.getWriter(); classSource = context.getClassSource(); + typeHelper = new JSTypeHelper(context.getClassSource()); } @Override @@ -48,6 +50,8 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor { return; } + writer.append("let ").appendFunction("$rt_jso_marker").ws().append("=").ws() + .appendGlobal("Symbol").append("('jsoClass');").newLine(); writer.append("(function()").ws().append("{").softNewLine().indent(); writer.append("var c;").softNewLine(); for (String className : classSource.getClassNames()) { @@ -74,20 +78,23 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor { } } } - if (methods.isEmpty() && properties.isEmpty()) { + + var isJsClassImpl = typeHelper.isJavaScriptImplementation(className); + if (methods.isEmpty() && properties.isEmpty() && !isJsClassImpl) { continue; } - boolean first = true; + writer.append("c").ws().append("=").ws().appendClass(className).append(".prototype;") + .softNewLine(); + if (isJsClassImpl) { + writer.append("c[").appendFunction("$rt_jso_marker").append("]").ws().append("=").ws().append("true;") + .softNewLine(); + } + for (var aliasEntry : methods.entrySet()) { if (classReader.getMethod(aliasEntry.getValue()) == null) { continue; } - if (first) { - writer.append("c").ws().append("=").ws().appendClass(className).append(".prototype;") - .softNewLine(); - first = false; - } if (isKeyword(aliasEntry.getKey())) { writer.append("c[\"").append(aliasEntry.getKey()).append("\"]"); } else { @@ -101,11 +108,6 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor { if (propInfo.getter == null || classReader.getMethod(propInfo.getter) == null) { continue; } - if (first) { - writer.append("c").ws().append("=").ws().appendClass(className).append(".prototype;") - .softNewLine(); - first = false; - } writer.append("Object.defineProperty(c,") .ws().append("\"").append(aliasEntry.getKey()).append("\",") .ws().append("{").indent().softNewLine(); @@ -128,7 +130,8 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor { private boolean hasClassesToExpose() { for (String className : classSource.getClassNames()) { ClassReader cls = classSource.get(className); - if (cls.getMethods().stream().anyMatch(method -> getPublicAlias(method) != null)) { + if (cls.getMethods().stream().anyMatch(method -> getPublicAlias(method) != null) + || typeHelper.isJavaScriptImplementation(className)) { return true; } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java index 4d408da7a..2b80a86bd 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java @@ -72,6 +72,8 @@ public class JSOPlugin implements TeaVMPlugin { wrapperGenerator); jsHost.add(new MethodReference(JSWrapper.class, "jsToWrapper", JSObject.class, JSWrapper.class), wrapperGenerator); + jsHost.add(new MethodReference(JSWrapper.class, "isJSImplementation", Object.class, boolean.class), + wrapperGenerator); host.add(new MethodReference(JSWrapper.class, "jsToWrapper", JSObject.class, JSWrapper.class), wrapperGenerator); diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapper.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapper.java index 778e5b16e..3ff556744 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapper.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapper.java @@ -59,6 +59,9 @@ public final class JSWrapper { return null; } var js = directJavaToJs(o); + if (isJSImplementation(o)) { + return o; + } if (wrappers != null) { var type = JSObjects.typeOf(js); if (type.equals("object") || type.equals("function")) { @@ -161,18 +164,21 @@ public final class JSWrapper { @NoSideEffects public static native boolean isJava(JSObject obj); + @NoSideEffects + private static native boolean isJSImplementation(Object obj); + public static JSObject unwrap(Object o) { if (o == null) { return null; } - return o instanceof JSWrapper ? ((JSWrapper) o).js : directJavaToJs(o); + return isJSImplementation(o) ? directJavaToJs(o) : ((JSWrapper) o).js; } public static JSObject maybeUnwrap(Object o) { if (o == null) { return null; } - return o instanceof JSWrapper ? unwrap(o) : directJavaToJs(o); + return isJava(o) ? unwrap(o) : directJavaToJs(o); } public static JSObject javaToJs(Object o) { diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapperGenerator.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapperGenerator.java index f1c4ad2eb..a2bbb328e 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapperGenerator.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapperGenerator.java @@ -48,6 +48,17 @@ public class JSWrapperGenerator implements Injector, DependencyPlugin { context.getWriter().append(")"); } break; + case "isJSImplementation": + if (context.getPrecedence().ordinal() >= Precedence.EQUALITY.ordinal()) { + context.getWriter().append("("); + } + context.writeExpr(context.getArgument(0), Precedence.MEMBER_ACCESS); + context.getWriter().append("[").appendFunction("$rt_jso_marker").append("]") + .ws().append("===").ws().append("true"); + if (context.getPrecedence().ordinal() >= Precedence.EQUALITY.ordinal()) { + context.getWriter().append(")"); + } + break; } }