Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -5349,6 +5349,31 @@ private JavaScriptRuntimeEnvironment createRuntimeEnvironment(final WindowExt wi
);
}

/// Remembers the application's light/dark choice for the loading screen of the NEXT launch:
/// `light` or `dark`, or null/anything else to follow the browser again. The page reads the
/// key in `<head>` before any Java runs (`JavascriptBundleWriter.splashTheme` in the
/// ByteCodeTranslator, which also names the key); a build whose `javascript.splash.appearance`
/// is `light` or `dark` ignores it.
private static void setSplashAppearance(String value) {
// The cast stays outside the try: a CHECKCAST is unchecked on ParparVM, so a handler
// around it would never run (scripts/check-cast-semantics.sh).
WindowExt win = (WindowExt) Window.current();
try {
JSOImplementations.JSStorage storage = win.getLocalStorage();
if (storage == null) {
return;
}
if ("light".equals(value) || "dark".equals(value)) {
storage.setItem("cn1.splashAppearance", value);
} else {
storage.removeItem("cn1.splashAppearance");
}
} catch (RuntimeException e) {
// Storage disabled (private mode, a sandboxed frame): the splash follows the browser.
Log.e(e);
}
}

@JSBody(params={"msg"}, script="window.onbeforeunload=function(){return msg;}")
private native static void setBeforeUnloadMessage(String msg);

Expand All @@ -5357,6 +5382,9 @@ private JavaScriptRuntimeEnvironment createRuntimeEnvironment(final WindowExt wi

@Override
public void setPlatformHint(String key, String value) {
if ("platformHint.javascript.splashAppearance".equalsIgnoreCase(key)) {
setSplashAppearance(value);
}
if ("platformHint.javascript.beforeUnloadMessage".equalsIgnoreCase(key)) {
if (value == null) {
removeBeforeUnload();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,18 @@ public interface JSFontMetrics extends JSObject {
JSObject getBounds();
}

/// The page's `window.localStorage`, for the few values the page itself reads before any
/// Java runs (the loading screen's remembered appearance).
public interface JSStorage extends JSObject {
void setItem(String key, String value);

void removeItem(String key);
}

public interface WindowExt extends JSObject {

@JSProperty
abstract JSStorage getLocalStorage();


@JSProperty
Expand Down
2 changes: 2 additions & 0 deletions Ports/JavaScriptPort/src/main/webapp/port.js
Original file line number Diff line number Diff line change
Expand Up @@ -4983,6 +4983,8 @@ const html5HideSplashWorkerSymbol = "cn1_com_codename1_impl_html5_HTML5Implement
if (typeof document !== "undefined"
&& typeof globalThis !== "undefined" && typeof globalThis.jQuery === "function") {
try {
// Same as the host handler: the page drops the splash background once started.
document.documentElement.setAttribute("data-cn1-started", "");
globalThis.jQuery("div#cn1-splash").fadeOut(100, function() {
globalThis.jQuery(this).remove();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/

package com.codenameone.developerguide.snippets.generated;

import com.codename1.ui.CN;

class WorkingWithJavascriptSplashAppearanceSnippet {
void snippet() {
// tag::working-with-javascript-splash-appearance[]
CN.setProperty("platformHint.javascript.splashAppearance", "dark"); // or "light"
CN.setProperty("platformHint.javascript.splashAppearance", null); // follow the browser again
// end::working-with-javascript-splash-appearance[]
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
// Generated from docs/developer-guide source blocks. Edit the guide snippets here, not inline.

// tag::working-with-javascript-html-001[]
<div id="cn1-splash">
<img class="icon" src="icon.png"/>

<img class="progress" src="progress.gif"/>
<p>...Loading...</p>
</div>
// end::working-with-javascript-html-001[]

// tag::working-with-javascript-html-002[]
<script src="//maps.googleapis.com/maps/api/js?v=3.exp&key=XYZ"></script>
// end::working-with-javascript-html-002[]
Expand Down
37 changes: 33 additions & 4 deletions docs/developer-guide/Working-With-Javascript.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -262,15 +262,44 @@ This tells Apache to proxy all requests for '/app' to the domain https://www.mye
[id="splash_screen", reftext="Customizing the Splash Screen"]
=== Customizing the splash screen

Since your application may include many resource files, videos, etc., the build-server will generate a splash screen for your app to display while it's loading. This basically shows a progress indicator with your app's icon.
While the application downloads and starts, the page shows a loading screen: the app icon, an animated progress bar and a "Loading" label. It stays up until the first form is shown.

You can customize this splash screen by modifying the HTML source inside the *cn1-splash* `div` tag of your app's index.html file:
Build hints set its look, so the settings survive every build instead of being edited into each generated bundle:

[source,html]
[cols="1,1,3",options="header"]
|===
|Build hint |Default |Meaning

|`javascript.splash.appearance`
|`auto` once any dark color is set, otherwise `light`
|`auto` follows the browser's light/dark preference. `light` or `dark` always uses that set of colors, for an app with a single appearance. An app that sets none of these hints keeps a light loading screen, so a light app never opens dark.

|`javascript.splash.background`, `javascript.splash.textColor`, `javascript.splash.accentColor`
|`#ffffff`, `#666666`, `#0072ff`
|The page background, the label and the progress bar in the light appearance.

|`javascript.splash.darkBackground`, `javascript.splash.darkTextColor`, `javascript.splash.darkAccentColor`
|`#121212`, `#b3b3b3`, `#4da3ff`
|The same three in the dark appearance.

|`javascript.splash.logo`
|The app icon
|A different image, as a path relative to the bundle's `index.html`, or `none` for no image.
|===

Colors are hex values (`#rgb`, `#rrggbb`, with optional alpha) or CSS color names. The page applies them before it paints anything, and keeps the background until the first form is shown, so a dark app no longer opens on a white page.

An app that lets the user choose its own appearance can have the next launch's loading screen match that choice, even when it differs from the browser's preference:

[source,java]
----
include::../demos/javascript/src/main/snippets/developer-guide/working-with-javascript.html[tag=working-with-javascript-html-001,indent=0]
include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/WorkingWithJavascriptSplashAppearanceSnippet.java[tag=working-with-javascript-splash-appearance,indent=0]
----

The choice is kept in the browser's local storage and applies only in `auto` mode.

For anything the hints don't cover, the markup is the `cn1-splash` `div` in the generated `index.html`; edit it there if you host the bundle yourself.

[id="debugging", reftext="Debugging in Chrome"]
=== Debugging

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,19 @@ public final class JavaScriptBuildHints {
"javascript.native.theme", "javascript.desktopTheme"
};

/// The `javascript.splash.*` hints that style the loading screen, each passed to the
/// translator as `codename1.<hint>` (see `JavascriptBundleWriter.SplashSettings`, which
/// validates them).
static final String[] SPLASH_HINTS = {
"javascript.splash.appearance", "javascript.splash.background",
"javascript.splash.textColor", "javascript.splash.accentColor",
"javascript.splash.darkBackground", "javascript.splash.darkTextColor",
"javascript.splash.darkAccentColor", "javascript.splash.logo"
};

/// The 192px icon `JavaScriptPwa` writes into every bundle.
static final String SPLASH_DEFAULT_LOGO = "_icons/icon-192.png";

private JavaScriptBuildHints() {
}

Expand Down Expand Up @@ -126,6 +139,20 @@ static List<String> translatorOptions(BuildRequest request) {
if (darkReader != null) {
out.add("-Dcodename1.javascript.darkreaderLock=" + !"false".equalsIgnoreCase(darkReader.trim()));
}
for (String hint : SPLASH_HINTS) {
String value = request.getArg(hint, null);
if (value != null && value.trim().length() > 0) {
out.add("-Dcodename1." + hint + "=" + value.trim());
}
}
// The loading screen shows the application's icon unless the developer named another
// logo (or none). JavaScriptPwa writes the icon to this path after the translator runs,
// from the same request.getIcon(); without an icon it writes a placeholder square, which
// identifies nothing, so then there is no logo.
String logo = request.getArg("javascript.splash.logo", null);
if ((logo == null || logo.trim().length() == 0) && request.getIcon() != null) {
out.add("-Dcodename1.javascript.splash.logo=" + SPLASH_DEFAULT_LOGO);
}
return out;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,29 @@ void translatorGetsTheTitleAndOnlyTheOptOutsThatWereSet() {
assertTrue(opts.contains("-Dcodename1.javascript.darkreaderLock=false"), opts.toString());
}

@Test
void splashHintsReachTheTranslatorAndTheIconIsTheDefaultLogo() {
BuildRequest r = request("javascript.splash.appearance", " dark ",
"javascript.splash.darkBackground", "#1e1e1e");
List<String> opts = JavaScriptBuildHints.translatorOptions(r);
assertTrue(opts.contains("-Dcodename1.javascript.splash.appearance=dark"), opts.toString());
assertTrue(opts.contains("-Dcodename1.javascript.splash.darkBackground=#1e1e1e"), opts.toString());
// No icon: the PWA step writes a placeholder square, which is not worth showing.
assertFalse(opts.toString().contains("splash.logo"), opts.toString());

r.setIcon(new byte[] {1});
opts = JavaScriptBuildHints.translatorOptions(r);
assertTrue(opts.contains("-Dcodename1.javascript.splash.logo=" + JavaScriptBuildHints.SPLASH_DEFAULT_LOGO),
opts.toString());

r = request("javascript.splash.logo", "none");
r.setIcon(new byte[] {1});
opts = JavaScriptBuildHints.translatorOptions(r);
assertTrue(opts.contains("-Dcodename1.javascript.splash.logo=none"), opts.toString());
assertFalse(opts.contains("-Dcodename1.javascript.splash.logo=" + JavaScriptBuildHints.SPLASH_DEFAULT_LOGO),
opts.toString());
}

@Test
void noHintsShipsOnlyTheLegacyPair() {
assertEquals(set("iOS7Theme", "android_holo_light"), ship(request()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,79 @@ static void register(List<Hint> h) {
+ "lock in its default Dynamic mode only; its Filter, Filter+ and Static modes "
+ "ignore it. Set to `false` to omit the tag."));

h.add(new Hint("javascript.splash.appearance")
.group(HintGroup.JAVASCRIPT)
.values("SplashAppearance", "auto", "light", "dark")
.platform("javascript")
.doc("Which colors the loading screen uses. Defaults to `auto` when any "
+ "`javascript.splash.dark*` color is set and to `light` otherwise, so an app "
+ "that never asked for a dark loading screen doesn't get one. `auto` follows the "
+ "browser's light/dark preference, or the choice the app remembered with "
+ "`Display.setProperty(\"platformHint.javascript.splashAppearance\", \"dark\")`. "
+ "`light` and `dark` always use that set, for apps with one appearance."));

h.add(new Hint("javascript.splash.background")
.group(HintGroup.JAVASCRIPT)
.type(HintType.STRING)
.def("#ffffff")
.platform("javascript")
.doc("The loading screen's background in light appearance, as a hex color (`#rgb`, `#rrggbb`, "
+ "with optional alpha) or a CSS color name. Applied from the first frame the "
+ "page paints until the first form shows."));

h.add(new Hint("javascript.splash.textColor")
.group(HintGroup.JAVASCRIPT)
.type(HintType.STRING)
.def("#666666")
.platform("javascript")
.doc("The loading screen's text color in light appearance, as a hex color (`#rgb`, `#rrggbb`, "
+ "with optional alpha) or a CSS color name. Applied from the first frame the "
+ "page paints until the first form shows."));

h.add(new Hint("javascript.splash.accentColor")
.group(HintGroup.JAVASCRIPT)
.type(HintType.STRING)
.def("#0072ff")
.platform("javascript")
.doc("The loading screen's progress bar color in light appearance, as a hex color (`#rgb`, `#rrggbb`, "
+ "with optional alpha) or a CSS color name. Applied from the first frame the "
+ "page paints until the first form shows."));

h.add(new Hint("javascript.splash.darkBackground")
.group(HintGroup.JAVASCRIPT)
.type(HintType.STRING)
.def("#121212")
.platform("javascript")
.doc("The loading screen's background in dark appearance, as a hex color (`#rgb`, `#rrggbb`, "
+ "with optional alpha) or a CSS color name. Applied from the first frame the "
+ "page paints until the first form shows."));

h.add(new Hint("javascript.splash.darkTextColor")
.group(HintGroup.JAVASCRIPT)
.type(HintType.STRING)
.def("#b3b3b3")
.platform("javascript")
.doc("The loading screen's text color in dark appearance, as a hex color (`#rgb`, `#rrggbb`, "
+ "with optional alpha) or a CSS color name. Applied from the first frame the "
+ "page paints until the first form shows."));

h.add(new Hint("javascript.splash.darkAccentColor")
.group(HintGroup.JAVASCRIPT)
.type(HintType.STRING)
.def("#4da3ff")
.platform("javascript")
.doc("The loading screen's progress bar color in dark appearance, as a hex color (`#rgb`, `#rrggbb`, "
+ "with optional alpha) or a CSS color name. Applied from the first frame the "
+ "page paints until the first form shows."));

h.add(new Hint("javascript.splash.logo")
.group(HintGroup.JAVASCRIPT)
.type(HintType.STRING)
.platform("javascript")
.doc("The image the loading screen shows above the progress bar, as a path relative to "
+ "the bundle's `index.html` (letters, digits, `/`, `.`, `-` and `_`). Defaults to the "
+ "app icon; `none` shows no image."));

h.add(new Hint("javascript.desktopTheme")
.group(HintGroup.JAVASCRIPT)
.type(HintType.STRING)
Expand Down
Loading
Loading