Skip to content

Initial work on the new JavaScript port#4677

Merged
shai-almog merged 111 commits intomasterfrom
javascript-port-initial-work
Apr 22, 2026
Merged

Initial work on the new JavaScript port#4677
shai-almog merged 111 commits intomasterfrom
javascript-port-initial-work

Conversation

@shai-almog
Copy link
Copy Markdown
Collaborator

No description provided.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 31, 2026

✅ Continuous Quality Report

Test & Coverage

Static Analysis

Generated automatically by the PR CI workflow.

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented Mar 31, 2026

Compared 37 screenshots: 37 matched.

Native Android coverage

  • 📊 Line coverage: 7.90% (4190/53047 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 6.20% (20685/333602), branch 3.02% (972/32188), complexity 3.67% (1130/30772), method 6.48% (929/14343), class 10.63% (202/1900)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 7.90% (4190/53047 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 6.20% (20685/333602), branch 3.02% (972/32188), complexity 3.67% (1130/30772), method 6.48% (929/14343), class 10.63% (202/1900)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 939.000 ms
Base64 CN1 encode 161.000 ms
Base64 encode ratio (CN1/native) 0.171x (82.9% faster)
Base64 native decode 944.000 ms
Base64 CN1 decode 282.000 ms
Base64 decode ratio (CN1/native) 0.299x (70.1% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog shai-almog force-pushed the javascript-port-initial-work branch from bfdfa3c to 771920d Compare March 31, 2026 04:08
@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented Mar 31, 2026

iOS screenshot updates

Compared 37 screenshots: 33 matched, 1 updated, 3 errors.

  • graphics-draw-arc — comparison error. Comparison error: 98635 > -901360897

    No preview available for this screenshot.
    Full-resolution PNG saved as graphics-draw-arc.png in workflow artifacts.

  • graphics-draw-image-rect — comparison error. Comparison error: PNG chunk truncated before CRC while processing: /Users/runner/work/_temp/cn1-ios-tests-ISPNUd/graphics-draw-image-rect.png

    No preview available for this screenshot.
    Full-resolution PNG saved as graphics-draw-image-rect.png in workflow artifacts.

  • graphics-draw-string — comparison error. Comparison error: 82239 > -1687093201

    No preview available for this screenshot.
    Full-resolution PNG saved as graphics-draw-string.png in workflow artifacts.

  • landscape — updated screenshot. Screenshot differs (2556x1179 px, bit depth 8).

    landscape
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as landscape.png in workflow artifacts.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 229 seconds

Build and Run Timing

Metric Duration
Simulator Boot 108000 ms
Simulator Boot (Run) 1000 ms
App Install 14000 ms
App Launch 10000 ms
Test Execution 167000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 1806.000 ms
Base64 CN1 encode 1873.000 ms
Base64 encode ratio (CN1/native) 1.037x (3.7% slower)
Base64 native decode 1046.000 ms
Base64 CN1 decode 1511.000 ms
Base64 decode ratio (CN1/native) 1.445x (44.5% slower)
Base64 SIMD encode 645.000 ms
Base64 encode ratio (SIMD/native) 0.357x (64.3% faster)
Base64 encode ratio (SIMD/CN1) 0.344x (65.6% faster)
Base64 SIMD decode 551.000 ms
Base64 decode ratio (SIMD/native) 0.527x (47.3% faster)
Base64 decode ratio (SIMD/CN1) 0.365x (63.5% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 122.000 ms
Image createMask (SIMD on) 23.000 ms
Image createMask ratio (SIMD on/off) 0.189x (81.1% faster)
Image applyMask (SIMD off) 180.000 ms
Image applyMask (SIMD on) 78.000 ms
Image applyMask ratio (SIMD on/off) 0.433x (56.7% faster)
Image modifyAlpha (SIMD off) 210.000 ms
Image modifyAlpha (SIMD on) 111.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.529x (47.1% faster)
Image modifyAlpha removeColor (SIMD off) 232.000 ms
Image modifyAlpha removeColor (SIMD on) 130.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.560x (44.0% faster)
Image PNG encode (SIMD off) 1447.000 ms
Image PNG encode (SIMD on) 1069.000 ms
Image PNG encode ratio (SIMD on/off) 0.739x (26.1% faster)
Image JPEG encode 666.000 ms

@shai-almog shai-almog force-pushed the javascript-port-initial-work branch from 771920d to 7b7456c Compare March 31, 2026 14:20
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 31, 2026

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 644 total, 0 failed, 2 skipped

Benchmark Results

  • Execution Time: 9613 ms

  • Hotspots (Top 20 sampled methods):

    • 21.76% com.codename1.tools.translator.Parser.isMethodUsed (370 samples)
    • 19.47% java.lang.String.indexOf (331 samples)
    • 15.88% java.util.ArrayList.indexOf (270 samples)
    • 5.18% java.lang.Object.hashCode (88 samples)
    • 5.12% com.codename1.tools.translator.BytecodeMethod.addToConstantPool (87 samples)
    • 3.41% java.lang.System.identityHashCode (58 samples)
    • 2.59% com.codename1.tools.translator.ByteCodeClass.calcUsedByNative (44 samples)
    • 1.71% com.codename1.tools.translator.ByteCodeClass.updateAllDependencies (29 samples)
    • 1.59% com.codename1.tools.translator.ByteCodeClass.markDependent (27 samples)
    • 1.53% com.codename1.tools.translator.BytecodeMethod.optimize (26 samples)
    • 1.41% java.lang.StringBuilder.append (24 samples)
    • 1.41% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (24 samples)
    • 1.06% java.lang.StringCoding.encode (18 samples)
    • 0.94% com.codename1.tools.translator.Parser.cullMethods (16 samples)
    • 0.94% com.codename1.tools.translator.BytecodeMethod.appendMethodC (16 samples)
    • 0.88% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (15 samples)
    • 0.88% com.codename1.tools.translator.Parser.getClassByName (15 samples)
    • 0.65% com.codename1.tools.translator.BytecodeMethod.isMethodUsedByNative (11 samples)
    • 0.59% sun.nio.fs.UnixNativeDispatcher.open0 (10 samples)
    • 0.59% java.io.UnixFileSystem.getBooleanAttributes0 (10 samples)
  • ⚠️ Coverage report not generated.

Static Analysis

  • ✅ SpotBugs: no findings (report was not generated by the build).
  • ⚠️ PMD report not generated.
  • ⚠️ Checkstyle report not generated.

Generated automatically by the PR CI workflow.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 2, 2026

Cloudflare Preview

@shai-almog shai-almog force-pushed the javascript-port-initial-work branch 12 times, most recently from 00a257b to 3701252 Compare April 3, 2026 12:17
shai-almog and others added 8 commits April 21, 2026 14:16
…i math

The 9 JavaScriptDisplayMetricsTest tests pass in 2.6s, proving the
density ladder + ppi math is not the source of the 600x300 Switch
pills in the Kotlin screenshot. Record which suspects this rules
out (density, convertToPixels(3mm)) and what is still on the table
(theme padding override, BoxLayout.encloseX stretch, Switch-font
path).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The new test probes every (density, mm) pair from MEDIUM up to 4K
against the observed 276 px per-side padding needed to reach the
600 px Kotlin switch pill. Documents in the test body which combos
are plausible (4K + 5.5mm) vs which are not (MEDIUM + 43mm), so
the next investigator knows the range that runtime instrumentation
has to cover. Passes in 3s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matches the JavaScriptDisplayMetricsTest pattern. Compiles the port's
shape path adapter standalone against the CN1 core test dependency,
then drives addShapeToPath() with a Proxy-based PathSink recording every
canvas op. Six tests cover:

  * simple rectangle -> moveTo + 3 lineTo + closePath (order)
  * quadraticCurveTo + bezierCurveTo dispatch
  * Sheet-like rounded-rect panel (approximated) -> >=2 beziers, >=3
    lineTos, exactly 1 moveTo + 1 closePath
  * empty path -> no emission
  * resolveJoin / resolveCap maps (miter/round/bevel, butt/round/square)
    including fallback branches

Localizes the Sheet / Picker panel-missing-bg bug away from the port's
shape->canvas path translation. Remaining suspects:

  * CN1 core RoundRectBorder.paintBorderBackground precondition chain
    (shadowOpacity == 0, bgImage == null, bgType in {SCALED, NONE},
    bgTransparency != 0) - needs instrumentation of the Sheet Style
    actually resolved on JS port
  * Theme loading path - Sheet UIID may not pick up its iOS7Theme.res
    bg properties on JS port

All 6 tests pass in 1.9s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
10 tests, 5.3s. Covers the resolve-width/height ladder, the
surface-kind dispatch (LOADED > MUTABLE > NONE), the zero-dim
draw no-op, and pattern-cache invalidation. All driven via a
Proxy-backed ImageModel so each test controls the exact
dimensions reported.

Third piece in the Switch/Sheet bug triage suite:

  * JavaScriptDisplayMetricsTest - rules out density/ppi math
  * JavaScriptShapePathAdapterTest - rules out shape->canvas path
  * JavaScriptNativeImageAdapterTest - rules out NativeImage
    dimension resolution

If all three pass but the Kotlin pill is still 600 px wide, the
bug is not in the port's pure-Java math. Remaining places to
instrument live: the worker-bridged window.devicePixelRatio,
the Sheet/Switch Style padding mm values, and BoxLayout.encloseX
width distribution.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
26 unit tests across three suites (density+ppi, shape path, native
image adapter) run in ~10 s total and rule out three concrete
hypotheses for the Kotlin huge-switch and Sheet missing-bg bugs.
Document the remaining suspects in priority order so the next
pass knows exactly where to instrument.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nent

Root cause of the Kotlin switch rendering as ~600x300 pills (and any
other CN1 sizing path that reads fractional theme constants):
parparvm_runtime.js parseDblImpl binding returned Number(text) without
applying the 10^exponent factor split out by StringToReal.parseDouble.
So Double.parseDouble("1.4") resolved to 14, "2.5" to 25, "1.5" to 15.

Switch.getTrackScaleX() defaults to "3" but the iOS theme overrides to
"2.5" which parseDouble mis-read as 25. Track width becomes fontSize*25
= ~475 instead of fontSize*2.5 = ~47. The Switch paints a rounded-rect
component background at that huge bounds, producing the ~479x285 pill
that showed up in kotlin.png.

Fix: in the parparvm_runtime.js parseDblImpl binding, multiply the
parsed integer by 10^exponentIndex.

Reproduced via new SwitchIsolationScreenshotTest - stripped-down pure
Java version of KotlinUiTest's switch row. Before the fix every switch
rendered at 479x285 regardless of wrapping layout. After the fix they
render at ~50x25 with proper off (gray) / on (green) states.

Regression guard: JsDoubleParseApp fixture + new test
JavascriptRuntimeSemanticsTest.parseDoubleAppliesExponentFromStringToRealSplit
covers 9 decimal / sign / exponent combos that all failed before the
fix and pass after.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Document how the user-suggested "simple Java reproduction" of the
Kotlin Switch caused the bug to surface immediately (all three
isolation-test rows showed identical oversized pills, proving it
wasn't layout / Kotlin / sibling-specific). That led to a
port-side createMutableImage log that pinpointed track images at
479x285 and thumb at 270x270, which - given fontSize=19 (verified)
- could only come from scale constants being 10x too large. Direct
Double.parseDouble("1.4") returned 14, confirming the runtime-js
binding bug.

Also note that the three earlier "disappointing" unit-test passes
(density, shape path, native image) were in fact correctly ruling
out three false leads. The fix took one line once the fixture was
in place.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…line

Same pattern as SwitchIsolationScreenshotTest: pure-Java minimal
reproduction showing just a Sheet with two labels and a
Style dump.

Diagnostic output from running it:
  uiid=Sheet bgColor=ffffff bgTransparency=255 bgType=0
  bgImage=null borderClass=RoundRectBorder

All RoundRectBorder.paintBorderBackground preconditions are met
(bgTransparency != 0, bgImage == null, bgType in {NONE, SCALED},
border is RoundRectBorder). So the missing white rounded panel
in Sheet.png is NOT a style resolution bug.

The actual bug is downstream - in the shadowOpacity > 0 path
which delegates to createTargetImage() - a mutable Image that
fills the shape into its own graphics, then gets drawn onto the
main canvas via g.drawImage. Either the mutable-image fillShape
doesn't render to its canvas, or the drawImage of the target back
to the main canvas drops the fill.

Next step (follow-up): add a port-side log inside
BufferedGraphics.fillShape to confirm fires during sheet paint.
If not, the createTargetImage path is being skipped. If it does
fire, the drawImage of mutable-surface back to main canvas is
dropping the alpha.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread vm/ByteCodeTranslator/src/javascript/browser_bridge.js Fixed
Comment thread vm/ByteCodeTranslator/src/javascript/browser_bridge.js Fixed
Comment thread vm/ByteCodeTranslator/src/javascript/browser_bridge.js Fixed
Comment thread vm/ByteCodeTranslator/src/javascript/browser_bridge.js Fixed
Comment thread vm/ByteCodeTranslator/src/javascript/browser_bridge.js Fixed
shai-almog and others added 18 commits April 21, 2026 21:06
…tial-work

# Conflicts:
#	scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunnerHelper.java
- Playwright viewport + canvas now 375x667 (down from 1280x900);
  cuts PNG size ~10x and keeps baselines in phone-portrait aspect.
- HTML5Implementation.getTransform(Object, Transform) was using the
  shared `graphics` field instead of the passed nativeGraphics arg —
  restored Node.render's save/restore to read from the correct ctx.
- Add PickerIsolationScreenshotTest mirroring the Switch/Sheet
  isolation pattern for iterating on the missing spinner wheels.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
isPhone_() uses a mobile-browser UA regex that rejects headless
Chromium, so Display.isTablet() was returning true at 375x667 and
routing Picker through PickerDialogTablet — which wraps the three
date Spinner3Ds in a full-screen dialog that leaves the wheels
off-canvas. Fall back to Material's sw600dp breakpoint on the
shortest viewport side when UA says not-phone.

Also:
- Gate the PrimitiveAdapter op-submit log behind PortDiag.PICKER_DIAG
  so it no longer burns 80 lines at suite start.
- Expand PickerIsolationScreenshotTest to dump the popup component
  tree (CN1SS:DIAG:picker-tree), probing both layout (Spinner3Ds now
  land as expected at 0,0,50x80 / 50,0,118x80 / 168,0,80x80 inside
  the 248x80 BoxLayout row) and the residual scene-graph render bug.

Remaining: the Spinner3D Scene/Node tree lays out correctly now but
its children (date rows) still don't paint. Separate commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Scene-graph render was invisibly producing degenerate canvas matrices
(m00=0,m11=0,tx=0,ty=0) that collapsed every wheel row onto a single
pixel. Root cause: ParparVM passes JSO-interface parameters wrapped in a
Java object where the raw JS value lives under __jsValue, so
`inner.getScaleX()` (and translate/concatenate/scale/rotate) dispatched
to the wrapper — which defines no such methods — and silently returned
undefined. canvas.setTransform(NaN, ...) then drove the transform to
zero, so every Node.render transform in the Picker's DateSpinner3D
mapped its rows off-canvas.

Fix:
- Reduce JSOAffineTransform to an empty tag interface.
- Replace every inner.jsoMethod() call with @JSBody helpers that unwrap
  __jsValue and manipulate the six m00..m12 fields directly. Keeps the
  Java API (JSAffineTransform.translate/concatenate/…) unchanged so all
  callers continue to work.
- Update the port.js bindCiFallback for
  JSAffineTransform.JSOFactory.setTransform/transform to read the same
  m00..m12 fields instead of the now-removed accessor methods.
- Drop the temporary PICKER_DIAG tracing from SetTransform / DrawString
  / FillRect / ClipRect after confirming the fix.
- Simplify PickerIsolationScreenshotTest now that the wheels render
  (kept as a targeted regression guard).

Picker date wheels (month/day/year) now paint correctly in the
lightweight popup at 375x667. Remaining DateSpinner3D column-layout
polish is a separate issue.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…test

List.of() is a Java 9 API; vm/tests compiles with source/target 1.8.
Swap for Collections.singletonList to unblock CI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- browser_bridge.js loadTrueTypeFont: escape backslashes before single
  quotes when interpolating fontUrl/fontFormat/fontName into the CSS
  string. A font path ending in \ could otherwise close the CSS string
  and smuggle further tokens into the stylesheet / FontFace descriptor.
  Factored into a small cssStringEscape helper matching the safe pattern
  fontmetrics.js already uses.
- HTML5Implementation.showButton_: the button label was concatenated
  into `<button>` markup fed to jQuery(), which executes tags and
  <script> in the text. Switched to building an empty button and using
  jQuery's .text() to set the label safely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Canvas rendering runs in a Web Worker whose self.devicePixelRatio is
undefined — the main-thread DPR never reaches it. getDevicePixelRatio()
therefore fell back to 1.0 on every phone-sized browser run, locking
CN1's density picker to DENSITY_MEDIUM regardless of the real ratio
and halving every padding/margin/font-size relative to iOS.

- browser_bridge.js: read window.devicePixelRatio on the main thread and
  pass it in the `start` message alongside locationSearch.
- worker.js: extract it on receive and assign self.devicePixelRatio so
  the existing `win.devicePixelRatio` lookup (via `self.window = self;`)
  returns the real ratio.
- run-javascript-headless-browser.mjs: set deviceScaleFactor: 2 so the
  Playwright page emulates a retina viewport (DPR=2 -> DENSITY_VERY_HIGH
  matching the iOS/Android reference proportions).

MainActivity / LightweightPickerButtons screenshots now render with
readable fonts and iOS-like padding instead of the compressed
low-density look.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ssets/

getResourceAsStream unconditionally rewrote every non-icon.png resource
to assets/<name>, so Resources.openLayered("/theme") only saw the big
merged system theme in assets/theme.res (iOS7 + platform defaults) and
never the app's own top-level theme.res that the ParparVM translator
emits from theme.css. The app's UIID overrides — e.g. TabsColorSync's
`color: red` / `color: green` — were silently discarded, leaving
material-icon tabs and custom-coloured labels rendering in the base
fgColor (black).

Try the bundle root first and fall back to assets/ only when the root
lookup returns null. icon.png continues to skip the assets/ prefix
either way.

Tabs now render with their colored material icons and labels.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…apter

The debug submit path I added while chasing the picker scene-graph bug
introduced a dependency on PortDiag.java, which isn't in the standalone
source set that JavaScriptRuntimeFacadeTest compiles the adapter with —
so the test's "compile as standalone Java helpers" assertion started
failing in CI with exit code 1. The logging was only useful during the
investigation and the bug is fixed, so drop the gate, inline the
submits, and remove the now-unused PortDiag entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s work

port.js captured the translator-emitted convertUnit/setPaddingUnit/
setMarginUnit originals eagerly, via jvm.classes[...].methods[...] at
script-eval time. That read returned null when the translated class
table wasn't yet populated, and the bindCiFallback wrapper then
short-circuited every subsequent call to `return 0`. Style.getMarginTop
/ getPaddingTop pipe through convertUnit, so every component's pixel
margin and padding collapsed to zero — the Picker's InteractionDialog
in particular lost its 729-pixel top margin and inflated to fill the
whole layered pane instead of anchoring at the bottom, and the
DateSpinner3D columns lost their 1.5x-label-width spacing and
collapsed into one visual column. Other layout paths happened to avoid
the regression because they read the raw float via getPaddingValue /
getMarginFloatValue.

- Replace the eager captures with a resolveTranslatedMethod(className,
  methodId) helper that walks jvm.translatedMethods (the pre-override
  mirror bindNative already keeps), then jvm.classes[...].methods, and
  finally the global symbol (skipping any entry already wrapped by a
  fallback to avoid recursion).
- Update the three Style.* fallbacks to resolve per call.
- As a defensive fallback, when the original truly can't be found, the
  null-unit path (which CN1 treats as pixels) now returns `value | 0`
  instead of 0 so a layout still gets a reasonable pixel value.

Also fix isTablet's sw600 heuristic: it compared native pixel width
(getDisplayWidth) to 600, but on a retina viewport (DPR=2) the native
width is doubled, so a 375-CSS-pixel phone was classified as a tablet
and Picker routed through its tablet dialog. Divide by DPR so the
threshold is in CSS px.

Net effect: LightweightPickerButtons + PickerIsolation now render with
a bottom-anchored popup, three cleanly-separated day / month / year
columns, and the correct fonts/colors matching the iOS reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
port.js emitted a hardcoded 1x1 transparent PNG under the
"bootstrap_placeholder" stream on every VM start. It existed as a
dummy CN1SS payload to validate the end-to-end CI pipeline before the
real suite was producing screenshots; now that the suite reliably
finishes with CN1SS:SUITE:FINISHED and emits its own named streams,
the bootstrap_placeholder output is pure noise. The tolerant
comparator in scripts/run-javascript-screenshot-tests.sh already
skips the sentinel so no downstream change is needed.

Also delete PickerIsolationScreenshotTest and
SheetIsolationScreenshotTest — both were added during the Spinner3D /
InteractionDialog investigation as targeted repros while the wheels
and popup positioning were broken. With the root causes fixed
(convertUnit fallback, isTablet DPR heuristic, JSO-method dispatch),
the regressions are covered by LightweightPickerButtonsScreenshotTest
and SheetScreenshotTest themselves, so the isolation tests are dead
weight. Keeping SwitchIsolationScreenshotTest which tracks the
earlier Kotlin-switch bug.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Theme 9-patch borders, EncodedImage draws, and byte-array-sourced
Image.createImage() calls all funnel through
BrowserDomRenderingBackend.createCrossOriginImageElement, which in the
stock Java flow returns as soon as the worker asks the main thread to
set img.src — before the browser has actually fetched or decoded the
bytes. On the first paint NativeImage.isComplete() then returns false
for most of the images, NativeImage.draw silently no-ops, and
Border.paintBorderBackground ends up painting only the one 9-patch
piece whose blob happened to decode fastest. That surfaced as the
~20x20 white square in the top-left of every form and as the missing
image variants in graphics-draw-image-rect (RGBImage / Image.createImage
from byte[] / from int[] RGBA).

Fix is the pre-paint barrier we agreed on:

- browser_bridge.js: new __cn1_decode_image_from_url__ host-bridge
  handler that creates the <img> on the main thread, awaits
  HTMLImageElement.decode() (with a load/error/10s-timeout fallback
  for browsers that lack decode), and wraps the element in a
  storeHostRef marker so it can be posted back to the worker without
  tripping structured-clone on the raw DOM node.
- port.js: bindCiFallback on
  BrowserDomRenderingBackend.createCrossOriginImageElement that calls
  the new host bridge and yields on the promise, so every image the
  worker creates is already decoded by the time it returns. Covers
  createBlobImageElement too since that method chains through
  createCrossOriginImageElement.

Net effect: title bars render their full 9-patch border (white) in
every form; graphics-draw-image-rect shows all image-type rows;
LightweightPickerButtons / MainActivity / Tabs are unaffected
visually but their title decorations are now produced by the 9-patch
instead of the lucky first piece.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…TATUS,

      commit baseline screenshots, strip leftover debug logging

- Delete SwitchIsolationScreenshotTest (last remaining investigation-era
  fixture) and its entry in Cn1ssDeviceRunner. The Kotlin on/off switch
  rendering regression it was guarding is now covered by the standing
  KotlinUiTest screenshot, so the extra row is redundant.

- Replace the 1099-line STATUS.md development journal with a concise
  port-status doc describing how to build / test / regenerate
  baselines, plus the set of intentionally time-limited tests. The
  journal had served its purpose and was tracking stale investigation
  notes (including items long since fixed).

- Strip the leftover CN1JS: println diagnostic in
  HTML5Implementation.drawString, HTML5Implementation's RenderQueue
  drain/flush counters, and the matching debugSubmit path in
  JavaScriptShapeGradientRenderAdapter. All three were investigation
  scaffolding that fired on every paint once for the first 60-80
  events, showing up as noise in CI logs. The companion removal in
  JavaScriptPrimitiveRenderAdapter already landed (ad73e89); this
  catches the siblings.

- Commit the curated JS-port baseline screenshots the user produced
  at 750x1334 (post-DPR / density / theme / image-decode-barrier
  fixes) into scripts/javascript/screenshots so the CI screenshot
  comparator has a stable reference to diff against.

Full suite: 49/49 tests complete, CN1SS:SUITE:FINISHED emitted, 34
PNGs captured. Unit tests: 42/42 pass (JavaScriptRuntimeFacadeTest,
JavaScriptNativeImageAdapterTest, JavaScriptShapePathAdapterTest,
JavaScriptDisplayMetricsTest).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ions

Scrubs leftover debug scaffolding accumulated during the screenshot
investigation, and reverts hellocodenameone test changes that would
have broken the existing iOS/Android baselines.

- Graphics.java (core, frozen): drop debug counters, debugClassName
  helper, and CN1JS: println blocks in drawString/drawImage/
  setTransform/scale/tileImage.
- BufferedGraphics.java: drop queue/drawImage debug counters,
  debugQueueEvent/sampleOps helpers, and flush() println pairs.
- SheetScreenshotTest: restore setTitleComponent(BoxLayout…) layout so
  the iOS/Android screenshot baselines keep matching.
- TabsScreenshotTest: drop CN1SS:TABS:step prints; restore direct
  addTab calls.
- BaseTest: drop the graphics-draw-image-rect 4000 ms delay override
  (no longer needed after the HTMLImageElement.decode() barrier fix).
- HelloCodenameOne.kt: drop CN1JS: lifecycle prints; keep the
  HTML5 CN.callSerially path with an explanatory comment.
- Refresh the 34 baseline PNGs in scripts/javascript/screenshots/
  from a clean verification run (49/49 tests, CN1SS:SUITE:FINISHED).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The try/catch wrapper with CN1JS: exception logging was investigation
scaffolding added during the screenshot-pipeline debugging. It diverges
from iOS/Android (which call drawContent directly) and would silently
swallow real draw failures.

Calls drawContent(g, bounds) directly and removes the helper, matching
the master version. Full suite still passes (49/49, all 34 screenshots
equal).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The README still said "baselines have not been checked in yet" and that
the browser harness was in progress. Both are now done — 34 PNGs live
here and the full Playwright pipeline is wired up. Replace with an
accurate description of what the baselines represent (platform-specific
JS rasterisation through iOS7Theme.res at 750×1334) and how to
regenerate them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The JavaScript screenshot workflow posts a PR comment with the diff
summary but does not exit non-zero when baselines don't match, which
means a regression shows up as a green check.

Now that curated baselines live in scripts/javascript/screenshots/ and
the suite runs deterministically (Chromium, 375x667, deviceScaleFactor
2), flip the same gating switch iOS and Android already use
(scripts-android.yml:120). Mismatches and comparison errors will now
fail the check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI is failing because commit 82057c7 re-generated baselines from a
local macOS run, but the screenshot workflow runs on Ubuntu — Chromium
font rasterisation / AA differs between the two platforms, so every
single baseline diffed (all 34 marked 'different') once
CN1SS_FAIL_ON_MISMATCH flipped on.

Restore the 34 PNGs from the javascript-ui-tests artifact of CI run
24778933448 (the failing run against this same code). 33 of the 34 are
byte-identical to the pre-82057c71 baselines (i.e. master's original
Ubuntu-sourced PNGs); Sheet.png is legitimately new because the
SheetScreenshotTest revert in 82057c7 moved the title icon back to
setTitleComponent.

Baselines must only be regenerated from CI artifacts going forward —
don't refresh them from local macOS runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog shai-almog marked this pull request as ready for review April 22, 2026 13:43
@shai-almog shai-almog merged commit 0f95dcd into master Apr 22, 2026
27 of 30 checks passed
liannacasper pushed a commit that referenced this pull request Apr 22, 2026
Rebased onto master to pull in PR #4677 (Initial work on the new
JavaScript port). Follow-up to make the JS port consume the new
native-themes output:

- HTML5Implementation.installNativeTheme(): new default-theme
  resolution parallel to the iOS/Android ports. Android defaults to
  /AndroidMaterialTheme.res (hololight / legacy reachable via
  cn1.androidTheme); iOS defaults to /iOSModernTheme.res (ios7 / legacy
  reachable via ios.themeMode). javascript.native.theme still wins if
  set. If the modern .res is missing (partial build) the loader falls
  back to the legacy theme so the app still boots.

- scripts/build-native-themes.sh now mirrors the generated
  iOSModernTheme.res and AndroidMaterialTheme.res into
  Ports/JavaScriptPort/src/main/webapp/assets/ alongside the existing
  legacy .res files. .gitignore in that directory treats the mirrors
  as build artifacts (sources in native-themes/ stay authoritative).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants