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");
+ }
}