Skip to content

JavaScript port: wire the native themes from Themes/, drop unused demo fixtures#5466

Open
shai-almog wants to merge 5 commits into
masterfrom
fix/js-port-prune-demo-assets
Open

JavaScript port: wire the native themes from Themes/, drop unused demo fixtures#5466
shai-almog wants to merge 5 commits into
masterfrom
fix/js-port-prune-demo-assets

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Follow-up to #5465 / #5457.

JavaScriptPort.jar carries webapp/ so off-repo builds can find port.js (that was #5457), and the translator copies webapp/assets into every app it builds. The side effect is that the port test app's own fixtures ship inside every JavaScript app's public web root:

file size
leather.res 2.1 MB
chrome.res 778 KB
video.mp4 666 KB
Handlee-Regular.ttf 39 KB
Page.html, test.json ~1 KB

~3.6 MB, none of it referenced by any class in the port or in CodenameOne core. I checked each name with fixed-string searches across Ports/JavaScriptPort/src/main/java and CodenameOne/src — zero hits. (Handlee-Regular.ttf appears only inside a Javadoc example in Font.java.)

They are excluded from the jar and stay in the source tree, so in-repo builds and the javascript-screenshots suite still see them.

Kept

The system themes HTML5Implementation.getNativeTheme() actually resolves — iOS7Theme.res, iPhoneTheme.res, iOSModernTheme.res, android_holo_light.res, tzone_theme.res — plus CN1Resource.res and theme.res.

Also: force the jar to be recreated

Ant's <jar> treats an existing archive as up to date when it is newer than every input file. The include/exclude list lives in the POM, not in the inputs, so on an incremental build a change to it is silently ignored and a stale jar ships. This cost a full debugging cycle: the exclusions above appeared to do nothing until I noticed the inner jar was hours old. The task now deletes the jar first.

Verification

Rebuilt the bundle and inspected the jar directly: 0 demo fixtures, 4 system themes retained. Then rebuilt a real app (the BuildCloud console) against it — bundle 16 MB → 12 MB, and it still renders with icons, theme and material icon font intact, all APIs 200, browser console clean.

🤖 Generated with Claude Code

JavaScriptPort.jar carries webapp/ so off-repo builds can find port.js, and
the translator copies webapp/assets into every app it builds. That meant
~3.6MB of the port test app's own fixtures -- leather.res (2.1MB),
chrome.res (778KB), video.mp4 (666KB), Handlee-Regular.ttf, Page.html,
test.json -- landed in every JavaScript app's public web root. None of them
is referenced by any class in the port or in CodenameOne core.

Exclude them from the jar. They stay in the source tree, so in-repo builds
and the javascript-screenshots suite still see them. The system themes
HTML5Implementation.getNativeTheme() resolves (iOS7Theme.res,
iPhoneTheme.res, iOSModernTheme.res, android_holo_light.res,
tzone_theme.res) and CN1Resource.res are kept.

Also delete the jar before rebuilding it: Ant's jar task treats an existing
archive as up to date when it is newer than every input file, so on an
incremental build a change to this include/exclude list is silently ignored
and a stale jar ships. That cost a full debugging cycle here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 25, 2026 11:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces the default output size of JavaScript builds by preventing the JavaScript port’s demo/test fixture assets from being bundled into JavaScriptPort.jar, which the translator then propagates into every JavaScript app’s public web root. It also makes the jar packaging step robust for incremental builds by forcing the jar to be recreated when include/exclude rules change.

Changes:

  • Delete the previously-built JavaScriptPort.jar before running Ant’s <jar> task to avoid stale archives on incremental builds.
  • Exclude large demo/test fixture files (e.g., leather.res, chrome.res, video.mp4) from the webapp/assets content that gets embedded into JavaScriptPort.jar.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread maven/parparvm/pom.xml Outdated
Comment on lines +239 to +242
The system themes assets/ must keep are the ones
HTML5Implementation.getNativeTheme() resolves:
iOS7Theme.res, iPhoneTheme.res, iOSModernTheme.res,
android_holo_light.res and tzone_theme.res. -->
@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [HTML preview] [Download]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 1 findings (Normal: 1)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

assets/theme.res is the port test app's theme (1.4MB). Real apps ship their
own theme.res at the bundle root and getResourceAsStream() deliberately
prefers the root copy for theme.res / CN1Resource.res, so this one is never
read -- confirmed against a running app, whose request log shows the root
theme.res fetched and this one never touched. Shipping it also kept a
shadowing hazard alive for no benefit.

assets/tzone_theme.res is returned by no code path: getNativeTheme() only
ever yields iOS7Theme / iPhoneTheme / iOSModernTheme / android_holo_light /
AndroidMaterialTheme / androidTheme. The single mention of tzone_theme in
the port is inside a comment, which is exactly why it survived the first
pass.

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

maven/parparvm/pom.xml:242

  • The comment listing the “system themes assets/ must keep” includes tzone_theme.res, but the JS port’s theme selection doesn’t return that resource. In Ports/JavaScriptPort/.../HTML5Implementation.java, resolveNativeThemeResource() returns only iOS7/iPhone/iOSModern/AndroidMaterial/android_holo_light/androidTheme (see ~4092-4147), so this list is misleading and contradicts the later exclusion of assets/tzone_theme.res. Update the comment to reflect the actual set of themes that may be loaded.
                                             The system themes assets/ must keep are the ones
                                             HTML5Implementation.getNativeTheme() resolves:
                                             iOS7Theme.res, iPhoneTheme.res, iOSModernTheme.res,
                                             android_holo_light.res and tzone_theme.res. -->

maven/parparvm/pom.xml:261

  • This exclusion rationale refers to getNativeTheme(), but there is no getNativeTheme() in the JavaScript port; theme selection is performed by HTML5Implementation.resolveNativeThemeResource() (called from installNativeTheme()). Updating this comment will avoid confusion when someone tries to audit which themes must remain bundled.
                                        <!-- No code path returns tzone_theme.res: getNativeTheme()
                                             only ever yields iOS7Theme / iPhoneTheme /
                                             iOSModernTheme / android_holo_light /
                                             AndroidMaterialTheme / androidTheme. The one mention of
                                             tzone_theme in the port is inside a comment. -->

I had described iOS7Theme/iPhoneTheme/android_holo_light as "the themes
getNativeTheme() resolves" and listed iOSModernTheme among them. That
misread installNativeTheme(): the JS port defaults to the LEGACY pair
(android_holo_light on an Android user agent, iOS7Theme elsewhere) so
existing screenshot baselines stay comparable, and the modern themes are
opt-in via ios.themeMode / and.themeMode / nativeTheme /
javascript.native.theme. The modern .res files live in Themes/ and are
staged per target, not from webapp/assets.

No functional change: the same files are excluded and the same ones kept.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 25, 2026 11:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

maven/parparvm/pom.xml:243

  • PR description says the bundle keeps tzone_theme.res and theme.res, but this POM change explicitly excludes both (assets/theme.res and assets/tzone_theme.res). Either the description/kept list is outdated, or these excludes are too aggressive. Please align the PR description and/or the inline comment here so it’s clear which themes/resources are intentionally retained vs excluded (and why).
                                             Kept: the native themes installNativeTheme() can open
                                             from the bundle. The JS port defaults to the LEGACY
                                             pair (android_holo_light.res on an Android user agent,
                                             iOS7Theme.res elsewhere) so existing screenshot
                                             baselines stay comparable; iPhoneTheme.res is the

HTML5Implementation.resolveNativeThemeResource() can return six themes --
iOS7Theme, iPhoneTheme, iOSModernTheme, androidTheme, android_holo_light,
AndroidMaterialTheme -- but the bundle only carried three of them, so
setting ios.themeMode / and.themeMode / nativeTheme /
javascript.native.theme to anything modern (or to legacy on Android) hit
the catch in installNativeTheme() and silently fell back to the legacy
theme. The modern themes postdate the old TeaVM port; the ParparVM port
never grew the wiring the other targets have.

The build cannot pick one of the six: resolveNativeThemeResource() decides
at RUNTIME from the browser user agent, so "modern" means iOSModernTheme
in an iOS-like browser and AndroidMaterialTheme everywhere else. Ship the
whole set instead.

Take them from Themes/, the single source of truth kept in sync from the
CSS sources by native-themes-sync.yml, the same way maven/ios folds
iOSModernTheme.res into nativeios.jar and maven/javase copies the set onto
its classpath -- rather than from the port's own copies under
webapp/assets, which drift: the webapp's iPhoneTheme.res was already a
stale version of the Themes/ one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 25, 2026 12:11
@shai-almog shai-almog changed the title Keep the JavaScript port's demo fixtures out of every app's bundle JavaScript port: wire the native themes from Themes/, drop unused demo fixtures Jul 25, 2026
@shai-almog

Copy link
Copy Markdown
Collaborator Author

Scope grew after review feedback — this now fixes a real bug, not just bloat.

The modern native themes were never wired up on the JavaScript port. They postdate the old TeaVM port, and the ParparVM port never grew the staging the other targets have. HTML5Implementation.resolveNativeThemeResource() can return six themes, but the bundle shipped three, so setting ios.themeMode / and.themeMode / nativeTheme / javascript.native.theme to anything modern — or to legacy on Android, which needs androidTheme.res — hit the catch in installNativeTheme() and silently fell back to the legacy theme.

Note the build cannot stage just the one an app needs: resolveNativeThemeResource() decides at runtime from the browser user agent, so modern resolves to iOSModernTheme.res in an iOS-like browser and AndroidMaterialTheme.res everywhere else. The whole set has to be present.

They now come from Themes/ — the single source of truth synced by native-themes-sync.yml — mirroring maven/ios folding iOSModernTheme.res into nativeios.jar and maven/javase copying the set onto its classpath. That also fixes a second problem: the port's own copies under webapp/assets/ drift, and iPhoneTheme.res there was already stale relative to Themes/.

Net size: the six themes add ~780KB versus the three stale copies, and removing the demo fixtures takes ~3.6MB off, so a JavaScript bundle still ends up substantially smaller than before.

I also closed #5468, which I had filed claiming the modern path was broken by default. That was wrong — legacy is the intended default, to keep screenshot baselines comparable. The real defect is the one fixed here: the opt-in never worked.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

maven/parparvm/pom.xml:263

  • This change excludes assets/tzone_theme.res from the shipped JavaScriptPort.jar, but (a) the PR description says it is kept, and (b) apps can still explicitly request it via Display property javascript.native.theme. With this exclusion, that explicit selection will now fall back to the legacy theme at runtime.
                                        <!-- No theme-resolution path yields tzone_theme.res; the one
                                             mention of it in the port is inside a comment. -->
                                        <exclude name="assets/tzone_theme.res"/>

@shai-almog

shai-almog commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 8.37% (7322/87435 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.26% (38206/462263), branch 3.02% (1311/43428), complexity 3.29% (1572/47783), method 4.94% (1275/25820), class 10.04% (349/3475)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/551 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 8.37% (7322/87435 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.26% (38206/462263), branch 3.02% (1311/43428), complexity 3.29% (1572/47783), method 4.94% (1275/25820), class 10.04% (349/3475)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/551 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 276ms / native 132ms = 2.0x speedup
SIMD float-mul (64K x300) java 151ms / native 167ms = 0.9x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 94.000 ms
Base64 CN1 decode 83.000 ms
Base64 native encode 294.000 ms
Base64 encode ratio (CN1/native) 0.320x (68.0% faster)
Base64 native decode 264.000 ms
Base64 decode ratio (CN1/native) 0.314x (68.6% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog

shai-almog commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 73000 ms
Simulator Boot (Run) 1000 ms
App Install 18000 ms
App Launch 1000 ms
Test Execution 878000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 67ms / native 3ms = 22.3x speedup
SIMD float-mul (64K x300) java 76ms / native 3ms = 25.3x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 230.000 ms
Base64 CN1 decode 148.000 ms
Base64 native encode 607.000 ms
Base64 encode ratio (CN1/native) 0.379x (62.1% faster)
Base64 native decode 708.000 ms
Base64 decode ratio (CN1/native) 0.209x (79.1% faster)
Base64 SIMD encode 79.000 ms
Base64 encode ratio (SIMD/CN1) 0.343x (65.7% faster)
Base64 SIMD decode 52.000 ms
Base64 decode ratio (SIMD/CN1) 0.351x (64.9% faster)
Base64 encode ratio (SIMD/native) 0.130x (87.0% faster)
Base64 decode ratio (SIMD/native) 0.073x (92.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 10.000 ms
Image createMask (SIMD on) 1.000 ms
Image createMask ratio (SIMD on/off) 0.100x (90.0% faster)
Image applyMask (SIMD off) 58.000 ms
Image applyMask (SIMD on) 29.000 ms
Image applyMask ratio (SIMD on/off) 0.500x (50.0% faster)
Image modifyAlpha (SIMD off) 78.000 ms
Image modifyAlpha (SIMD on) 29.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.372x (62.8% faster)
Image modifyAlpha removeColor (SIMD off) 67.000 ms
Image modifyAlpha removeColor (SIMD on) 71.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.060x (6.0% slower)

The bundle is meant to be unpacked straight into a web root, but two things
made every consumer post-process it first.

Zip the dist FLAT. zipDirectory() was passed the dist directory's own name as
the zip root, so everything landed under "<MainClass>-js/" and had to be
flattened before it could be served. The old cloud/TeaVM bundle was flat, so
this also lets one deployment script handle both.

Stop copying build descriptors into the bundle. ByteCodeTranslator copies
every non-class input file into the output by basename, so a Maven-built app
leaks META-INF/maven/**/pom.xml -- dependency list and all -- plus
pom.properties and MANIFEST.MF. Harmless for the iOS target, where the output
is a source tree; for the JavaScript target that output IS a public document
root. None of them is an application resource.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 25, 2026 12:47
@shai-almog

Copy link
Copy Markdown
Collaborator Author

Two more build-side fixes, prompted by the fair question of why a consumer should have to post-process the bundle at all. The JavaScript build is supposed to hand back something deployable as-is, and it wasn't.

1. The zip is now flat. zipDirectory() was passed the dist directory's own name as the zip root, so everything landed under <MainClass>-js/ and every consumer had to flatten it before serving. The old cloud/TeaVM bundle was flat, so this also means one deployment script can handle both.

2. Build descriptors no longer leak into the bundle. ByteCodeTranslator copies every non-class input file into the output by basename, so a Maven-built app's META-INF/maven/**/pom.xml — dependency list and all — plus pom.properties and MANIFEST.MF ended up in the bundle root. That is harmless for the iOS target, where the output is a source tree, but for JavaScript the output is a public document root. None of them is an application resource.

Both were being worked around downstream; that workaround is now deleted.

Two things I deliberately did not change, since they look intentional and you'd know better:

  • vm_protocol.md and jso-bridge-dispatch-ids.txt are still emitted into the dist. JavascriptTargetIntegrationTest asserts the former ("Translator should emit the VM protocol contract artifact"), so it is a deliberate contract artifact; nothing reads the latter. They are developer artifacts on a public web root, so they arguably belong beside the dist rather than inside it — but that is test-encoded intent, not mine to flip.
  • The media stack (js/videojs/, js/samplerate.min.js, ~1.4MB) ships unconditionally. Only the Media/Capture APIs use it. The translator already computes reachability for the RTA pass, so it could include it only when those classes survive culling — that would drop another 1.4MB from most bundles. Happy to look at that separately if it's worth doing.

Verified end to end after both changes: the bundle unzips straight into a web root, the console renders, all APIs return 200, and a request log from the running app matches the shipped file list exactly.

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.

2 participants