Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
3417f7d
Enable modern native themes on the JavaScript port
shai-almog May 27, 2026
177d941
Bump JS-port per-test timeout 10s -> 30s for DualAppearance
shai-almog May 28, 2026
424c4dc
[diag] RoundRectBorder: log Dialog rendering conditions on JS port
shai-almog May 28, 2026
c53baa3
[diag] Fix PMD UnnecessaryFullyQualifiedName lint
shai-almog May 28, 2026
d14ace2
[diag] Component: log Dialog/DialogBody paintBackgroundImpl entry on …
shai-almog May 28, 2026
cf47542
[diag] Cn1ssDeviceRunner: restore 10s default, give DualAppearance 30s
shai-almog May 28, 2026
901eef4
CSS compiler: prefer RoundRectBorder over CSSBorder for plain border-…
shai-almog May 28, 2026
aae45cf
Remove DLG-DIAG / RRB-DIAG runtime logging now that root cause is ide…
shai-almog May 28, 2026
132b92d
RoundRectBorder: force cached-image path on HTML5 JS port
shai-almog May 28, 2026
893cee1
RoundRectBorder.createTargetImage: also force setClip+bgPainter on JS…
shai-almog May 28, 2026
4f43adc
Revert "RoundRectBorder.createTargetImage: also force setClip+bgPaint…
shai-almog May 28, 2026
f2e9c47
Revert "RoundRectBorder: force cached-image path on HTML5 JS port"
shai-almog May 28, 2026
c6e026a
Add JS-port modern-theme goldens (25 PNGs)
shai-almog May 28, 2026
6d13059
Drop 8 flaky JS-port modern-theme goldens
shai-almog May 28, 2026
5b1b05f
Add latest CI captures as JS-port modern-theme goldens (11 PNGs)
shai-almog May 28, 2026
b1ed44a
Revert "Add latest CI captures as JS-port modern-theme goldens (11 PN…
shai-almog May 28, 2026
f36a5bc
Add remaining 11 JS-port modern-theme goldens
shai-almog May 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,12 @@ static void register() {
// Group.
set("{{@nativeTheme}}.label", "Native Theme");
set("{{@nativeTheme}}.description",
"Controls the Codename One look & feel on iOS and Android. "
+ "Modern themes are generated from CSS under native-themes/; "
+ "legacy themes remain selectable via the values below.");
"Controls the Codename One look & feel on iOS, Android, and "
+ "the JavaScript port (browser OS auto-detection: iOS/Mac "
+ "browsers get the iOS theme, everything else gets the "
+ "Android theme). Modern themes are generated from CSS "
+ "under native-themes/; legacy themes remain selectable "
+ "via the values below.");

// Cross-platform meta hint.
set("{{#nativeTheme#nativeTheme}}.label", "Shared override");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2816,60 +2816,27 @@ public void setHeight(HTMLCanvasElement canvas, int canvasHeight) {
@Override
public void installNativeTheme(){
try {
// Prefer the modern native theme when explicitly requested via
// ios.themeMode / and.themeMode (legacy alias: cn1.androidTheme)
// / nativeTheme (legacy alias: cn1.nativeTheme) /
// javascript.native.theme. If no hint is set we keep the
// pre-existing JS-port default (iOS 7 / Holo Light) since the JS
// bundle may not include the modern .res files
// (scripts/build-native-themes.sh has to have mirrored them
// before the JS bundle was produced).
String defaultTheme = isAndroid_() ? "/android_holo_light.res" : "/iOS7Theme.res";
Display d = Display.getInstance();
String iosMode = d.getProperty("ios.themeMode", null);
String androidMode = d.getProperty("and.themeMode",
d.getProperty("cn1.androidTheme", null));
String shared = d.getProperty("nativeTheme",
d.getProperty("cn1.nativeTheme", null));
if (isAndroid_()) {
if (androidMode == null && shared != null) {
if ("modern".equalsIgnoreCase(shared)) {
androidMode = "material";
} else if ("legacy".equalsIgnoreCase(shared)) {
androidMode = "hololight";
}
}
if (androidMode != null) {
androidMode = androidMode.toLowerCase();
if ("material".equals(androidMode) || "modern".equals(androidMode) || "auto".equals(androidMode)) {
defaultTheme = "/AndroidMaterialTheme.res";
} else if ("legacy".equals(androidMode)) {
defaultTheme = "/androidTheme.res";
} else if ("hololight".equals(androidMode) || "holo".equals(androidMode)) {
defaultTheme = "/android_holo_light.res";
}
}
} else {
if (iosMode == null && shared != null) {
if ("modern".equalsIgnoreCase(shared)) {
iosMode = "modern";
} else if ("legacy".equalsIgnoreCase(shared)) {
iosMode = "ios7";
}
}
if (iosMode != null) {
iosMode = iosMode.toLowerCase();
if ("modern".equals(iosMode) || "liquid".equals(iosMode) || "auto".equals(iosMode)) {
defaultTheme = "/iOSModernTheme.res";
} else if ("legacy".equals(iosMode) || "iphone".equals(iosMode)) {
defaultTheme = "/iPhoneTheme.res";
}
}
}
String nativeTheme = Display.getInstance().getProperty("javascript.native.theme", defaultTheme);
// Pick the .res to load based on the build hints and the
// detected browser OS. The JS-port default stays on the
// pre-existing legacy theme (Holo Light when the user agent
// is Android, iOS 7 on everything else) so legacy
// screenshot baselines remain comparable. Apps that want
// the modern Liquid Glass / Material 3 theme can opt in
// via ios.themeMode / and.themeMode / nativeTheme (legacy
// aliases: cn1.androidTheme / cn1.nativeTheme) /
// javascript.native.theme, including the new "auto" value
// that picks iOS modern for iOS/Mac browsers and Material
// 3 elsewhere.
String resolved = resolveNativeThemeResource();
// Publish the detected modern-theme resource so screenshot
// tests (DualAppearanceBaseTest) can install the same
// platform-appropriate theme on the JS port without
// duplicating the OS-detection logic.
String modern = isIOSLikeBrowser() ? "/iOSModernTheme.res" : "/AndroidMaterialTheme.res";
Display.getInstance().setProperty("cn1.modernThemeResource", modern);
Resources r;
try {
r = Resources.open(nativeTheme);
r = Resources.open(resolved);
} catch (Throwable notFound) {
// Fall back to the legacy theme if the chosen .res isn't in
// the JS bundle (partial build, missing mirror step, etc.).
Expand All @@ -2890,6 +2857,104 @@ public void installNativeTheme(){
return;
}

/**
* iOS/Mac browsers should pick up the iOS Liquid Glass theme;
* every other browser (Android, Windows, Linux, Chrome OS) gets
* the Android Material 3 theme. The legacy default split was
* Android vs. "everything else falls back to iOS"; for the modern
* native theme the more useful split is iOS-family vs. everyone
* else because Windows and Linux desktops match Material 3 better
* than Liquid Glass.
*/
private static boolean isIOSLikeBrowser() {
return isIOS() || isMac();
}

/**
* Resolves the native-theme .res path from build hints + detected
* browser OS. Recognised hints (highest to lowest precedence):
* - {@code javascript.native.theme}: explicit res path override.
* - {@code ios.themeMode}: auto/modern/liquid/ios7/flat/legacy/
* iphone. Applied to iOS/Mac browsers only.
* - {@code and.themeMode}: auto/material/modern/hololight/holo/
* legacy. Applied when the browser is not iOS/Mac. Alias:
* {@code cn1.androidTheme}.
* - {@code nativeTheme}: modern/auto/legacy. Maps onto the iOS
* or Android branch based on the detected browser OS. Alias:
* {@code cn1.nativeTheme}. The new {@code auto} value triggers
* OS-based selection: iOS/Mac browsers get the Liquid Glass
* theme, everything else gets Material 3.
*
* <p>With no hint set, the JS port keeps the pre-existing default
* (Android Holo Light when the user agent is Android, iOS 7
* everywhere else - identical to the historical legacy split) so
* existing JS screenshot baselines remain comparable. Apps that
* want the modern Liquid Glass / Material 3 theme can opt in by
* setting {@code nativeTheme=auto} or {@code nativeTheme=modern}
* (or the platform-specific {@code ios.themeMode} /
* {@code and.themeMode}). The {@code cn1.modernThemeResource}
* runtime property published by {@link #installNativeTheme()}
* always points at the OS-appropriate modern .res so screenshot
* tests can install it regardless of the configured default.
*/
private static String resolveNativeThemeResource() {
Display d = Display.getInstance();
String explicit = d.getProperty("javascript.native.theme", null);
if (explicit != null && explicit.length() > 0) {
return explicit;
}
String shared = d.getProperty("nativeTheme", d.getProperty("cn1.nativeTheme", null));
// Auto-detected branch chooses Liquid Glass on iOS/Mac and
// Material 3 elsewhere. Used for any "modern"/"auto" path.
boolean iosLike = isIOSLikeBrowser();
if (iosLike) {
String iosMode = d.getProperty("ios.themeMode", null);
if (iosMode == null && shared != null) {
if ("modern".equalsIgnoreCase(shared) || "auto".equalsIgnoreCase(shared)) {
iosMode = "modern";
} else if ("legacy".equalsIgnoreCase(shared)) {
iosMode = "ios7";
}
}
if (iosMode != null) {
iosMode = iosMode.toLowerCase();
if ("legacy".equals(iosMode) || "iphone".equals(iosMode)) {
return "/iPhoneTheme.res";
}
if ("ios7".equals(iosMode) || "flat".equals(iosMode)) {
return "/iOS7Theme.res";
}
// modern / liquid / auto / anything else -> modern theme
return "/iOSModernTheme.res";
}
// No iOS hint - keep the pre-existing JS-port default.
return "/iOS7Theme.res";
}
String androidMode = d.getProperty("and.themeMode", d.getProperty("cn1.androidTheme", null));
if (androidMode == null && shared != null) {
if ("modern".equalsIgnoreCase(shared) || "auto".equalsIgnoreCase(shared)) {
androidMode = "material";
} else if ("legacy".equalsIgnoreCase(shared)) {
androidMode = "hololight";
}
}
if (androidMode != null) {
androidMode = androidMode.toLowerCase();
if ("legacy".equals(androidMode)) {
return "/androidTheme.res";
}
if ("hololight".equals(androidMode) || "holo".equals(androidMode)) {
return "/android_holo_light.res";
}
// material / modern / auto / anything else -> modern theme
return "/AndroidMaterialTheme.res";
}
// No Android hint - keep the pre-existing JS-port default,
// which routes Android user agents to Holo Light and every
// other non-iOS browser (Linux/Windows/Chrome OS) to iOS 7.
return isAndroid_() ? "/android_holo_light.res" : "/iOS7Theme.res";
}

@Override
public boolean hasNativeTheme() {
return true;
Expand Down
41 changes: 13 additions & 28 deletions Ports/JavaScriptPort/src/main/webapp/port.js
Original file line number Diff line number Diff line change
Expand Up @@ -3214,20 +3214,15 @@ const cn1ssForcedTimeoutTestClasses = Object.freeze({
// emitChannel hijack — see matching entry in cn1ssForcedTimeoutTestNames below.
"com_codenameone_examples_hellocodenameone_tests_ChatInputScreenshotTest": "chatInputEmitHijack",
"com_codenameone_examples_hellocodenameone_tests_ChatViewScreenshotTest": "chatViewEmitHijack",
"com_codenameone_examples_hellocodenameone_tests_ButtonThemeScreenshotTest": "themeScreenshot",
"com_codenameone_examples_hellocodenameone_tests_TextFieldThemeScreenshotTest": "themeScreenshot",
"com_codenameone_examples_hellocodenameone_tests_CheckBoxRadioThemeScreenshotTest": "themeScreenshot",
"com_codenameone_examples_hellocodenameone_tests_SwitchThemeScreenshotTest": "themeScreenshot",
"com_codenameone_examples_hellocodenameone_tests_PickerThemeScreenshotTest": "themeScreenshot",
"com_codenameone_examples_hellocodenameone_tests_ToolbarThemeScreenshotTest": "themeScreenshot",
"com_codenameone_examples_hellocodenameone_tests_TabsThemeScreenshotTest": "themeScreenshot",
"com_codenameone_examples_hellocodenameone_tests_MultiButtonThemeScreenshotTest": "themeScreenshot",
"com_codenameone_examples_hellocodenameone_tests_ListThemeScreenshotTest": "themeScreenshot",
"com_codenameone_examples_hellocodenameone_tests_DialogThemeScreenshotTest": "themeScreenshot",
"com_codenameone_examples_hellocodenameone_tests_FloatingActionButtonThemeScreenshotTest": "themeScreenshot",
"com_codenameone_examples_hellocodenameone_tests_SpanLabelThemeScreenshotTest": "themeScreenshot",
"com_codenameone_examples_hellocodenameone_tests_DarkLightShowcaseThemeScreenshotTest": "themeScreenshot",
"com_codenameone_examples_hellocodenameone_tests_PaletteOverrideThemeScreenshotTest": "themeScreenshot",
// The 14 *ThemeScreenshotTest entries that used to live here were
// unparked when the JS port started bundling the modern native
// theme resources (iOSModernTheme.res / AndroidMaterialTheme.res
// mirrored into webapp/assets/ by scripts/build-native-themes.sh).
// DualAppearanceBaseTest now installs the OS-appropriate modern
// theme via the cn1.modernThemeResource Display property that
// HTML5Implementation publishes during installNativeTheme(); see
// the matching cn1ssForcedTimeoutTestNames map below for the
// short-name list that was un-parked in tandem.
// jsChunkDrop block removed for the graphics grid, KotlinUiTest,
// MainScreenScreenshotTest, the Tabs / ImageViewer / TextArea /
// ToastBar / picker tests, and ChartLine -- the chunk emitter bug
Expand Down Expand Up @@ -3348,20 +3343,10 @@ const cn1ssForcedTimeoutTestNames = Object.freeze({
// the JS-port emit path (separate investigation).
"ChatInputScreenshotTest": "chatInputEmitHijack",
"ChatViewScreenshotTest": "chatViewEmitHijack",
"ButtonThemeScreenshotTest": "themeScreenshot",
"TextFieldThemeScreenshotTest": "themeScreenshot",
"CheckBoxRadioThemeScreenshotTest": "themeScreenshot",
"SwitchThemeScreenshotTest": "themeScreenshot",
"PickerThemeScreenshotTest": "themeScreenshot",
"ToolbarThemeScreenshotTest": "themeScreenshot",
"TabsThemeScreenshotTest": "themeScreenshot",
"MultiButtonThemeScreenshotTest": "themeScreenshot",
"ListThemeScreenshotTest": "themeScreenshot",
"DialogThemeScreenshotTest": "themeScreenshot",
"FloatingActionButtonThemeScreenshotTest": "themeScreenshot",
"SpanLabelThemeScreenshotTest": "themeScreenshot",
"DarkLightShowcaseThemeScreenshotTest": "themeScreenshot",
"PaletteOverrideThemeScreenshotTest": "themeScreenshot",
// The 14 *ThemeScreenshotTest short-name entries were un-parked
// alongside the fully-qualified-class entries in
// cn1ssForcedTimeoutTestClasses above when the modern native
// theme resources started shipping in the JS port bundle.
// See the matching comment in cn1ssForcedTimeoutTestClasses above:
// the jsChunkDrop short-name entries (KotlinUiTest,
// MainScreenScreenshotTest, the ImageViewer / Tabs / TextArea /
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6218,6 +6218,23 @@ public com.codename1.ui.plaf.Border getThemeBorder(Map<String,LexicalUnit> style
if (cn1BackgroundType != null && usesRoundBorder(styles)) {
return createRoundBorder(styles);
}
// Prefer RoundRectBorder for the simple border-radius case
// (no border-image, no compound per-side borders). The JS
// port's CSSBorder.paintBorderBackground calls
// g.fillShape(p) directly against the main canvas, which
// doesn't actually render on the cooperative-scheduler
// worker-side bridge (visible symptom: Dialog / TextField /
// ChatBubble UIIDs show no rounded background, only their
// children render). RoundRectBorder.paintBorderBackground
// routes through createTargetImage + drawImage on the same
// path that already works for cn1-pill-border (Button), so
// switching dispatch fixes Dialog without changing iOS /
// Android pixels (RoundRectBorder produces a visually-
// equivalent rounded rect there).
if (b.canBeAchievedWithRoundRectBorder(styles) && b.hasBorderRadius()
&& !b.hasBorderImage() && !b.hasUnequalBorders()) {
return createRoundRectBorder(styles);
}
if (b.canBeAchievedWithCSSBorder(styles) && (b.hasBorderImage() || b.hasUnequalBorders() || b.hasBorderRadius()) ) {
return createCSSBorder(styles);
}
Expand Down
16 changes: 16 additions & 0 deletions scripts/build-javascript-port-hellocodenameone.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,22 @@ if [ "${SKIP_MAVEN_BUILD:-0}" != "1" ] && [ "${SKIP_PARPARVM_BUILD:-0}" != "1" ]
mvn -B -f "$REPO_ROOT/maven/pom.xml" -pl parparvm -am -DskipTests -Dmaven.javadoc.skip=true package
fi

# Mirror the modern native themes (iOSModernTheme.res /
# AndroidMaterialTheme.res) into Ports/JavaScriptPort/src/main/webapp/
# assets so the bundle picks them up when JavascriptBundleWriter copies
# webapp/assets/* into the served output. The mirror files are
# gitignored build artefacts, so without this step a fresh checkout
# would silently fall back to iOS7Theme.res / android_holo_light.res
# at runtime.
if [ "${SKIP_NATIVE_THEMES_BUILD:-0}" != "1" ]; then
if [ -x "$REPO_ROOT/scripts/build-native-themes.sh" ]; then
bj_log "Compiling native themes (iOS Modern / Android Material) for JS bundle"
"$REPO_ROOT/scripts/build-native-themes.sh"
else
bj_log "WARNING: scripts/build-native-themes.sh missing - modern themes won't be in bundle"
fi
fi

if [ "${SKIP_MAVEN_BUILD:-0}" != "1" ] && [ "${SKIP_COMMON_BUILD:-0}" != "1" ]; then
bj_log "Building HelloCodenameOne common module and compile-scope dependencies"
mkdir -p "$HOME/.codenameone"
Expand Down
Loading
Loading