diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintSchemaDefaults.java b/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintSchemaDefaults.java index a749a31e14..e4f37534ec 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintSchemaDefaults.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintSchemaDefaults.java @@ -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"); diff --git a/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Implementation.java b/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Implementation.java index 7238e67376..2c467d6656 100644 --- a/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Implementation.java +++ b/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Implementation.java @@ -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.). @@ -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. + * + *

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; diff --git a/Ports/JavaScriptPort/src/main/webapp/port.js b/Ports/JavaScriptPort/src/main/webapp/port.js index c92c98a82b..55df041acd 100644 --- a/Ports/JavaScriptPort/src/main/webapp/port.js +++ b/Ports/JavaScriptPort/src/main/webapp/port.js @@ -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 @@ -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 / diff --git a/maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java b/maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java index c105f365be..d52142e5a7 100644 --- a/maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java +++ b/maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java @@ -6218,6 +6218,23 @@ public com.codename1.ui.plaf.Border getThemeBorder(Map 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); } diff --git a/scripts/build-javascript-port-hellocodenameone.sh b/scripts/build-javascript-port-hellocodenameone.sh index 8869234b55..3e82593f33 100755 --- a/scripts/build-javascript-port-hellocodenameone.sh +++ b/scripts/build-javascript-port-hellocodenameone.sh @@ -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" diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java index 9fc609a146..e1da43daf0 100644 --- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java +++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java @@ -55,27 +55,35 @@ public final class Cn1ssDeviceRunner extends DeviceRunner { - // Per-test deadline cap. On the JavaScript port the overall CI browser - // lifetime is only ~150s, so a stuck test mustn't eat the whole budget; - // the tight 10s cap kept the suite from being blocked by the rare hung - // form (e.g. KotlinUiTest's missing cn1lib). iOS / Android / JavaSE - // run with much longer wall budgets so a per-test cap of 30s is safe - - // it only matters for genuinely stuck tests, and 30s leaves headroom - // for theme captures whose chunked-log emission is rate-limited (Android - // throttles 500-byte chunks at 30ms each to keep logcat from dropping - // lines, so a 60KB PNG plus its preview takes ~6s per appearance, and - // a dual-appearance test like SpanLabelTheme legitimately needs ~12s - // even on a healthy device). The 30s figure also covers the iOS Metal - // port's mutable rendering, which allocates a temp UIImage + MTLTexture - // per round-rect / arc / gradient call (the CG-rasterise-then-DrawImage - // bridge), so FillRoundRect / DrawRoundRect and DrawImage on slow CI - // simulators legitimately blow past the 10s budget while still making - // forward progress. + // Per-test deadline cap. The 10s HTML5 default keeps already- + // hanging tests (LightweightPickerButtons, ToastBarTopPosition + // hitting the canvasContextWipe noCanvas path) from eating the + // 1740s suite-level budget. DualAppearanceBaseTest subclasses + // override this via {@link BaseTest#getTimeoutMs()} because they + // run light + dark phases serially and each phase pays + // registerReadyCallback's 1500ms UITimer + wait_for_ui_settle + // (up to ~800ms) + capture/encode + chunked emit, so the + // bytecode-translated path easily clears 10s on shared GHA + // runners. At 10s the dark phase's emit fired AFTER the test had + // already timed out and the runner had advanced to the next test + // - the late dark emit then captured whatever form happened to + // be on the canvas at that moment (visible symptom: + // ToolbarTheme_dark.png showed "TabsTheme / light"). iOS / + // Android / JavaSE keep their wider 30s cap because they don't + // share the JS port's canvas-hang failure mode. private static final int TEST_TIMEOUT_MS_HTML5 = 10000; private static final int TEST_TIMEOUT_MS_NATIVE = 30000; private static final int TEST_POLL_INTERVAL_MS = 50; - private static int testTimeoutMs() { + private static int testTimeoutMs(BaseTest testClass) { + // DualAppearanceBaseTest needs more wall time on HTML5 (light + dark + // phases serially, each paying registerReadyCallback's 1500ms + settle + // + capture). Other tests stay at the tighter HTML5 default so a hung + // test doesn't eat the suite-level budget. + if ("HTML5".equals(Display.getInstance().getPlatformName()) + && testClass instanceof DualAppearanceBaseTest) { + return TEST_TIMEOUT_MS_NATIVE; + } return "HTML5".equals(Display.getInstance().getPlatformName()) ? TEST_TIMEOUT_MS_HTML5 : TEST_TIMEOUT_MS_NATIVE; @@ -289,7 +297,7 @@ private void runNextTest(int index) { logThrowable("runTest:" + testName, t); testClass.fail(String.valueOf(t)); } - awaitTestCompletion(index, testClass, testName, System.currentTimeMillis() + testTimeoutMs()); + awaitTestCompletion(index, testClass, testName, System.currentTimeMillis() + testTimeoutMs(testClass)); }); } @@ -306,7 +314,6 @@ private boolean shouldForceTimeoutInHtml5(String testName) { // the 300s end-marker deadline. Keep all skip lookups inline to avoid // triggering the same static-init failure path. return isJsSkippedNativeTest(testName) - || isJsSkippedThemeTest(testName) || isJsSkippedAnimationTest(testName) || isJsSkippedScreenshotTest(testName); } @@ -327,31 +334,6 @@ private static boolean isJsSkippedNativeTest(String testName) { || "CryptoApiTest".equals(testName); } - private static boolean isJsSkippedThemeTest(String testName) { - // The native-theme fidelity tests (each emits a light+dark PNG pair) - // matter for iOS/Android/JavaSE where the user actually looks at - // visual output. The JS port run has a tight 150s browser-lifetime - // budget that doesn't accommodate another 13 x 2 captures; skip them - // here. Re-enable selectively when we move the JS port to a - // longer-lived harness. - return "ButtonThemeScreenshotTest".equals(testName) - || "TextFieldThemeScreenshotTest".equals(testName) - || "CheckBoxRadioThemeScreenshotTest".equals(testName) - || "SwitchThemeScreenshotTest".equals(testName) - || "PickerThemeScreenshotTest".equals(testName) - || "ToolbarThemeScreenshotTest".equals(testName) - || "TabsThemeScreenshotTest".equals(testName) - || "MultiButtonThemeScreenshotTest".equals(testName) - || "ListThemeScreenshotTest".equals(testName) - || "DialogThemeScreenshotTest".equals(testName) - || "FloatingActionButtonThemeScreenshotTest".equals(testName) - || "SpanLabelThemeScreenshotTest".equals(testName) - || "DarkLightShowcaseThemeScreenshotTest".equals(testName) - || "PaletteOverrideThemeScreenshotTest".equals(testName) - || "CssGradientsScreenshotTest".equals(testName) - || "CssFilterBlurScreenshotTest".equals(testName); - } - private static boolean isJsSkippedAnimationTest(String testName) { // Animation grid tests render six full-form frames each. They exceed // the JS port's 150s browser-lifetime budget and the value is already diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DualAppearanceBaseTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DualAppearanceBaseTest.java index a37fdee6fa..3cd8bebf1b 100644 --- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DualAppearanceBaseTest.java +++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DualAppearanceBaseTest.java @@ -101,6 +101,23 @@ protected final void annotateComponent(Component c, String legend) { annotations.add(new Annotation(c, legend)); } + /// Gates {@link #done()} until {@link #finish()} runs. The JS-port + /// emit fallback (`cn1ssEmitCurrentFormScreenshotDom` in port.js) + /// force-calls done() on the active test after the per-emit + /// completion runnable returns. For a single-phase test that's + /// the safety net that finalises the test; for DualAppearance + /// the light-phase completion only async-kicks-off the dark + /// phase (form.show() returns before paint), so the force-done + /// would finalise the test before the dark capture fires - the + /// runner then advances to the next test, and the late dark + /// emit captures whatever form happens to be on the canvas at + /// that moment (visible symptom: ListTheme_dark.png showed + /// "DialogTheme / light"; PickerTheme_dark.png showed Toolbar; + /// 7 of 16 modern-theme tests produced no captures at all + /// because they sat in the gap behind the polluting late emit). + /// finish() flips this gate so the natural done() chain works. + private volatile boolean bothPhasesComplete; + @Override public boolean runTest() { installModernThemeIfRequested(); @@ -108,6 +125,18 @@ public boolean runTest() { return true; } + @Override + protected synchronized void done() { + if (!bothPhasesComplete) { + // Premature done() call (e.g. JS-port force-done after the + // light emit's completion runnable returned). Stay + // not-done so the test runner keeps polling until the + // dark phase finishes and finish() flips the gate below. + return; + } + super.done(); + } + private void runAppearance(boolean dark, final String suffix, final Runnable next) { Display.getInstance().setDarkMode(dark); // UIManager caches resolved Style objects per UIID; without this call @@ -240,6 +269,9 @@ private void finish() { UIManager.initFirstTheme("/theme"); } UIManager.getInstance().refreshTheme(); + // Lift the gate before calling done() so the overridden + // done() above lets the call through this time. + bothPhasesComplete = true; done(); } @@ -308,6 +340,16 @@ private String pickModernThemeResource() { if ("and".equals(platform)) { return "/AndroidMaterialTheme.res"; } + // The JavaScript port reports its platform as "HTML5"; it + // publishes ``cn1.modernThemeResource`` from HTML5Implementation's + // installNativeTheme() based on the detected browser OS + // (iOS/Mac -> iOSModernTheme.res, anything else -> + // AndroidMaterialTheme.res), which is the same selection + // logic this test would otherwise have to duplicate. + String published = Display.getInstance().getProperty("cn1.modernThemeResource", null); + if (published != null && published.length() > 0) { + return published; + } return null; } diff --git a/scripts/javascript/screenshots/ButtonTheme_dark.png b/scripts/javascript/screenshots/ButtonTheme_dark.png new file mode 100644 index 0000000000..d904d10cce Binary files /dev/null and b/scripts/javascript/screenshots/ButtonTheme_dark.png differ diff --git a/scripts/javascript/screenshots/ButtonTheme_light.png b/scripts/javascript/screenshots/ButtonTheme_light.png new file mode 100644 index 0000000000..42c8c49e9e Binary files /dev/null and b/scripts/javascript/screenshots/ButtonTheme_light.png differ diff --git a/scripts/javascript/screenshots/CheckBoxRadioTheme_dark.png b/scripts/javascript/screenshots/CheckBoxRadioTheme_dark.png new file mode 100644 index 0000000000..b8803b9c90 Binary files /dev/null and b/scripts/javascript/screenshots/CheckBoxRadioTheme_dark.png differ diff --git a/scripts/javascript/screenshots/CheckBoxRadioTheme_light.png b/scripts/javascript/screenshots/CheckBoxRadioTheme_light.png new file mode 100644 index 0000000000..a813bdd446 Binary files /dev/null and b/scripts/javascript/screenshots/CheckBoxRadioTheme_light.png differ diff --git a/scripts/javascript/screenshots/DialogTheme_dark.png b/scripts/javascript/screenshots/DialogTheme_dark.png new file mode 100644 index 0000000000..0ff95d7827 Binary files /dev/null and b/scripts/javascript/screenshots/DialogTheme_dark.png differ diff --git a/scripts/javascript/screenshots/DialogTheme_light.png b/scripts/javascript/screenshots/DialogTheme_light.png new file mode 100644 index 0000000000..85909f6c1b Binary files /dev/null and b/scripts/javascript/screenshots/DialogTheme_light.png differ diff --git a/scripts/javascript/screenshots/FloatingActionButtonTheme_dark.png b/scripts/javascript/screenshots/FloatingActionButtonTheme_dark.png new file mode 100644 index 0000000000..56882b0872 Binary files /dev/null and b/scripts/javascript/screenshots/FloatingActionButtonTheme_dark.png differ diff --git a/scripts/javascript/screenshots/FloatingActionButtonTheme_light.png b/scripts/javascript/screenshots/FloatingActionButtonTheme_light.png new file mode 100644 index 0000000000..5c0eb2d1d8 Binary files /dev/null and b/scripts/javascript/screenshots/FloatingActionButtonTheme_light.png differ diff --git a/scripts/javascript/screenshots/ListTheme_dark.png b/scripts/javascript/screenshots/ListTheme_dark.png new file mode 100644 index 0000000000..8e53ff1698 Binary files /dev/null and b/scripts/javascript/screenshots/ListTheme_dark.png differ diff --git a/scripts/javascript/screenshots/ListTheme_light.png b/scripts/javascript/screenshots/ListTheme_light.png new file mode 100644 index 0000000000..39c0ddbcf3 Binary files /dev/null and b/scripts/javascript/screenshots/ListTheme_light.png differ diff --git a/scripts/javascript/screenshots/MultiButtonTheme_dark.png b/scripts/javascript/screenshots/MultiButtonTheme_dark.png new file mode 100644 index 0000000000..0d93d13c9f Binary files /dev/null and b/scripts/javascript/screenshots/MultiButtonTheme_dark.png differ diff --git a/scripts/javascript/screenshots/MultiButtonTheme_light.png b/scripts/javascript/screenshots/MultiButtonTheme_light.png new file mode 100644 index 0000000000..6d6b8ff350 Binary files /dev/null and b/scripts/javascript/screenshots/MultiButtonTheme_light.png differ diff --git a/scripts/javascript/screenshots/PaletteOverrideTheme_dark.png b/scripts/javascript/screenshots/PaletteOverrideTheme_dark.png new file mode 100644 index 0000000000..6218b8f157 Binary files /dev/null and b/scripts/javascript/screenshots/PaletteOverrideTheme_dark.png differ diff --git a/scripts/javascript/screenshots/PaletteOverrideTheme_light.png b/scripts/javascript/screenshots/PaletteOverrideTheme_light.png new file mode 100644 index 0000000000..56882b0872 Binary files /dev/null and b/scripts/javascript/screenshots/PaletteOverrideTheme_light.png differ diff --git a/scripts/javascript/screenshots/PickerTheme_dark.png b/scripts/javascript/screenshots/PickerTheme_dark.png new file mode 100644 index 0000000000..7f93bcf793 Binary files /dev/null and b/scripts/javascript/screenshots/PickerTheme_dark.png differ diff --git a/scripts/javascript/screenshots/PickerTheme_light.png b/scripts/javascript/screenshots/PickerTheme_light.png new file mode 100644 index 0000000000..68fb97146f Binary files /dev/null and b/scripts/javascript/screenshots/PickerTheme_light.png differ diff --git a/scripts/javascript/screenshots/ShowcaseTheme_dark.png b/scripts/javascript/screenshots/ShowcaseTheme_dark.png new file mode 100644 index 0000000000..181ffbf0d0 Binary files /dev/null and b/scripts/javascript/screenshots/ShowcaseTheme_dark.png differ diff --git a/scripts/javascript/screenshots/ShowcaseTheme_light.png b/scripts/javascript/screenshots/ShowcaseTheme_light.png new file mode 100644 index 0000000000..0ff95d7827 Binary files /dev/null and b/scripts/javascript/screenshots/ShowcaseTheme_light.png differ diff --git a/scripts/javascript/screenshots/SpanLabelTheme_dark.png b/scripts/javascript/screenshots/SpanLabelTheme_dark.png new file mode 100644 index 0000000000..aff6e3e22a Binary files /dev/null and b/scripts/javascript/screenshots/SpanLabelTheme_dark.png differ diff --git a/scripts/javascript/screenshots/SpanLabelTheme_light.png b/scripts/javascript/screenshots/SpanLabelTheme_light.png new file mode 100644 index 0000000000..4107b253db Binary files /dev/null and b/scripts/javascript/screenshots/SpanLabelTheme_light.png differ diff --git a/scripts/javascript/screenshots/SwitchTheme_dark.png b/scripts/javascript/screenshots/SwitchTheme_dark.png new file mode 100644 index 0000000000..9db37343b8 Binary files /dev/null and b/scripts/javascript/screenshots/SwitchTheme_dark.png differ diff --git a/scripts/javascript/screenshots/SwitchTheme_light.png b/scripts/javascript/screenshots/SwitchTheme_light.png new file mode 100644 index 0000000000..cde93e2d1c Binary files /dev/null and b/scripts/javascript/screenshots/SwitchTheme_light.png differ diff --git a/scripts/javascript/screenshots/TabsTheme_dark.png b/scripts/javascript/screenshots/TabsTheme_dark.png new file mode 100644 index 0000000000..93b7fb18b6 Binary files /dev/null and b/scripts/javascript/screenshots/TabsTheme_dark.png differ diff --git a/scripts/javascript/screenshots/TabsTheme_light.png b/scripts/javascript/screenshots/TabsTheme_light.png new file mode 100644 index 0000000000..620d731887 Binary files /dev/null and b/scripts/javascript/screenshots/TabsTheme_light.png differ diff --git a/scripts/javascript/screenshots/TextFieldTheme_dark.png b/scripts/javascript/screenshots/TextFieldTheme_dark.png new file mode 100644 index 0000000000..8f63689ab3 Binary files /dev/null and b/scripts/javascript/screenshots/TextFieldTheme_dark.png differ diff --git a/scripts/javascript/screenshots/TextFieldTheme_light.png b/scripts/javascript/screenshots/TextFieldTheme_light.png new file mode 100644 index 0000000000..09b3be4eeb Binary files /dev/null and b/scripts/javascript/screenshots/TextFieldTheme_light.png differ diff --git a/scripts/javascript/screenshots/ToolbarTheme_dark.png b/scripts/javascript/screenshots/ToolbarTheme_dark.png new file mode 100644 index 0000000000..5c7ccc02de Binary files /dev/null and b/scripts/javascript/screenshots/ToolbarTheme_dark.png differ diff --git a/scripts/javascript/screenshots/ToolbarTheme_light.png b/scripts/javascript/screenshots/ToolbarTheme_light.png new file mode 100644 index 0000000000..c42d5a64b5 Binary files /dev/null and b/scripts/javascript/screenshots/ToolbarTheme_light.png differ