From a1b33307eaeddc4e0a99de4b62d74ffa37612b45 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 26 Oct 2026 08:56:35 +0200 Subject: [PATCH] JavaScript port: no desktop menu on touch long press, page background follows the form Fixes #6108: Android Chrome fires contextmenu for a touch long press, which opened CN1's right-click menu whenever the press landed on the canvas. Fixes #6109: the page behind the canvas takes the form's opaque plain background on form show and canvas resize, so resizes no longer flash white. Co-Authored-By: Claude Opus 5.5 --- .../impl/html5/HTML5Implementation.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) 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 2ff394fb42a..98037c79ba3 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 @@ -4984,12 +4984,64 @@ public void mouseWheelMoved(WheelEvent e) { pointerWheelMoved(x, y, dx, dy, true, 0); } + /// The CSS color last given to the page, or null while it is the stylesheet's own. + private String pageBackground; + + /// Gives the page behind the canvas the background of the form now showing (issue #6109). + /// + /// Resizing a canvas clears it, and the browser grows the viewport before the worker hears + /// of it, so until the form repaints at the new size the page itself shows -- white, behind + /// a dark form, on every keyboard open and window resize. Nothing is painted here and no + /// paint is added: the page is only seen in that gap, so its color is set when a form + /// arrives and when the canvas is resized, and the DOM is written only when it changes. A + /// dialog keeps the color of the form beneath it, which still fills the screen. + /// + /// A form without an opaque, single-color background leaves the page alone: what shows through it is + /// whatever was there, and a color guessed from the theme could be wrong. + private void syncPageBackground(Form f) { + if (f == null || f instanceof com.codename1.ui.Dialog) { + return; + } + Style s = f.getContentPane().getUnselectedStyle(); + if ((s.getBgTransparency() & 0xff) != 0xff) { + s = f.getUnselectedStyle(); + } + String color = null; + // An image or gradient is not one color; the page is left alone rather than given its base. + if ((s.getBgTransparency() & 0xff) == 0xff && s.getBgImage() == null + && !isGradientBackground(s.getBackgroundType())) { + String hex = Integer.toHexString(s.getBgColor() & 0xffffff); + color = "#000000".substring(0, 7 - hex.length()) + hex; + } + if (color == null ? pageBackground == null : color.equals(pageBackground)) { + return; + } + pageBackground = color; + if (color == null) { + doc().getDocumentElement().getStyle().removeProperty("background-color"); + doc().getBody().getStyle().removeProperty("background-color"); + } else { + doc().getDocumentElement().getStyle().setProperty("background-color", color); + doc().getBody().getStyle().setProperty("background-color", color); + } + } + + // Not BACKGROUND_NONE: a style's type defaults to BACKGROUND_IMAGE_SCALED and a theme + // writes no type for a plain color, so a plain fill is any image type with no image. + private static boolean isGradientBackground(byte type) { + return type == Style.BACKGROUND_GRADIENT_LINEAR_VERTICAL + || type == Style.BACKGROUND_GRADIENT_LINEAR_HORIZONTAL + || type == Style.BACKGROUND_GRADIENT_RADIAL + || type >= Style.BACKGROUND_GRADIENT_LINEAR && type <= Style.BACKGROUND_GRADIENT_REPEATING_RADIAL; + } + private void updateCanvasSize() { refreshDevicePixelRatio(); JavaScriptCanvasLayout.Dimensions dimensions = JavaScriptCanvasLayout.compute( doc().getBody().getClientWidth(), window.getInnerHeight() - chromeTopCss, devicePixelRatioValue()); + syncPageBackground(getCurrentForm()); canvas.setWidth(dimensions.getBackingWidth()); canvas.setHeight(dimensions.getBackingHeight()); outputCanvas.setWidth(dimensions.getBackingWidth()); @@ -13332,6 +13384,7 @@ public void setCurrentForm(Form f) { // resolved then, so it is brought up to date as it appears rather than coming back in // the old palette. refreshThemeIfStale(f); + syncPageBackground(f); // Taken for every arrival, whatever is done with it: the framework queues one direction // per display asked for, and a queue read only on some paths would drift out of step // with what is still waiting. @@ -15729,6 +15782,16 @@ public void handleEvent(final MouseEvent evt) { if (target == null || !"codenameone-canvas".equals(target.getAttribute("id"))) { return; } + // Only a right-click, never a finger. Android Chrome fires contextmenu for a touch + // long press too, so a long press landing on the canvas rather than on a native text + // overlay opened this desktop menu, while one landing on the overlay got the + // browser's -- the menu alternated between attempts (issue #6108). A long press is + // already TextSelection's (LongPointerPress) on canvas text, and the menu was placed + // at lastMouseEvent, which a touch never updates. touchDown covers a browser whose + // contextmenu is not a PointerEvent: it fires while the finger is still down. + if (isTouchPointer(evt) || pointerState.isTouchDown()) { + return; + } Form f = CN.getCurrentForm(); if (f == null) { return;