RFR: 8088420: JavaFX WebView memory leak via EventListener [v2]

Kevin Rushforth kcr at openjdk.java.net
Fri May 20 01:29:56 UTC 2022


On Thu, 19 May 2022 15:57:46 GMT, Jay Bhaskar <jbhaskar at openjdk.org> wrote:

>> This PR is new implementation of JavaEvent listener memory management.
>> Issue  [JDK-8088420](https://bugs.openjdk.java.net/browse/JDK-8088420?filter=-1)
>> 
>> 1. Calling remove event listener does not free jni global references.
>> 2. When WebView goes out of scope (disposed from app) , its Event Listeners are not being garbage collected.
>> 
>> Solution:
>> 1.  Detached the jni global reference from JavaEventListener.
>> 2. Create scoped ref counted wrapper class JavaObjectWrapperHandler for jni global reference.
>> 3. Create unique  JavaObjectWrapperHandler object for each JavaEventListener.
>> 4. EventListenerManager is a singleton class , which stores the JavaObjectWrapperHandler mapped with JavaEventListener.
>> 5. EventListenerManager also stores the JavaEventListener mapped with DOMWindow.
>> 6. When Event listener explicitly removed , JavaEventListener is being forwarded to EventListenerManager to clear the listener.
>> 7. When WebView goes out of scope, EventListenerManager will de-registered all the event listeners based on the ref counts attached with WebView DOMWindow.
>
> Jay Bhaskar has updated the pull request incrementally with one additional commit since the last revision:
> 
>   Platform.exit() , removing code block, as it is causing other test fail

The fix looks good with a couple questions about removing entries from the map when they are done and some cleanup comments.

In addition to the inline comments I left about the test, I think the following scenarios should be added to the automated test:

1. Multiple listeners single webiew implicit release (i.e., WebView goes out of scope)
2. Multiple listeners multiple webiew with explicit release of one webview and implicit release of the other (this one is basically a combination of the others)
3. Ref count test (single webview is fine) with adding and removing listeners. It should handle the cases of both increasing and decreasing the ref count, and adding the listener to another DOM node after its ref count has gone to zero to make sure that case works.

modules/javafx.web/src/main/native/Source/WebCore/bindings/java/EventListenerManager.cpp line 57:

> 55:          if (it->second && it->second->use_count() == 1) {
> 56:              delete it->second;
> 57:              it->second = nullptr;

Do you need to remove the item from the list in this case?

modules/javafx.web/src/main/native/Source/WebCore/bindings/java/EventListenerManager.cpp line 91:

> 89:     for (win_it = windowHasEvent.begin(); win_it != windowHasEvent.end(); win_it++) {
> 90:         // de register associated event listeners with window
> 91:         if(window == win_it->second) {

Minor: add space after the `if`.

modules/javafx.web/src/main/native/Source/WebCore/bindings/java/EventListenerManager.cpp line 92:

> 90:         // de register associated event listeners with window
> 91:         if(window == win_it->second) {
> 92:             unregisterListener(win_it->first);

Same question as earlier: do you need to also remove the entries matching the `window` from the list here?

modules/javafx.web/src/main/native/Source/WebCore/bindings/java/EventListenerManager.h line 48:

> 46: 
> 47: class JavaObjectWrapperHandler {
> 48:     jobject handler_;

Have you considered using `JGObject` here instead of raw `jobject`?

modules/javafx.web/src/main/native/Source/WebCore/bindings/java/JavaEventListener.cpp line 50:

> 48:                                         ? static_cast<const JavaEventListener*>(&other)
> 49:                                         : nullptr;
> 50:     return jother && ( this == jother);

Minor: you can remove the space after `(`

modules/javafx.web/src/main/native/Source/WebCore/bindings/java/JavaEventListener.h line 32:

> 30: #include "Node.h"
> 31: 
> 32: #if PLATFORM(JAVA)

This is a java platform-specific class, so you don't need the `#if PLATFORM(JAVA)` in this file.

modules/javafx.web/src/test/java/test/javafx/scene/web/EventListenerLeakTest.java line 43:

> 41: import javafx.scene.web.WebEngine;
> 42: import javafx.scene.web.WebView;
> 43: import org.junit.AfterClass;

This import is no longer used.

modules/javafx.web/src/test/java/test/javafx/scene/web/EventListenerLeakTest.java line 53:

> 51: import org.junit.Before;
> 52: import org.w3c.dom.Document;
> 53: import org.w3c.dom.NodeList;

Minor: maybe move these up to the previous block (with the ordinary imports?

modules/javafx.web/src/test/java/test/javafx/scene/web/EventListenerLeakTest.java line 631:

> 629: 
> 630:         // Verify that all listeners have not been released
> 631:         Thread.sleep(100);

The sleep should be right before the call to `getClickCount` (as in other cases in the test).

modules/javafx.web/src/test/java/test/javafx/scene/web/EventListenerLeakTest.java line 637:

> 635:         });
> 636: 
> 637:         assertEquals("Click count", 1, listeners1.get(0).getClickCount());

You should add a comment that this check is testing that the immediately previous click does _not_ get delivered since the associated DOM node is not part of the page any more. This is why the count remains at 1 (from the first click on the original page).


Also, I think it would be useful here to clear the references to the listeners and WebView and make sure that the listener attached to the previously loaded page for this WebView gets released. Something like this as the final statements of the method:


        // Clear strong reference to listener and WebView
        listeners1.clear();
        webView1 = null;

        // Verify that there is no strong reference to the WebView
        assertNumActive("WebView", webViewRefs, 0);

        // Verify that no listeners are strongly held
        assertNumActive("MyListener", listenerRefs, 0);

tests/manual/web/EventListenerLeak.java line 1:

> 1: import javafx.application.Application;

You need to add a copyright header block.

tests/manual/web/EventListenerLeak.java line 265:

> 263:     public static void main(String[] args) {
> 264: 
> 265:         // NOTE: this timer is probably not needed

It turns out that this is needed to ensure GC, so you can remove the comment.

-------------

PR: https://git.openjdk.java.net/jfx/pull/799


More information about the openjfx-dev mailing list