[whatwg/dom] Owned weak event listener to prevent memory leak (using EventListenerOptions) (#243)

## The problem
In order to implement a UI component such as popup menu or tooltip, it is usually necessary to add an event listener on an ancestor node (e.g. `document`) to hide/remove the component when the user click/touch outside of the component. However, it is a significant burden to make sure the event listener on the ancestor node is removed when the component is to be hidden/removed. For example, if some uncareful code removed one of the ancestor nodes of the component from the tree without also remove the listener on `document`, then the listener is leaked.

It would be nice if the event listener on the ancestor node is automatically removed by UA after the  component is removed.

## Prior art: weak event listener
There is no concept of weak event listener in DOM, HTML or ECMAScript standards yet, though [ActionScript 3](http://blog.gskinner.com/archives/2006/07/as3_weakly_refe.html) and [.Net](http://www.codeproject.com/Articles/738109/The-NET-weak-event-pattern-in-Csharp) already implemented it to prevent leak of listener. In short, a weak event listener is an event listener object that is weakly referenced by the event source and/or the runtime. If the weak event listener is no longer strongly referenced by user's code except the the event source, it is legitimate to be removed from the event source and GC'ed.

Weak event listener can be useful to solve the problem mentioned above: we can add an event listener weakly on the ancestor node and let the component reference the event listener strongly. After the component is removed and GC'ed, the event listener will also be removed from the ancestor node.

However, weak event listener exposes indeterministic behavior of GC to user's code: weak event listener is still working after it is no longer strongly referenced by user's code but not GC'ed yet.

## Proposed solution: owned weak event listener
This solution is described as follow:
* An event listener added to a DOM Node has an optional owner, which is also a DOM Node. (Currently I think non-DOM events are not difficult to manage so are out of scope).
* If an event listener has an owner, and the owner is not in the active document, it will not receive any event; if the owner is re-inserted into the the active document, the listener may receive events again.
* If the event listener and its owner are both no longer strongly referenced by user's code, the UA may remove the listener from its event target and GC the listener and its owner.

To implement this feature, the owner Node should hold strong references to its owned event listeners, and the event target Node may hold weak references to the turple of event listener and its owner.

Note that this feature is deterministic: an event listener won't be GC'ed before its owner can be GC'd; The the event listener is turned off when its owner is removed from the document, not when it is GC'ed.

The API looks like:
```webidl
dictionary AddEventListenerOptions: EventListenerOptions {
  //...
  Node owner = null;
};
```

To solve the problem mentioned at the beginning, just let the owner be the component:
```js
function showPopupComponent(popup, x, y) { // popup: Element
  popup.style.position = 'absolute';
  popup.style.left = x + 'px';
  popup.style.top = y + 'px';
  document.body.appendChild(popup);
  // no need to care about removing this event listener,
  // it will stop to work after the popup is removed and will eventually be GC'ed
  document.addEventListener('click', ev => {
      if (!popup.contains(ev.target)) {
        document.body.removeChild(popup);
      }
    },
    { owner: popup }
  );
}
```


---
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/dom/issues/243

Received on Monday, 9 May 2016 06:49:49 UTC