Skip to content
Open
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
30 changes: 28 additions & 2 deletions .github/workflows/scripts-javascript.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ on:
- 'scripts/test-javascript-surface-replay.mjs'
- 'scripts/test-javascript-weak-refs.mjs'
- 'scripts/test-javascript-vault-bridge.mjs'
- 'scripts/test-javascript-composited-rendering.mjs'
- 'scripts/javascript/composited-app/**'
Comment thread
shai-almog marked this conversation as resolved.
- 'vm/ByteCodeTranslator/src/javascript/browser_bridge.js'
- 'scripts/run-javascript-lifecycle-tests.sh'
- 'scripts/verify-javascript-lens-parity.mjs'
Expand Down Expand Up @@ -58,6 +60,8 @@ on:
- 'scripts/ci/retry.sh'
- '.github/workflows/scripts-javascript.yml'
- 'scripts/run-javascript-browser-tests.sh'
- 'scripts/test-javascript-composited-rendering.mjs'
- 'scripts/javascript/composited-app/**'
- 'scripts/run-javascript-screenshot-tests.sh'
- 'scripts/run-javascript-headless-browser.mjs'
- 'scripts/run-javascript-lifecycle-tests.mjs'
Expand Down Expand Up @@ -231,7 +235,7 @@ jobs:
uses: actions/cache@v5
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-chromium-v2
key: ${{ runner.os }}-playwright-chromium-firefox-v1
restore-keys: |
${{ runner.os }}-playwright-

Expand All @@ -246,7 +250,7 @@ jobs:
# vendor mirror makes its apt-get fail as a whole and it reports
# only "Failed to install browser dependencies".
bash "$GITHUB_WORKSPACE/scripts/ci/apt-get-update.sh"
npx playwright install-deps chromium
npx playwright install-deps chromium firefox
# `npm install playwright` resolves the floating version, so the
# cached browser binary can drift away from what the freshly
# installed Playwright expects (the cache only stores the binary).
Expand All @@ -255,6 +259,10 @@ jobs:
# job resilient to Playwright revving its bundled chromium build
# in between cache writes.
npx playwright install chromium
# Firefox for the composited rendering check below: issue #5910 was reported
# from Firefox, and the two engines composite the canvas and the text layer
# differently enough that one passing says little about the other.
npx playwright install firefox

# Placed here, before the workspace build, because it needs nothing from
# it: the vault bridge is sliced straight out of browser_bridge.js and run
Expand Down Expand Up @@ -287,6 +295,24 @@ jobs:
# xvfb-run is required for CSS compilation which uses java.awt
SKIP_PARPARVM_BUILD=1 xvfb-run ./scripts/build-javascript-port-hellocodenameone.sh "${GITHUB_WORKSPACE}/artifacts/javascript-ui-tests/hellocodenameone-javascript-port.zip"

# What the screenshot suite cannot see. Its capture reads the app's canvas back, which
# suspends the DOM text layer, so it never looks at the page a browser shows -- and
# issues #5910 (toolbar scrolled away, dialog over a blank page) and #5912 (a phone could
# not scroll or tap) came back more than once while it stayed green. This builds the app
# those issues were reported against through the Maven plugin, as a user's project is
# built, and drives it with real wheel, mouse and touch input in Chromium and Firefox.
- name: Composited rendering and input (Chromium, Firefox, phone emulation)
run: |
set -euo pipefail
export JAVA_HOME="${JAVA_HOME_17_X64}"
export PATH="$JAVA_HOME/bin:$PATH"
(cd scripts/javascript/composited-app && xvfb-run mvn -B package -DskipTests \
-Dcodename1.platform=javascript -Dcodename1.buildTarget=local-javascript)
bundle_dir="${RUNNER_TEMP}/composited-app"
rm -rf "$bundle_dir" && mkdir -p "$bundle_dir"
unzip -q scripts/javascript/composited-app/javascript/target/compositedapp-javascript-1.0-SNAPSHOT.zip -d "$bundle_dir"
node scripts/test-javascript-composited-rendering.mjs "$bundle_dir" "${GITHUB_WORKSPACE}/artifacts/javascript-ui-tests/composited"

- name: Locate JavaScript bundle
id: locate_bundle
run: |
Expand Down
15 changes: 15 additions & 0 deletions CodenameOne/src/com/codename1/ui/Display.java
Original file line number Diff line number Diff line change
Expand Up @@ -9588,6 +9588,21 @@ private void setScrollPosition(Component target, boolean vertical, int position)
private boolean scrollAxisForWheel(Component cmp, boolean vertical, int delta) {
Component c = cmp;
while (c != null) {
// The walk ends at the top level. Form and Window answer isScrollableY() for their
// content pane, which the walk has already visited on the way up -- but applyScroll
// would move the top level's OWN scroll position, and that translates everything it
// paints: the Toolbar scrolled off with the content, and the space it left was blank
// (issue #5910). Reaching the top level means the page is already at its edge.
if (c instanceof TopLevelContainer) {
if (c.getParent() == null) {
return false;
}
// An embedded Form -- in a Window's layered pane, in an EmbeddedContainer --
// has the same delegating isScrollableY() and must not be moved either, but a
// scrollable host above it can still take the wheel.
c = c.getParent();
continue;
}
// A disabled component takes no wheel, exactly as it took no synthetic drag:
// Form.pointerDragged gated on isEnabled, so disabling a scroller used to stop
// the wheel too. The walk continues, so an enabled ancestor still gets it.
Expand Down
14 changes: 10 additions & 4 deletions CodenameOne/src/com/codename1/ui/Form.java
Original file line number Diff line number Diff line change
Expand Up @@ -3299,11 +3299,17 @@ void initComponentImpl() {
// an EmbeddedContainer -- would otherwise overwrite the real surface's native
// menu bar with its own.
if (getParent() == null) {
if (Display.getInstance().isNativeCommands()) {
Display.impl.setNativeCommands(menuBar.getCommands());
} else if (isDesktopNativeChrome() && toolbar != null) {
// bridge the (hidden) toolbar's commands to the native desktop menu bar
if (isDesktopNativeChrome() && toolbar != null) {
// bridge the (hidden) toolbar's commands to the native desktop menu bar.
// Checked BEFORE the native command behaviour: the desktop native themes declare
// commandBehavior Native, and with a Toolbar the commands live on the Toolbar
// rather than on this form's MenuBar -- publishing the MenuBar's list there
// handed the native menu bar nothing, so it came up empty while the Toolbar
// that held the commands was hidden. getAllNativeMenuCommands() includes the
// Toolbar's MenuBar, so nothing the other branch would publish is lost.
Display.impl.setNativeCommands(toolbar.getAllNativeMenuCommands());
Comment thread
shai-almog marked this conversation as resolved.
} else if (Display.getInstance().isNativeCommands()) {
Display.impl.setNativeCommands(menuBar.getCommands());
}
}
if (getParent() != null) {
Expand Down
14 changes: 11 additions & 3 deletions CodenameOne/src/com/codename1/ui/Label.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public class Label extends Component implements IconHolder, TextHolder {
private String text = "";
private TextSelectionSupport textSelectionSupport;
private boolean textSelectionEnabled;
/// True once `#setTextSelectionEnabled(boolean)` decided; until then the label follows
/// `TextSelection#setDefaultSelectable(boolean)`.
private boolean textSelectionExplicit;
private Image icon;
private final ActionListener iconChangeListener = new ActionListener() {
@Override
Expand Down Expand Up @@ -1499,7 +1502,11 @@ public void setAutoSizeMode(boolean autoSizeMode) {
///
/// - #setTextSelectionEnabled(boolean)
public boolean isTextSelectionEnabled() {
return textSelectionEnabled;
if (textSelectionExplicit) {
return textSelectionEnabled;
}
// A Button's press is its action, so it never takes the selectable default.
return !(this instanceof Button) && TextSelection.isSelectableByDefault(this);
}

/// Enables text selection on this label. Text selection must also be enabled on the Form in order to
Expand All @@ -1518,6 +1525,7 @@ public boolean isTextSelectionEnabled() {
/// - TextSelection#setEnabled(boolean)
public void setTextSelectionEnabled(boolean enabled) {
this.textSelectionEnabled = enabled;
this.textSelectionExplicit = true;
if (enabled) {
setCursor(Component.TEXT_CURSOR);
}
Expand All @@ -1543,12 +1551,12 @@ public TextSelection.Spans getTextSelectionForBounds(TextSelection sel, Rectangl

@Override
public boolean isTextSelectionEnabled(TextSelection sel) {
return textSelectionEnabled;
return Label.this.isTextSelectionEnabled();
}

@Override
public boolean isTextSelectionTriggerEnabled(TextSelection sel) {
return textSelectionEnabled;
return Label.this.isTextSelectionEnabled();
}

@Override
Expand Down
13 changes: 10 additions & 3 deletions CodenameOne/src/com/codename1/ui/TextArea.java
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ public void actionPerformed(ActionEvent evt) {
}
};
private boolean textSelectionEnabled;
/// True once `#setTextSelectionEnabled(boolean)` decided; until then a read-only text area
/// follows `TextSelection#setDefaultSelectable(boolean)`.
private boolean textSelectionExplicit;
private TextSelection.Spans span;
private TextSelection.TextSelectionSupport textSelectionSupport;

Expand Down Expand Up @@ -2352,7 +2355,10 @@ protected Spans calculateTextSelectionSpan(TextSelection sel) {
///
/// - #setTextSelectionEnabled(boolean)
public boolean isTextSelectionEnabled() {
return textSelectionEnabled;
if (textSelectionExplicit) {
return textSelectionEnabled;
}
return !isEditable() && TextSelection.isSelectableByDefault(this);
}

/// Enables text selection on this TextArea. Text selection must also be enabled on the Form in order to
Expand All @@ -2371,6 +2377,7 @@ public boolean isTextSelectionEnabled() {
/// - TextSelection#setEnabled(boolean)
public void setTextSelectionEnabled(boolean enabled) {
this.textSelectionEnabled = enabled;
this.textSelectionExplicit = true;
updateCursor();
}

Expand All @@ -2397,13 +2404,13 @@ public TextSelection.Spans getTextSelectionForBounds(TextSelection sel, Rectangl

@Override
public boolean isTextSelectionEnabled(TextSelection sel) {
return (!isEditable() && textSelectionEnabled) || (isEditable() && !isEnabled());
return (!isEditable() && TextArea.this.isTextSelectionEnabled()) || (isEditable() && !isEnabled());
}


@Override
public boolean isTextSelectionTriggerEnabled(TextSelection sel) {
return (!isEditable() && textSelectionEnabled) || (isEditable() && !isEnabled());
return (!isEditable() && TextArea.this.isTextSelectionEnabled()) || (isEditable() && !isEnabled());
}


Expand Down
37 changes: 36 additions & 1 deletion CodenameOne/src/com/codename1/ui/TextSelection.java
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ public int compare(Component o1, Component o2) {
private final Spans selectedSpans = new Spans();
private final EventDispatcher textSelectionListeners = new EventDispatcher();
private final Rectangle tmpRect = new Rectangle();
private static boolean defaultSelectable;
private final Component root;
private boolean rtl;
private final TextSelectionTrigger trigger = getDefaultTextSelectionTrigger();
Expand Down Expand Up @@ -467,6 +468,40 @@ public void run() {
this.root = root;
}

/// Makes read-only text selectable wherever the application did not decide: labels, span
/// labels and non-editable text areas report `isTextSelectionEnabled()` true unless
/// `setTextSelectionEnabled` was called on them. Off by default.
///
/// Buttons never take the default, and neither does text inside a lead component such as
/// a `MultiButton`: the press that would start a selection there is the press that
/// activates the control.
///
/// Text selection still has to be enabled on the form (`Form#getTextSelection()`) for any
/// of this to be reachable. The JavaScript port does both when the
/// `javascript.textSelection` build hint is set.
///
/// #### Parameters
///
/// - `selectable`: true to make read-only text selectable by default
public static void setDefaultSelectable(boolean selectable) {
defaultSelectable = selectable;
}

/// Whether read-only text is selectable by default. See `#setDefaultSelectable(boolean)`.
///
/// #### Returns
///
/// true when read-only text is selectable unless a component says otherwise
public static boolean isDefaultSelectable() {
return defaultSelectable;
}

/// Whether `cmp` takes the default of `#setDefaultSelectable(boolean)`: it is on, and the
/// component is not part of a lead component, whose press belongs to the control.
static boolean isSelectableByDefault(Component cmp) {
return defaultSelectable && cmp.getLeadComponent() == null;
}

/// Gets the default trigger type for text selection. This will vary by platform.
/// On mobile/touch devices, it will return `TextSelectionTrigger#LongPress`,
/// and on desktop environments with a mouse, it will return `TextSelectionTrigger#Press`.
Expand Down Expand Up @@ -545,7 +580,7 @@ public void setEnabled(boolean enabled) {
f.removePointerDraggedListener(pressListener);
f.removePointerReleasedListener(pressListener);
f.removeDragFinishedListener(pressListener);
f.addLongPressListener(pressListener);
f.removeLongPressListener(pressListener);
Display.impl.deinitializeTextSelection(this);
}
}
Expand Down
34 changes: 29 additions & 5 deletions CodenameOne/src/com/codename1/ui/Toolbar.java
Original file line number Diff line number Diff line change
Expand Up @@ -910,8 +910,13 @@ public void actionPerformed(ActionEvent evt) {
///
/// - `callback`: gets the search string callbacks
public void showSearchBar(final ActionListener<ActionEvent> callback) {
SearchBar s = new CallbackSearchBar(this, callback);
Form f = getComponentForm();
if (f == null) {
// Detached -- the desktop chrome hides the Toolbar in native title-bar mode -- so
// there is no Toolbar on screen for the search bar to replace.
return;
}
SearchBar s = new CallbackSearchBar(this, callback);
setHidden(true);
f.removeComponentFromForm(this);
f.setToolbar(s);
Expand Down Expand Up @@ -1019,6 +1024,7 @@ public void addCommandToOverflowMenu(Command cmd) {
}
overflowCommands.add(cmd);
sideMenu.installRightCommands();
refreshDesktopHiddenNativeCommands();
}

/// Returns the commands within the overflow menu which can be useful for
Expand Down Expand Up @@ -1485,7 +1491,11 @@ private boolean addDesktopHiddenSideMenuCommand(Command cmd) {
return true;
}

/// `Form.initComponentImpl` publishes the commands when the form is shown, this covers later changes
/// `Form.initComponentImpl` publishes the commands when the form is shown, this covers later changes.
/// Every mutation of the Toolbar's command lists calls it -- the left and right bars and the
/// overflow as well as the side menu -- because with the Toolbar hidden by the desktop chrome
/// the native menu is the only place those commands can be reached. A no-op unless this
/// Toolbar's hidden host form is the one on screen.
private void refreshDesktopHiddenNativeCommands() {
Form host = desktopHiddenHost;
if (host != null && host.isInitialized() && host.getParent() == null
Expand Down Expand Up @@ -2516,10 +2526,13 @@ public Command addCommandToRightBar(String name, Image icon, final ActionListene
///
/// - `cmd`: Command to remove
public void removeCommand(Command cmd) {
if (desktopHiddenSideMenuCommands != null && desktopHiddenSideMenuCommands.remove(cmd)) {
refreshDesktopHiddenNativeCommands();
if (desktopHiddenSideMenuCommands != null) {
desktopHiddenSideMenuCommands.remove(cmd);
}
getMenuBar().removeCommand(cmd);
// After the removal, not before: the published set is read back from the Toolbar, and
// a command in the left or right bar leaves through the MenuBar above.
refreshDesktopHiddenNativeCommands();
}

/// Adds a Command to the TitleArea on the right side.
Expand All @@ -2532,6 +2545,7 @@ public void addCommandToRightBar(Command cmd) {
cmd.putClientProperty("TitleCommand", Boolean.TRUE);
cmd.putClientProperty("Left", null);
sideMenu.addCommand(cmd, 0);
refreshDesktopHiddenNativeCommands();
}

/// Adds a Command to the TitleArea on the left side.
Expand Down Expand Up @@ -2563,6 +2577,7 @@ public void addCommandToLeftBar(Command cmd) {
cmd.putClientProperty("TitleCommand", Boolean.TRUE);
cmd.putClientProperty("Left", Boolean.TRUE);
sideMenu.addCommand(cmd, 0);
refreshDesktopHiddenNativeCommands();
}

/// Returns the commands within the right bar section which can be useful for
Expand Down Expand Up @@ -2642,6 +2657,12 @@ public Vector getAllNativeMenuCommands() {
}
}
addUniqueCommands(all, desktopHiddenSideMenuCommands);
// The search command opens a search bar IN the Toolbar, which is exactly what native
// chrome detaches; from a native menu it would have nothing to open. Leaving it out keeps
// the menu to commands that work there.
if (searchCommand != null) {
all.removeElement(searchCommand);
}
return all;
}

Expand Down Expand Up @@ -3211,7 +3232,10 @@ public Iterable<Command> getRightSideMenuCommands() {
///
/// - `cmd`: the command to remove from the overflow
public void removeOverflowCommand(Command cmd) {
overflowCommands.remove(cmd);
if (overflowCommands != null) {
overflowCommands.remove(cmd);
}
refreshDesktopHiddenNativeCommands();
}

/// Normally on a right side menu the alignment should be "mirrored" in
Expand Down
Loading
Loading