This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 4c940d24d0 [IT] fix broken selenium integration tests (#8372)
4c940d24d0 is described below

commit 4c940d24d011f90da8240eadad66f9d689b300a2
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Mon Sep 14 16:47:30 2026 +0200

    [IT] fix broken selenium integration tests (#8372)
---
 .../main/java/org/apache/hop/ui/hopgui/HopWeb.java |  1 +
 .../org/apache/hop/ui/hopgui/HopWebEntryPoint.java |  2 +
 .../org/apache/hop/ui/hopgui/gc-pending-images.js  | 54 ++++++++++++++++++++++
 .../org/apache/hop/web/it/HopWebCanvasTest.java    | 33 +++++++++++++
 4 files changed, 90 insertions(+)

diff --git a/rap/src/main/java/org/apache/hop/ui/hopgui/HopWeb.java 
b/rap/src/main/java/org/apache/hop/ui/hopgui/HopWeb.java
index ddc6af384c..5089dc5578 100644
--- a/rap/src/main/java/org/apache/hop/ui/hopgui/HopWeb.java
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/HopWeb.java
@@ -168,6 +168,7 @@ public class HopWeb implements ApplicationConfiguration {
             "org/apache/hop/ui/hopgui/clipboard.js",
             "org/apache/hop/ui/hopgui/canvas-zoom.js",
             "org/apache/hop/ui/hopgui/canvas-svg.js",
+            "org/apache/hop/ui/hopgui/gc-pending-images.js",
             "org/apache/hop/ui/hopgui/monaco-editor.js",
             "org/apache/hop/ui/hopgui/mac-command-keys.js")
         .forEach(
diff --git a/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java 
b/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
index a40e6ccb1d..aa799ab010 100644
--- a/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
@@ -170,6 +170,8 @@ public class HopWebEntryPoint extends AbstractEntryPoint {
     String jsLocation = resourceManager.getLocation("js/canvas-zoom.js");
     jsLoader.require(jsLocation);
     jsLoader.require(resourceManager.getLocation("js/canvas-svg.js"));
+    // RAP's GC leaves image onload handlers alive after dispose; see the 
script.
+    jsLoader.require(resourceManager.getLocation("js/gc-pending-images.js"));
     jsLoader.require(resourceManager.getLocation("js/monaco-editor.js"));
     // Map Mac Command key to Ctrl so RAP ACTIVE_KEYS (CTRL+S etc.) match when 
user presses Cmd+S
     String macKeysLocation = 
resourceManager.getLocation("js/mac-command-keys.js");
diff --git 
a/rap/src/main/resources/org/apache/hop/ui/hopgui/gc-pending-images.js 
b/rap/src/main/resources/org/apache/hop/ui/hopgui/gc-pending-images.js
new file mode 100644
index 0000000000..3e2867b11f
--- /dev/null
+++ b/rap/src/main/resources/org/apache/hop/ui/hopgui/gc-pending-images.js
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//# sourceURL=gc-pending-images.js
+
+/**
+ * Stops a disposed RAP canvas from throwing when an image it was still 
loading arrives.
+ *
+ * RAP's rwt.widgets.GC draws images asynchronously: when drawImage is asked 
for an image the
+ * browser has not loaded yet, drawing pauses and an onload handler finishes 
it later. A new
+ * draw() clears those handlers, but destruct() does not (RAP 4.7.0 and 4.8.0, 
unchanged
+ * upstream since 2022): it just nulls the 2D context. So a canvas that is 
disposed while one of
+ * its images is in flight - a context dialog closed the moment it appears, 
before every action
+ * icon has arrived - later runs onload against a dead GC and logs
+ * "Uncaught TypeError: Cannot read properties of null (reading 'save')".
+ *
+ * Nothing is lost for the user (the widget is gone), but it is an uncaught 
error on every
+ * quick close, and the selenium tests rightly fail on any browser console 
error. Drop the
+ * pending handlers before the destructor runs, which is what draw() already 
does.
+ */
+(function () {
+    "use strict";
+
+    if (typeof rwt === "undefined" || !rwt.widgets || !rwt.widgets.GC) {
+        return;
+    }
+    var GC = rwt.widgets.GC;
+    // rwt.qx.Class stores "destruct" here, and rwt.qx.Object.dispose calls it 
per class.
+    var destruct = GC.$$destructor;
+    if (!destruct || destruct.hopDropsPendingImages) {
+        return;
+    }
+    GC.$$destructor = function () {
+        if (typeof this._cleanPendingImages === "function") {
+            this._cleanPendingImages();
+        }
+        destruct.call(this);
+    };
+    GC.$$destructor.hopDropsPendingImages = true;
+})();
diff --git 
a/web-tests/src/test/java/org/apache/hop/web/it/HopWebCanvasTest.java 
b/web-tests/src/test/java/org/apache/hop/web/it/HopWebCanvasTest.java
index 5d54710af5..e3e2f72a53 100644
--- a/web-tests/src/test/java/org/apache/hop/web/it/HopWebCanvasTest.java
+++ b/web-tests/src/test/java/org/apache/hop/web/it/HopWebCanvasTest.java
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import org.apache.hop.web.it.pages.PipelineGraphPage;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
+import org.openqa.selenium.JavascriptExecutor;
 
 /**
  * Working on the canvas itself: moving things, undoing, zooming.
@@ -101,4 +102,36 @@ class HopWebCanvasTest extends HopWebTestBase {
         graph.transformIconSize(TRANSFORM) <= normal,
         () -> "zooming back out left the icon at " + 
graph.transformIconSize(TRANSFORM));
   }
+
+  /**
+   * RAP's client-side GC keeps the onload handler of an image it is still 
waiting for after the
+   * canvas is disposed, and that handler then throws against the nulled 
context. A context dialog
+   * closed before every action icon has arrived is enough to trigger it, 
which the dialog sweep did
+   * several times a week. Hop Web patches the destructor 
(gc-pending-images.js); this runs a GC
+   * through that destructor in the real client and checks the handlers are 
gone.
+   */
+  @Test
+  @DisplayName("a disposed canvas drops the images it was still loading")
+  void disposedCanvasForgetsPendingImages() {
+    Object result =
+        ((JavascriptExecutor) driver)
+            .executeScript(
+                String.join(
+                    "\n",
+                    "var GC = rwt.widgets.GC;",
+                    "var image = { onload: function() {}, onerror: function() 
{} };",
+                    "var gc = Object.create(GC.prototype);",
+                    "gc._control = { removeEventListener: function() {},",
+                    "                isCreated: function() { return false; } 
};",
+                    "gc._canvas = {};",
+                    "gc._context = {};",
+                    "gc._pendingImages = [ image ];",
+                    "GC.$$destructor.call(gc);",
+                    "return image.onload === null && image.onerror === 
null;"));
+
+    assertTrue(
+        Boolean.TRUE.equals(result),
+        "disposing a GC left its pending image handlers alive; they would run 
against a null"
+            + " context when the image arrives");
+  }
 }

Reply via email to