Bug 12850 - Event listener leaks seen @ gmail.com
Summary: Event listener leaks seen @ gmail.com
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: WebCore JavaScript (show other bugs)
Version: 420+
Hardware: Mac OS X 10.4
: P1 Normal
Assignee: Feng Qian
URL: http://gmail.com
Keywords:
: 12853 (view as bug list)
Depends on:
Blocks:
 
Reported: 2007-02-21 21:53 PST by Feng Qian
Modified: 2007-02-26 06:42 PST (History)
1 user (show)

See Also:


Attachments
fix RemoveEventListener (3.94 KB, patch)
2007-02-22 00:32 PST, Feng Qian
mjs: review-
Details | Formatted Diff | Diff
test case (216 bytes, text/html)
2007-02-22 12:04 PST, Feng Qian
no flags Details
revised patch (5.36 KB, patch)
2007-02-22 13:02 PST, Feng Qian
no flags Details | Formatted Diff | Diff
fixed wrong path names in ChangeLog (5.36 KB, patch)
2007-02-22 13:11 PST, Feng Qian
no flags Details | Formatted Diff | Diff
revised patch again (9.06 KB, patch)
2007-02-22 14:06 PST, Feng Qian
darin: review-
Details | Formatted Diff | Diff
revised patch again again (8.71 KB, patch)
2007-02-22 14:44 PST, Feng Qian
mjs: review-
Details | Formatted Diff | Diff
patch again again again (12.07 KB, patch)
2007-02-22 19:08 PST, Feng Qian
mjs: review+
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Feng Qian 2007-02-21 21:53:06 PST
You need a gmail account. In KJS::Collector::collect(), before exiting the function, log heap.numLiveObjects. After visiting GMail, close the window, open a new one, there are still >10k live objects.

I looked further, there are protected objects (FunctionImp) that cannot be reclaimed. These protected FunctionImp also causes other objects to leak.
Comment 1 Feng Qian 2007-02-22 00:32:48 PST
Created attachment 13311 [details]
fix RemoveEventListener

I think the problem is in kjs_window.cpp, Window::getJSEventListener, which creates a JSEventListener object if it does not exist. The constructor of JSEventListener makes JSValue GC-protected.

In Window::RemoveEventListener and DOMEventTargetNode::RemoveEventListener, both calls getJSEventListener to check if a listener exists. This leaks both JSEventListener and the JavaScript event listener object.

I added a new function Window::hasJSEventListener for checking if a JSEventListener exists for a JS event listener object without creating a new one.

Before the change, run-safari and visit Gmail, then exist. Safari reports:
LEAK: 2102 Node
LEAK: 158688 KJS::Node

After applying the patch, no message reported.
Comment 2 Maciej Stachowiak 2007-02-22 01:04:26 PST
Comment on attachment 13311 [details]
fix RemoveEventListener

The change itself looks good. Thanks, this will is a really valuable fix! Technicalities:

- Needs a ChangeLog entry
- Needs a test case
- Per the coding style guidelines, the * should go next to the type name, although it is hard to tell from the current surrounding code.

r- for now for the technicalities.
Comment 3 Maciej Stachowiak 2007-02-22 01:24:29 PST
May also fix http://bugs.webkit.org/show_bug.cgi?id=12659
Comment 4 Feng Qian 2007-02-22 12:04:53 PST
Created attachment 13324 [details]
test case

It leaks several objects.
Comment 5 Feng Qian 2007-02-22 13:02:51 PST
Created attachment 13325 [details]
revised patch

Renamed hasJSEventListener to findJSEventListener.
Fixed some style issues and added an entry in ChangeLog.
Comment 6 Feng Qian 2007-02-22 13:10:02 PST
(In reply to comment #3)
> May also fix http://bugs.webkit.org/show_bug.cgi?id=12659
> 

I cannot find significant number of leaked JS object when visiting both sites even without fixing this bug. It might be fixed by an early patch that clearing helper object properties of window object.
Comment 7 Feng Qian 2007-02-22 13:11:35 PST
Created attachment 13326 [details]
fixed wrong path names in ChangeLog
Comment 8 Feng Qian 2007-02-22 14:01:27 PST
*** Bug 12853 has been marked as a duplicate of this bug. ***
Comment 9 Feng Qian 2007-02-22 14:06:12 PST
Created attachment 13327 [details]
revised patch again

added findJSUnprotectedEventListener and fixed use in JSXMLHttpRequest.cpp;
added a leak counter for EventListeners.
Comment 10 Darin Adler 2007-02-22 14:16:47 PST
Comment on attachment 13327 [details]
revised patch again

Great fix!

Is there no way to add a test for this? We really want a test for each bug fix if at all possible.

+            if (listener) {
                 node->removeEventListener(args[0]->toString(exec), listener,args[2]->toBoolean(exec));
+            }

Please don't add braces. Our coding style guidelines call for no braces on a single-line if statement. Also, adding those braces makes the patch longer for no good reason.

+JSEventListener *Window::findJSEventListener(JSValue* val, bool html) {

Braces that open a function call go on a separate line per our guidelines. The * goes next to the type name as per our style guidelines.

+  JSObject *object = static_cast<JSObject *>(val);

The * goes next to the type name as per our style guidelines.

-  case Window::RemoveEventListener:
+  case Window::RemoveEventListener: {
         if (!window->isSafeScript(exec))
             return jsUndefined();
-        if (JSEventListener *listener = Window::retrieveActive(exec)->getJSEventListener(args[1]))
+        JSEventListener *listener = Window::retrieveActive(exec)->findJSEventListener(args[1]);
+        if (listener) {
             if (Document *doc = frame->document())
                 doc->removeWindowEventListener(AtomicString(args[0]->toString(exec)), listener, args[2]->toBoolean(exec));
+        }
         return jsUndefined();
+  }

Why the gratuitous style change here? By putting the variable inside the if we avoid the need for braces around the entire case statement. There's no need to tweak the style while fixing this bug; I think it just makes the patch bigger and obscures the point of what you're fixing.

+    JSEventListener *findJSEventListener(JSValue*, bool html = false);

* goes next to the type name.

+    // JS Object.
+    JSUnprotectedEventListener *findJSUnprotectedEventListener(JSValue*, bool html = false);

Please put a blank lines between the comment and the declaration here as with the other functions.
Comment 11 Feng Qian 2007-02-22 14:29:21 PST
(In reply to comment #10)
> (From update of attachment 13327 [details] [edit])
> Great fix!
>

Thanks for your quick review. Here are some of my arguments:
 
> Is there no way to add a test for this? We really want a test for each bug fix
> if at all possible.

I attached a test case to in the bug report.
http://bugs.webkit.org/attachment.cgi?id=13324&action=view


> 
> +            if (listener) {
>                  node->removeEventListener(args[0]->toString(exec),
> listener,args[2]->toBoolean(exec));
> +            }
> 
> Please don't add braces. Our coding style guidelines call for no braces on a
> single-line if statement. Also, adding those braces makes the patch longer for
> no good reason.
> 
> +JSEventListener *Window::findJSEventListener(JSValue* val, bool html) {
> 
> Braces that open a function call go on a separate line per our guidelines. The
> * goes next to the type name as per our style guidelines.

I followed local style in the file. The local style also uses 2 spaces for indention.

> 
> +  JSObject *object = static_cast<JSObject *>(val);
> 
> The * goes next to the type name as per our style guidelines.
> 
> -  case Window::RemoveEventListener:
> +  case Window::RemoveEventListener: {
>          if (!window->isSafeScript(exec))
>              return jsUndefined();
> -        if (JSEventListener *listener =
> Window::retrieveActive(exec)->getJSEventListener(args[1]))
> +        JSEventListener *listener =
> Window::retrieveActive(exec)->findJSEventListener(args[1]);
> +        if (listener) {
>              if (Document *doc = frame->document())
>                 
> doc->removeWindowEventListener(AtomicString(args[0]->toString(exec)), listener,
> args[2]->toBoolean(exec));
> +        }
>          return jsUndefined();
> +  }
> 
> Why the gratuitous style change here? By putting the variable inside the if we
> avoid the need for braces around the entire case statement. There's no need to
> tweak the style while fixing this bug; I think it just makes the patch bigger
> and obscures the point of what you're fixing.
> 
> +    JSEventListener *findJSEventListener(JSValue*, bool html = false);
> 
> * goes next to the type name.
> 
> +    // JS Object.
> +    JSUnprotectedEventListener *findJSUnprotectedEventListener(JSValue*, bool
> html = false);
> 
> Please put a blank lines between the comment and the declaration here as with
> the other functions.
> 

Comment 12 Feng Qian 2007-02-22 14:44:03 PST
Created attachment 13328 [details]
revised patch again again

In new functions I added, I follow style guide: 4-space indention and * following type name. 

In existing functions, I follow the local rules.
Comment 13 Maciej Stachowiak 2007-02-22 18:34:38 PST
Comment on attachment 13328 [details]
revised patch again again

The right way to add a test case is to included it in the patch, in the LayoutTests directory. In this case, LayoutTests/fast/events would be the appropriate directory. This is so the person committing doesn't have to do extra work applying the patch.

Also: I think the comments on the findJSEventListener and related methods are excessive. Can we get these down to one or two lines instead of having whole paragraphs in the header?

Better yet, how about renaming the methods to make behavior more clear from the name? getJSEventListener could be renamed to findOrCreateJSEventListener for instance.

r- for lack of test case. 

Please also consider my feedback about the large comments. We tend to avoid those in WebKit because they can make it harder to read the code. Code-wise though, the patch is fine to land once a test case is added to the patch.
Comment 14 Feng Qian 2007-02-22 19:08:51 PST
Created attachment 13336 [details]
patch again again again

Your comments are well taken.
Comment 15 Maciej Stachowiak 2007-02-22 19:17:05 PST
Comment on attachment 13336 [details]
patch again again again

r=me
Comment 16 Sam Weinig 2007-02-22 20:01:11 PST
Landed in r19820.