Skip to content

Commit d95ba83

Browse files
committed
native rebinding now preserves overwritten translated JS method functions
1 parent eef71e3 commit d95ba83

4 files changed

Lines changed: 164 additions & 39 deletions

File tree

Ports/JavaScriptPort/STATUS.md

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ Current State
1919
- Note: a short-lived `forceShow()` experiment in `BaseTest.registerReadyCallback` was reverted because it caused re-entrant callback loops and prevented `CN1SS:SUITE:FINISHED`.
2020
- New patch (not yet CI-validated in this document revision): host screenshot capture now tracks the real draw target canvas and includes off-DOM canvases reachable via host refs.
2121
- New patch (not yet CI-validated in this document revision): screenshot candidate selection now prioritizes near-screen-sized canvases to prevent tiny offscreen buffers from being selected (fixes unexpected `120x80`/`4x4` outputs).
22+
- New patch (not yet CI-validated in this document revision): native rebinding now preserves overwritten translated JS method functions in `jvm.translatedMethods` for targeted fallback/original resolution.
23+
- New patch (not yet CI-validated in this document revision): canvas visual scoring now samples multiple regions (not only center), to better detect non-white drawn content.
2224

2325
What Was Fixed In This Pass
2426
---------------------------
@@ -125,6 +127,26 @@ What Was Fixed In This Pass
125127
- Motivation:
126128
- Latest artifacts showed mixed dimensions (`120x80`, `4x4`) and colored tiny captures, indicating offscreen utility buffers were being selected.
127129

130+
11. Added translated-method preservation during native rebinding.
131+
- Files:
132+
- `vm/ByteCodeTranslator/src/javascript/parparvm_runtime.js`
133+
- `Ports/JavaScriptPort/src/main/webapp/port.js`
134+
- Changes:
135+
- Runtime now records pre-override translated functions in `jvm.translatedMethods` when installing/reinstalling natives.
136+
- `Cn1ssDeviceRunnerHelper.emitCurrentFormScreenshot...` fallback resolution now checks `jvm.translatedMethods` before globals/class tables.
137+
- CI fallback wrappers are tagged (`__cn1CiFallbackSymbol`) so resolution can skip recursive self-selection.
138+
- Motivation:
139+
- `originalMissing=1` persisted even though translated helper symbols exist in `translated_app.js`; rebinding order was losing discoverability of original translated handlers.
140+
141+
12. Improved screenshot canvas scoring robustness.
142+
- File:
143+
- `vm/ByteCodeTranslator/src/javascript/browser_bridge.js`
144+
- Changes:
145+
- Replaced single-center 48x48 sample with multi-region sampling (center, corners, and edge midpoints).
146+
- Score still prefers non-white/opaque content, but now reflects content distribution across the frame.
147+
- Motivation:
148+
- White-frame capture can occur when center-only sampling misses rendered content (e.g., content concentrated away from center).
149+
128150
Known Failing Symptoms (Latest CI Logs/Artifacts)
129151
-------------------------------------------------
130152

@@ -136,6 +158,12 @@ Known Failing Symptoms (Latest CI Logs/Artifacts)
136158
- Seen in `ValidatorLightweightPickerScreenshotTest`, `InPlaceEditViewTest`, `StreamApiTest`, `TimeApiTest`.
137159
- Timeout-only tests still timeout by design/behavior (`MediaPlayback...`, `BytecodeTranslatorRegression...`, selected API tests).
138160
- Screenshot pixels are still wrong in CI (host-canvas fallback path remains dominant in logs).
161+
- Local note from current workspace revalidation:
162+
- Fresh locally built bundle (`/tmp/hellocodenameone-javascript-port-local.zip`) currently emits only `bootstrap_placeholder` as a named stream.
163+
- Browser diagnostics in that run show repeated:
164+
- `PARPAR:DIAG:FALLBACK:cn1ssEmitCurrentFormScreenshotDom:originalMissing=1`
165+
- `PARPAR:DIAG:FALLBACK:cn1ssEmitCurrentFormScreenshotDom:noCanvas=1:test=...`
166+
- This local symptom differs from the latest CI artifacts (which still emit 32 screenshots, mostly white), so CI confirmation is required before accepting/rejecting the current translated-method resolution patch.
139167

140168
Other CI Signal
141169
---------------
@@ -151,11 +179,12 @@ Priority Next Steps
151179
- Confirm `settleChanged`, `canvasSig`, and `canvasSource` diagnostics vary across tests.
152180
2. Validate size normalization after large-canvas gating:
153181
- Expect screenshot dimensions to remain consistent at app target size (no `120x80`/`4x4` non-bootstrap outputs).
154-
3. If white-frame reuse persists, capture and compare per-test `settleSig`/`canvasSig`/`canvasSource` to identify whether paint is not happening or capture target is still wrong.
155-
3. Fix per-test null receiver/init path (`__classDef` null) at first failing stack, not via broad fallbacks.
156-
4. Fix missing `Button.initLaf(UIManager)` symbol resolution in worker runtime path.
157-
5. Fix worker-mode orientation lock path so DOM access is host-bridge mediated (no direct `document` access in worker).
158-
6. Confirm VM completeness stability in CI with parser/runtime patches (`expected 7` consistently).
182+
3. Validate `originalResolved=translated:...` vs `originalMissing=1` in CI browser log after translated-method preservation patch.
183+
4. If white-frame reuse persists, capture and compare per-test `settleSig`/`canvasSig`/`canvasSource` to identify whether paint is not happening or capture target is still wrong.
184+
5. Fix per-test null receiver/init path (`__classDef` null) at first failing stack, not via broad fallbacks.
185+
6. Fix missing `Button.initLaf(UIManager)` symbol resolution in worker runtime path.
186+
7. Fix worker-mode orientation lock path so DOM access is host-bridge mediated (no direct `document` access in worker).
187+
8. Confirm VM completeness stability in CI with parser/runtime patches (`expected 7` consistently).
159188

160189
Files Touched In This Pass
161190
--------------------------

Ports/JavaScriptPort/src/main/webapp/port.js

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -188,24 +188,28 @@ function emitCiFallbackMarker(symbol, markerType) {
188188
}
189189
function bindCiFallback(symbol, names, fn) {
190190
emitCiFallbackMarker(symbol, "ENABLED");
191-
bindNative(names, function*() {
191+
const wrappedFallback = function*() {
192192
emitCiFallbackMarker(symbol, "HIT");
193193
return yield* fn.apply(this, arguments);
194-
});
194+
};
195+
wrappedFallback.__cn1CiFallbackSymbol = symbol;
196+
bindNative(names, wrappedFallback);
195197
}
196198
function bindCiFallbackWithMethodId(symbol, names, fn) {
197199
emitCiFallbackMarker(symbol, "ENABLED");
198200
for (let i = 0; i < names.length; i++) {
199201
const methodId = names[i];
200-
bindNative([methodId], function*() {
202+
const wrappedFallback = function*() {
201203
emitCiFallbackMarker(symbol, "HIT");
202204
const args = new Array(arguments.length + 1);
203205
args[0] = methodId;
204206
for (let j = 0; j < arguments.length; j++) {
205207
args[j + 1] = arguments[j];
206208
}
207209
return yield* fn.apply(this, args);
208-
});
210+
};
211+
wrappedFallback.__cn1CiFallbackSymbol = symbol;
212+
bindNative([methodId], wrappedFallback);
209213
}
210214
}
211215
function aliasGlobalToImpl(symbol) {
@@ -3111,23 +3115,60 @@ function emitCn1ssChunks(base64, testName, channelName) {
31113115
}
31123116

31133117
const cn1ssEmitCurrentFormScreenshotMethodId = "cn1_com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunnerHelper_emitCurrentFormScreenshot_java_lang_String_java_lang_Runnable";
3114-
const cn1ssEmitCurrentFormScreenshotOriginal =
3115-
(typeof global[cn1ssEmitCurrentFormScreenshotMethodId] === "function")
3116-
? global[cn1ssEmitCurrentFormScreenshotMethodId]
3117-
: ((typeof global[cn1ssEmitCurrentFormScreenshotMethodId + "__impl"] === "function")
3118-
? global[cn1ssEmitCurrentFormScreenshotMethodId + "__impl"]
3119-
: null);
3118+
const cn1ssHelperClassName = "com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunnerHelper";
3119+
3120+
function isFallbackFunctionForSymbol(fn, symbol) {
3121+
return !!(fn && fn.__cn1CiFallbackSymbol === symbol);
3122+
}
3123+
3124+
function resolveTranslatedMethodCandidate(methodIds, ownerClassName, fallbackSymbol) {
3125+
const translatedMethods = jvm && jvm.translatedMethods ? jvm.translatedMethods : null;
3126+
if (translatedMethods) {
3127+
for (let i = 0; i < methodIds.length; i++) {
3128+
const methodId = methodIds[i];
3129+
const candidate = translatedMethods[methodId];
3130+
if (typeof candidate === "function" && !isFallbackFunctionForSymbol(candidate, fallbackSymbol)) {
3131+
return { fn: candidate, source: "translated:" + methodId };
3132+
}
3133+
}
3134+
}
3135+
for (let i = 0; i < methodIds.length; i++) {
3136+
const methodId = methodIds[i];
3137+
const candidate = global[methodId];
3138+
if (typeof candidate === "function" && !isFallbackFunctionForSymbol(candidate, fallbackSymbol)) {
3139+
return { fn: candidate, source: "global:" + methodId };
3140+
}
3141+
}
3142+
const ownerClass = jvm && jvm.classes ? jvm.classes[ownerClassName] : null;
3143+
const methods = ownerClass && ownerClass.methods ? ownerClass.methods : null;
3144+
if (methods) {
3145+
for (let i = 0; i < methodIds.length; i++) {
3146+
const methodId = methodIds[i];
3147+
const candidate = methods[methodId];
3148+
if (typeof candidate === "function" && !isFallbackFunctionForSymbol(candidate, fallbackSymbol)) {
3149+
return { fn: candidate, source: "class:" + ownerClassName + ":" + methodId };
3150+
}
3151+
}
3152+
}
3153+
return null;
3154+
}
31203155

31213156
bindCiFallback("Cn1ssDeviceRunnerHelper.emitCurrentFormScreenshotDom", [
31223157
cn1ssEmitCurrentFormScreenshotMethodId,
31233158
cn1ssEmitCurrentFormScreenshotMethodId + "__impl"
31243159
], function*(testName, completion) {
3160+
const fallbackSymbol = "Cn1ssDeviceRunnerHelper.emitCurrentFormScreenshotDom";
31253161
const test = toCn1StringValue(testName);
31263162
const normalizedTest = resolveCn1ssTestName(test);
31273163
let shouldUseDomFallback = true;
3128-
if (typeof cn1ssEmitCurrentFormScreenshotOriginal === "function") {
3164+
const originalResolved = resolveTranslatedMethodCandidate([
3165+
cn1ssEmitCurrentFormScreenshotMethodId,
3166+
cn1ssEmitCurrentFormScreenshotMethodId + "__impl"
3167+
], cn1ssHelperClassName, fallbackSymbol);
3168+
if (originalResolved && typeof originalResolved.fn === "function") {
31293169
try {
3130-
yield* cn1ssEmitCurrentFormScreenshotOriginal(testName, completion);
3170+
emitDiagLine("PARPAR:DIAG:FALLBACK:cn1ssEmitCurrentFormScreenshotDom:originalResolved=" + originalResolved.source);
3171+
yield* originalResolved.fn(testName, completion);
31313172
return null;
31323173
} catch (originalErr) {
31333174
emitDiagLine("PARPAR:DIAG:FALLBACK:cn1ssEmitCurrentFormScreenshotDom:originalInvokeErr="

vm/ByteCodeTranslator/src/javascript/browser_bridge.js

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -434,35 +434,62 @@
434434
}
435435
var sampleW = Math.min(48, w);
436436
var sampleH = Math.min(48, h);
437-
var startX = ((w - sampleW) / 2) | 0;
438-
var startY = ((h - sampleH) / 2) | 0;
439-
var img;
440-
try {
441-
img = ctx.getImageData(startX, startY, sampleW, sampleH);
442-
} catch (_err) {
443-
return null;
444-
}
445-
if (!img || !img.data || !img.data.length) {
446-
return null;
447-
}
448-
var data = img.data;
437+
var regions = [
438+
[0.5, 0.5],
439+
[0.2, 0.2], [0.8, 0.2], [0.2, 0.8], [0.8, 0.8],
440+
[0.5, 0.2], [0.5, 0.8], [0.2, 0.5], [0.8, 0.5]
441+
];
449442
var opaqueCount = 0;
450443
var nonWhiteCount = 0;
451-
for (var i = 0; i < data.length; i += 4) {
452-
var r = data[i] | 0;
453-
var g = data[i + 1] | 0;
454-
var b = data[i + 2] | 0;
455-
var a = data[i + 3] | 0;
456-
if (a > 12) {
457-
opaqueCount++;
458-
if (!(r >= 248 && g >= 248 && b >= 248)) {
459-
nonWhiteCount++;
444+
var signature = 'none';
445+
var sigHash = 2166136261 >>> 0;
446+
var sampled = 0;
447+
for (var ri = 0; ri < regions.length; ri++) {
448+
var rx = regions[ri][0];
449+
var ry = regions[ri][1];
450+
var startX = Math.max(0, Math.min(w - sampleW, (((w - sampleW) * rx) | 0)));
451+
var startY = Math.max(0, Math.min(h - sampleH, (((h - sampleH) * ry) | 0)));
452+
var img;
453+
try {
454+
img = ctx.getImageData(startX, startY, sampleW, sampleH);
455+
} catch (_err) {
456+
continue;
457+
}
458+
if (!img || !img.data || !img.data.length) {
459+
continue;
460+
}
461+
sampled++;
462+
var data = img.data;
463+
for (var i = 0; i < data.length; i += 4) {
464+
var r = data[i] | 0;
465+
var g = data[i + 1] | 0;
466+
var b = data[i + 2] | 0;
467+
var a = data[i + 3] | 0;
468+
if (a > 12) {
469+
opaqueCount++;
470+
if (!(r >= 248 && g >= 248 && b >= 248)) {
471+
nonWhiteCount++;
472+
}
473+
}
474+
if ((i & 31) === 0) {
475+
sigHash ^= r;
476+
sigHash = Math.imul(sigHash, 16777619);
477+
sigHash ^= g;
478+
sigHash = Math.imul(sigHash, 16777619);
479+
sigHash ^= b;
480+
sigHash = Math.imul(sigHash, 16777619);
481+
sigHash ^= a;
482+
sigHash = Math.imul(sigHash, 16777619);
460483
}
461484
}
462485
}
486+
if (sampled === 0) {
487+
return null;
488+
}
489+
signature = String((sigHash >>> 0).toString(16));
463490
return {
464491
score: (nonWhiteCount * 4) + opaqueCount,
465-
signature: shortSignatureFromImageData(img)
492+
signature: signature
466493
};
467494
}
468495

vm/ByteCodeTranslator/src/javascript/parparvm_runtime.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1797,7 +1797,19 @@ function bindNative(names, fn) {
17971797
cls.methods[name] = fn;
17981798
}
17991799
}
1800+
function rememberTranslatedMethod(name, existingFn) {
1801+
if (typeof existingFn !== "function" || existingFn === fn) {
1802+
return;
1803+
}
1804+
if (!jvm.translatedMethods) {
1805+
jvm.translatedMethods = Object.create(null);
1806+
}
1807+
if (typeof jvm.translatedMethods[name] !== "function") {
1808+
jvm.translatedMethods[name] = existingFn;
1809+
}
1810+
}
18001811
function registerNative(name) {
1812+
rememberTranslatedMethod(name, global[name]);
18011813
jvm.nativeMethods[name] = fn;
18021814
global[name] = fn;
18031815
jvm[name] = fn;
@@ -1831,16 +1843,32 @@ function installCompatibilityClasses() {
18311843
}
18321844
}
18331845
function installNativeBindings() {
1846+
if (!jvm.translatedMethods) {
1847+
jvm.translatedMethods = Object.create(null);
1848+
}
18341849
const names = Object.keys(jvm.nativeMethods || {});
18351850
for (let i = 0; i < names.length; i++) {
18361851
const name = names[i];
18371852
const nativeFn = jvm.nativeMethods[name];
18381853
if (typeof nativeFn !== "function") {
18391854
continue;
18401855
}
1856+
const existingGlobal = global[name];
1857+
if (typeof existingGlobal === "function" && existingGlobal !== nativeFn) {
1858+
if (typeof jvm.translatedMethods[name] !== "function") {
1859+
jvm.translatedMethods[name] = existingGlobal;
1860+
}
1861+
}
18411862
global[name] = nativeFn;
18421863
jvm[name] = nativeFn;
18431864
if (!name.endsWith("__impl")) {
1865+
const implName = name + "__impl";
1866+
const existingImpl = global[implName];
1867+
if (typeof existingImpl === "function" && existingImpl !== nativeFn) {
1868+
if (typeof jvm.translatedMethods[implName] !== "function") {
1869+
jvm.translatedMethods[implName] = existingImpl;
1870+
}
1871+
}
18441872
global[name + "__impl"] = nativeFn;
18451873
jvm[name + "__impl"] = nativeFn;
18461874
}

0 commit comments

Comments
 (0)