Skip to content

Commit 88ff91f

Browse files
shai-almogclaude
andcommitted
js-port: full type marshalling for native interfaces
Extend native-interface support to every NativeInterface type (all primitives, String, the primitive arrays byte[]/int[]/long[]/double[]/ float[]/boolean[]/char[]/short[], and String[]) in both directions. - NativeInterfaceBridge: per-primitive call* + callString/callVoid/ callObject, plus a typed callArray(iface, method, args, componentToken) for array returns. - JavaScriptBuilder: <Iface>Impl return-type dispatch routes arrays to callArray with the element's component token (JAVA_INT.../ java_lang_String) and boxes primitive args into the Object[]. - JavascriptNativeRegistry: the call* symbols are RUNTIME_IMPLEMENTED so the translator defers to the worker-side wrappers. - parparvm_runtime.js: bindNative wrappers funnel through the single __cn1_native_interface_call__ host hook and coerce the resolved JS value to the declared Java type -- createJavaString (String/String[]), _LfromNumber (long/long[]), jvm.newArray(token) (typed arrays), int/short/byte/char/float/double normalisation. toHostTransferArg now unboxes argument values too (boxed Integer/Long/.../Boolean -> JS primitive, long {__l} -> number) so primitive/long/array arguments marshal correctly to the host. - browser_bridge.js: one __cn1_native_interface_call__ handler dispatches to cn1_native_interfaces[iface][method](args..., callback). Verified in the local initializr bundle: no "Missing javascript native method" stubs remain, the bindNative wrappers/array builder are present, and WebsiteThemeNativeImpl is reachable. PeerComponent returns route through callObject (best-effort) pending dedicated peer wrapping. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 424534c commit 88ff91f

5 files changed

Lines changed: 168 additions & 43 deletions

File tree

Ports/JavaScriptPort/src/main/java/com/codename1/impl/platform/js/NativeInterfaceBridge.java

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,20 @@
1212
*
1313
* <p>The generated {@code <Interface>Impl} classes (emitted by the JavaScript
1414
* builder) delegate every interface method to one of the {@code call*} natives
15-
* below. Because this class lives in {@code com.codename1.impl.platform.js}, the
16-
* translator categorizes these natives as HOST_HOOK: the worker suspends and the
17-
* call is replayed on the <em>main thread</em> (via {@code browser_bridge.js}),
18-
* where the developer-authored JS stub runs with full DOM access and completes
19-
* the call through its callback. The worker resumes with the returned value.</p>
15+
* below, picked by the method's return type. These natives are runtime-
16+
* implemented in {@code parparvm_runtime.js}: the worker suspends, the call is
17+
* replayed on the <em>main thread</em> (via {@code browser_bridge.js}) where the
18+
* developer-authored JS stub runs with full DOM access and completes the call
19+
* through its callback, and the worker resumes with the result coerced to the
20+
* declared Java type.</p>
2021
*
21-
* <p>This preserves the existing JS native-interface impl format
22-
* ({@code cn1_native_interfaces["<pkg>_<Iface>"]["<method>_<paramTypes>"](args..., callback)})
23-
* so existing stubs work unchanged.</p>
22+
* <p>Supported types mirror {@code NativeInterface}: all primitives, {@code String},
23+
* primitive arrays plus {@code String[]} (via {@link #callArray}), and
24+
* {@code com.codename1.ui.PeerComponent} (routed through {@link #callObject}).</p>
2425
*
2526
* <p>{@code iface} is the interface class name with dots replaced by underscores
26-
* (the {@code cn1_native_interfaces} registry key) and {@code method} is the
27-
* trailing-underscore method key (e.g. {@code "isDarkMode_"}). {@code args}
27+
* (the {@code cn1_native_interfaces} registry key), {@code method} is the
28+
* trailing-underscore method key (e.g. {@code "isDarkMode_"}), and {@code args}
2829
* holds the (boxed) Java arguments, or an empty array for a no-arg method.</p>
2930
*/
3031
public final class NativeInterfaceBridge {
@@ -33,23 +34,33 @@ private NativeInterfaceBridge() {
3334

3435
public static native boolean callBoolean(String iface, String method, Object[] args);
3536

36-
public static native int callInt(String iface, String method, Object[] args);
37+
public static native byte callByte(String iface, String method, Object[] args);
3738

38-
public static native long callLong(String iface, String method, Object[] args);
39+
public static native short callShort(String iface, String method, Object[] args);
3940

40-
public static native double callDouble(String iface, String method, Object[] args);
41+
public static native int callInt(String iface, String method, Object[] args);
4142

42-
public static native float callFloat(String iface, String method, Object[] args);
43+
public static native char callChar(String iface, String method, Object[] args);
4344

44-
public static native byte callByte(String iface, String method, Object[] args);
45+
public static native long callLong(String iface, String method, Object[] args);
4546

46-
public static native short callShort(String iface, String method, Object[] args);
47+
public static native float callFloat(String iface, String method, Object[] args);
4748

48-
public static native char callChar(String iface, String method, Object[] args);
49+
public static native double callDouble(String iface, String method, Object[] args);
4950

5051
public static native String callString(String iface, String method, Object[] args);
5152

5253
public static native Object callObject(String iface, String method, Object[] args);
5354

5455
public static native void callVoid(String iface, String method, Object[] args);
56+
57+
/**
58+
* Array-returning call. {@code componentToken} identifies the element type so
59+
* the runtime can build the correctly-typed Java array: {@code "JAVA_INT"},
60+
* {@code "JAVA_BYTE"}, {@code "JAVA_LONG"}, {@code "JAVA_DOUBLE"},
61+
* {@code "JAVA_FLOAT"}, {@code "JAVA_BOOLEAN"}, {@code "JAVA_CHAR"},
62+
* {@code "JAVA_SHORT"} or {@code "java_lang_String"}. The caller casts the
63+
* result to the concrete array type.
64+
*/
65+
public static native Object callArray(String iface, String method, Object[] args, String componentToken);
5566
}

maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/JavaScriptBuilder.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,15 @@ private void appendNativeInterfaceImplMethod(StringBuilder sb, Method m) {
632632
sb.append(" return ").append(call).append("callChar(").append(invokeArgs).append(");\n");
633633
} else if (ret == String.class) {
634634
sb.append(" return ").append(call).append("callString(").append(invokeArgs).append(");\n");
635+
} else if (ret.isArray()) {
636+
// Primitive arrays + String[]: callArray builds the correctly-typed
637+
// Java array from the JS array the host returns (componentToken picks
638+
// the element type).
639+
sb.append(" return (").append(ret.getCanonicalName()).append(") ")
640+
.append(call).append("callArray(").append(invokeArgs)
641+
.append(", \"").append(arrayComponentToken(ret.getComponentType())).append("\");\n");
635642
} else {
643+
// PeerComponent / other reference types -- best effort passthrough.
636644
sb.append(" return (").append(ret.getCanonicalName()).append(") ")
637645
.append(call).append("callObject(").append(invokeArgs).append(");\n");
638646
}
@@ -664,6 +672,20 @@ private static String nativeInterfaceMethodKey(Method m) {
664672
return key.toString();
665673
}
666674

675+
// Runtime newArray() component-class token for an array's element type.
676+
private static String arrayComponentToken(Class<?> component) {
677+
if (component == int.class) return "JAVA_INT";
678+
if (component == long.class) return "JAVA_LONG";
679+
if (component == double.class) return "JAVA_DOUBLE";
680+
if (component == float.class) return "JAVA_FLOAT";
681+
if (component == boolean.class) return "JAVA_BOOLEAN";
682+
if (component == byte.class) return "JAVA_BYTE";
683+
if (component == short.class) return "JAVA_SHORT";
684+
if (component == char.class) return "JAVA_CHAR";
685+
if (component == String.class) return "java_lang_String";
686+
return component.getName().replace('.', '_');
687+
}
688+
667689
private static String xmlvmTypeName(Class<?> type) {
668690
if (type.isArray()) {
669691
return xmlvmTypeName(type.getComponentType()) + "_1ARRAY";

vm/ByteCodeTranslator/src/com/codename1/tools/translator/JavascriptNativeRegistry.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,23 @@ enum NativeCategory {
117117
"cn1_java_util_TimeZone_getTimezoneRawOffset_java_lang_String_R_int",
118118
"cn1_java_util_TimeZone_isTimezoneDST_java_lang_String_long_R_boolean",
119119
"cn1_com_codename1_impl_platform_js_VMHost_getLastEventCode_R_int",
120-
"cn1_com_codename1_impl_platform_js_VMHost_pollEventCode_R_int"
120+
"cn1_com_codename1_impl_platform_js_VMHost_pollEventCode_R_int",
121+
// NativeInterface bridge: runtime-implemented in parparvm_runtime.js
122+
// (bindNative). Each forwards to the main thread via the
123+
// __cn1_native_interface_call__ host hook and coerces the result to
124+
// the declared Java type (createJavaString / _LfromNumber / newArray).
125+
"cn1_com_codename1_impl_platform_js_NativeInterfaceBridge_callBoolean_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_boolean",
126+
"cn1_com_codename1_impl_platform_js_NativeInterfaceBridge_callByte_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_byte",
127+
"cn1_com_codename1_impl_platform_js_NativeInterfaceBridge_callShort_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_short",
128+
"cn1_com_codename1_impl_platform_js_NativeInterfaceBridge_callInt_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_int",
129+
"cn1_com_codename1_impl_platform_js_NativeInterfaceBridge_callChar_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_char",
130+
"cn1_com_codename1_impl_platform_js_NativeInterfaceBridge_callLong_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_long",
131+
"cn1_com_codename1_impl_platform_js_NativeInterfaceBridge_callFloat_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_float",
132+
"cn1_com_codename1_impl_platform_js_NativeInterfaceBridge_callDouble_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_double",
133+
"cn1_com_codename1_impl_platform_js_NativeInterfaceBridge_callString_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_java_lang_String",
134+
"cn1_com_codename1_impl_platform_js_NativeInterfaceBridge_callObject_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_java_lang_Object",
135+
"cn1_com_codename1_impl_platform_js_NativeInterfaceBridge_callArray_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_java_lang_String_R_java_lang_Object",
136+
"cn1_com_codename1_impl_platform_js_NativeInterfaceBridge_callVoid_java_lang_String_java_lang_String_java_lang_Object_1ARRAY"
121137
));
122138

123139
private static final Set<String> HOST_HOOK_PREFIXES = new HashSet<String>(Arrays.asList(

vm/ByteCodeTranslator/src/javascript/browser_bridge.js

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -214,31 +214,13 @@
214214
});
215215
}
216216

217-
// One handler per NativeInterfaceBridge.call* native symbol (the suffix encodes
218-
// the bridge method + its (String, String, Object[]) signature and return type;
219-
// void has no _R_). Dispatch is identical across return types -- the worker side
220-
// coerces the resolved value to the declared Java type.
221-
var __cn1NativeInterfaceBridgeSymbols = [
222-
'callBoolean_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_boolean',
223-
'callInt_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_int',
224-
'callLong_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_long',
225-
'callDouble_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_double',
226-
'callFloat_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_float',
227-
'callByte_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_byte',
228-
'callShort_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_short',
229-
'callChar_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_char',
230-
'callString_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_java_lang_String',
231-
'callObject_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_R_java_lang_Object',
232-
'callVoid_java_lang_String_java_lang_String_java_lang_Object_1ARRAY'
233-
];
234-
for (var __cn1NiIdx = 0; __cn1NiIdx < __cn1NativeInterfaceBridgeSymbols.length; __cn1NiIdx++) {
235-
(function(suffix) {
236-
var symbol = 'cn1_com_codename1_impl_platform_js_NativeInterfaceBridge_' + suffix;
237-
hostBridge.register(symbol, function(iface, method, args) {
238-
return cn1InvokeNativeInterface(iface, method, args);
239-
});
240-
})(__cn1NativeInterfaceBridgeSymbols[__cn1NiIdx]);
241-
}
217+
// Single host hook for every NativeInterfaceBridge.call* native. The worker-side
218+
// bindNative wrappers (parparvm_runtime.js) funnel here with (iface, method, args)
219+
// and coerce the resolved value to the declared Java return type, so dispatch is
220+
// uniform on this side.
221+
hostBridge.register('__cn1_native_interface_call__', function(iface, method, args) {
222+
return cn1InvokeNativeInterface(iface, method, args);
223+
});
242224

243225
var hostRefNextId = 1;
244226
var hostRefById = {};

vm/ByteCodeTranslator/src/javascript/parparvm_runtime.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2400,6 +2400,23 @@ const jvm = {
24002400
if (value.__class === "java_lang_String") {
24012401
return this.toNativeString(value);
24022402
}
2403+
// 64-bit long ({__l:1,l,h}) -> JS number for the host.
2404+
if (value.__l === 1) {
2405+
return _LtoNumber(value);
2406+
}
2407+
// Boxed primitives -> their JS value. NativeInterface args arrive boxed in an
2408+
// Object[] (Integer.valueOf(...) etc.); the host wants the plain value.
2409+
switch (value.__class) {
2410+
case "java_lang_Integer": return value.cn1_java_lang_Integer_value | 0;
2411+
case "java_lang_Short": return value.cn1_java_lang_Short_value | 0;
2412+
case "java_lang_Byte": return value.cn1_java_lang_Byte_value | 0;
2413+
case "java_lang_Character": return value.cn1_java_lang_Character_value | 0;
2414+
case "java_lang_Boolean": return !!value.cn1_java_lang_Boolean_value;
2415+
case "java_lang_Double": return Number(value.cn1_java_lang_Double_value);
2416+
case "java_lang_Float": return Number(value.cn1_java_lang_Float_value);
2417+
case "java_lang_Long": return _LtoNum(value.cn1_java_lang_Long_value);
2418+
default: break;
2419+
}
24032420
if (value.__cn1HostRef != null) {
24042421
return value.__cn1HostClass
24052422
? { __cn1HostRef: value.__cn1HostRef, __cn1HostClass: value.__cn1HostClass }
@@ -5159,6 +5176,83 @@ bindNative(["cn1_com_codename1_impl_platform_js_VMHost_pollEventCode_R_int",
51595176
return event && event.code != null ? (event.code | 0) : -1;
51605177
});
51615178

5179+
// ---- NativeInterface bridge -----------------------------------------------------
5180+
// The generated <Iface>Impl methods call these NativeInterfaceBridge.call*
5181+
// natives. Each forwards the (iface, method, args) tuple to the MAIN thread via
5182+
// the shared __cn1_native_interface_call__ host hook (browser_bridge.js runs the
5183+
// developer's JS stub with DOM access and resolves through its callback), then
5184+
// coerces the JS result to the declared Java return type. Args were already
5185+
// unboxed by toHostTransferArg (boxed primitives / Java String / long).
5186+
const __NI_PREFIX = "cn1_com_codename1_impl_platform_js_NativeInterfaceBridge_";
5187+
const __NI_SIG = "java_lang_String_java_lang_String_java_lang_Object_1ARRAY";
5188+
function* __cn1NativeInterfaceCall(iface, method, args) {
5189+
return yield jvm.invokeHostNative("__cn1_native_interface_call__", [iface, method, args]);
5190+
}
5191+
function __cn1NativeInterfaceArray(v, token) {
5192+
if (v == null) {
5193+
return null;
5194+
}
5195+
const len = v.length | 0;
5196+
const arr = jvm.newArray(len, token, 1);
5197+
for (let i = 0; i < len; i++) {
5198+
const e = v[i];
5199+
switch (token) {
5200+
case "java_lang_String": arr[i] = (e == null ? null : createJavaString(e)); break;
5201+
case "JAVA_LONG": arr[i] = _LfromNumber(Number(e || 0)); break;
5202+
case "JAVA_BOOLEAN": arr[i] = !!e; break;
5203+
case "JAVA_CHAR": arr[i] = (e | 0) & 0xffff; break;
5204+
case "JAVA_BYTE": arr[i] = ((e | 0) << 24) >> 24; break;
5205+
case "JAVA_SHORT": arr[i] = ((e | 0) << 16) >> 16; break;
5206+
case "JAVA_INT": arr[i] = e | 0; break;
5207+
case "JAVA_FLOAT": arr[i] = Math.fround(Number(e || 0)); break;
5208+
case "JAVA_DOUBLE": arr[i] = Number(e || 0); break;
5209+
default: arr[i] = e;
5210+
}
5211+
}
5212+
return arr;
5213+
}
5214+
bindNative([__NI_PREFIX + "callBoolean_" + __NI_SIG + "_R_boolean"], function*(iface, method, args) {
5215+
return !!(yield* __cn1NativeInterfaceCall(iface, method, args));
5216+
});
5217+
bindNative([__NI_PREFIX + "callByte_" + __NI_SIG + "_R_byte"], function*(iface, method, args) {
5218+
const v = yield* __cn1NativeInterfaceCall(iface, method, args); return ((v | 0) << 24) >> 24;
5219+
});
5220+
bindNative([__NI_PREFIX + "callShort_" + __NI_SIG + "_R_short"], function*(iface, method, args) {
5221+
const v = yield* __cn1NativeInterfaceCall(iface, method, args); return ((v | 0) << 16) >> 16;
5222+
});
5223+
bindNative([__NI_PREFIX + "callInt_" + __NI_SIG + "_R_int"], function*(iface, method, args) {
5224+
return (yield* __cn1NativeInterfaceCall(iface, method, args)) | 0;
5225+
});
5226+
bindNative([__NI_PREFIX + "callChar_" + __NI_SIG + "_R_char"], function*(iface, method, args) {
5227+
return ((yield* __cn1NativeInterfaceCall(iface, method, args)) | 0) & 0xffff;
5228+
});
5229+
bindNative([__NI_PREFIX + "callLong_" + __NI_SIG + "_R_long"], function*(iface, method, args) {
5230+
return _LfromNumber(Number((yield* __cn1NativeInterfaceCall(iface, method, args)) || 0));
5231+
});
5232+
bindNative([__NI_PREFIX + "callFloat_" + __NI_SIG + "_R_float"], function*(iface, method, args) {
5233+
return Math.fround(Number((yield* __cn1NativeInterfaceCall(iface, method, args)) || 0));
5234+
});
5235+
bindNative([__NI_PREFIX + "callDouble_" + __NI_SIG + "_R_double"], function*(iface, method, args) {
5236+
return Number((yield* __cn1NativeInterfaceCall(iface, method, args)) || 0);
5237+
});
5238+
bindNative([__NI_PREFIX + "callString_" + __NI_SIG + "_R_java_lang_String"], function*(iface, method, args) {
5239+
const v = yield* __cn1NativeInterfaceCall(iface, method, args);
5240+
return v == null ? null : createJavaString(v);
5241+
});
5242+
bindNative([__NI_PREFIX + "callObject_" + __NI_SIG + "_R_java_lang_Object"], function*(iface, method, args) {
5243+
const v = yield* __cn1NativeInterfaceCall(iface, method, args);
5244+
return (typeof v === "string") ? createJavaString(v) : (v == null ? null : v);
5245+
});
5246+
bindNative([__NI_PREFIX + "callVoid_" + __NI_SIG], function*(iface, method, args) {
5247+
yield* __cn1NativeInterfaceCall(iface, method, args);
5248+
return null;
5249+
});
5250+
bindNative([__NI_PREFIX + "callArray_java_lang_String_java_lang_String_java_lang_Object_1ARRAY_java_lang_String_R_java_lang_Object"],
5251+
function*(iface, method, args, token) {
5252+
const v = yield* __cn1NativeInterfaceCall(iface, method, args);
5253+
return __cn1NativeInterfaceArray(v, jvm.toNativeString(token));
5254+
});
5255+
51625256
// Worker liveness heartbeat (diag-only). If the worker wedges in a synchronous
51635257
// green-thread step this timer CANNOT fire (single-threaded) and the heartbeat
51645258
// STOPS; if the worker is merely parked/starved (idle, a host callback not

0 commit comments

Comments
 (0)