001 
002 /*
003  *  JScripter Emulation 1.0 - To Script Java
004  *  Copyright (C) 2008-2011  J.J.Liu<jianjunliu@126.com> <http://www.jscripter.org>
005  *  
006  *  This program is free software: you can redistribute it and/or modify
007  *  it under the terms of the GNU Affero General Public License as published by
008  *  the Free Software Foundation, either version 3 of the License, or
009  *  (at your option) any later version.
010  *  
011  *  This program is distributed in the hope that it will be useful,
012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014  *  GNU Affero General Public License for more details.
015  *  
016  *  You should have received a copy of the GNU Affero General Public License
017  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
018  */
019 
020 package org.jscripter.emu.gc;
021 
022 import js.ArrayLike;
023 import js.Index;
024 import js.Js;
025 import js.ObjectLike;
026 import js.Vars;
027 import js.core.JsFunction;
028 import js.dom.EventTarget;
029 import js.user.JsDOMException;
030 import js.user.JsDocument;
031 import js.user.JsDocumentFragment;
032 import js.user.JsEvent;
033 import js.user.JsHTMLElement;
034 import js.user.JsNode;
035 import js.user.JsWindow;
036 import jsx.client.Document;
037 import jsx.core.ArrayLikes;
038 import jsx.core.ObjectLikes;
039 import jsx.dom.Nodes;
040 
041 /**
042  * <p>An <tt>internal</tt> base class of DOM object visitors emulating garbage collection based 
043  * finalization in JavaScript for DOM implementations.</p>
044  * <p>This class is only used internally by JS re-compiler implementations.</p>
045  * 
046  * @author <a href="mailto:jianjunliu@126.com">J.J.Liu (Jianjun Liu)</a> at <a href="http://www.jscripter.org" target="_blank">http://www.jscripter.org</a>
047  * 
048  * @javascript This class is only loaded and resolved by re-compiler implementations.
049  */
050 public class GCDOM extends GC
051 {
052     /**
053      * <p>Internally constructs an object visitor of this type.</p>
054      * @since 1.0
055      * @javascript Re-compilers must report error on end-users directly using this constructor.
056      */
057     protected GCDOM() {}
058 
059     private final static Index<Integer> COUNT = new Index<Integer>("$GCDOM.COUNT");
060 
061     /**
062      * <p>Internally visits an object.</p>
063      * @param o An object to visit.
064      * @since 1.0
065      * @javascript Re-compilers must report error on end-users directly using this method.
066      */
067     @Override
068     protected void visit(ObjectLike o) {
069         if (isRelevant(o) && nodeType(o) == 1) {
070             domInc((JsNode)o);
071         }
072     }
073 
074     static {
075         new GCDOM().visit(Document.body.var());
076     }
077 
078     /**
079      * <p>Internally gets the type of a given node object.</p>
080      * @param o The given node object. 
081      * @return The type of the node of 0 for none.
082      * @since 1.0
083      * @javascript Re-compilers must report error on end-users directly using this method.
084      */
085     protected static final int nodeType(ObjectLike o) {
086         Number t = Nodes.nodeType((JsNode)o);
087         if (not(t)) {
088             return 0;
089         }
090         return t.intValue();
091     }
092 
093     /**
094      * <p>Internally determines whether a given object is a node.</p>
095      * @param o The given object. 
096      * @return <tt>true</tt> if the object is a node; <tt>false</tt>, otherwise.
097      * @since 1.0
098      * @javascript Re-compilers must report error on end-users directly using this method.
099      */
100     protected static final boolean isNode(ObjectLike o) {
101         int t = nodeType(o);
102         return Js.not(or(Js.lt(t, 1), Js.gt(t, 11)));
103     }
104 
105     /**
106      * <p>Internally determines whether a given object is an element but not a 
107      * finalization relevant node.</p>
108      * @param o The given object. 
109      * @return <tt>true</tt> if the object is an element; <tt>false</tt>, otherwise.
110      * @since 1.0
111      * @javascript Re-compilers must report error on end-users directly using this method.
112      */
113     protected static final boolean isElement(ObjectLike o) {
114         return Js.not(or(Js.lt(ObjectLikes.get(o, COUNT), 1), nodeType(o) != 1));
115     }
116 
117     private static JsNode domInc(JsNode o) {
118         if (isObjectLike(o)) {
119             ObjectLikes.inc(o, COUNT);
120             ObjectLikes.inc(o, REFS);
121         }
122         return o;
123     }
124 
125     private static JsNode domDec(JsNode o) {
126         if (isObjectLike(o)) {
127             ObjectLikes.dec(o, COUNT);
128             ObjectLikes.dec(o, REFS);
129         }
130         return o;
131     }
132 
133     /**
134      * <p>Internally adds a node to the document tree by appending it to the {@link JsNode#childNodes} 
135      * array of this node with finalization emulation.</p>
136      * <p>This method adds the node <tt>c</tt> to the document, inserting it as 
137      * the last child of this node. If <tt>c</tt> is already in the document tree, 
138      * it is removed from the tree and then reinserted at its new location. If <tt>c</tt> 
139      * is a {@link JsDocumentFragment} node, it is not inserted itself; instead, all its 
140      * children are appended, in order, to the end of this node's {@link JsNode#childNodes} 
141      * array. Note that a node from (or created by) one document cannot be inserted into 
142      * a different document. That is, the {@link JsNode#ownerDocument} property of <tt>c</tt> 
143      * must be the same as the {@link JsNode#ownerDocument} property of this node.</p>
144      * @param p The current node. 
145      * @param c The node to be inserted into the document. If the node is of 
146      * {@link JsDocumentFragment}, it is not directly inserted, but each of its children 
147      * are.
148      * @return The node that was added.
149      * @throws RuntimeException JavaScript throws a {@link JsDOMException} object with 
150      * the {@link JsDOMException#code} property of the value {@link JsDOMException#HIERARCHY_REQUEST_ERR} 
151      * if the node does not allow children, it does not allow children of the specified 
152      * type, or <tt>c</tt> is an ancestor of this node (or is this node itself), 
153      * the value {@link JsDOMException#WRONG_DOCUMENT_ERR} if the {@link JsNode#ownerDocument} 
154      * property of <tt>c</tt> is not the same as the {@link JsNode#ownerDocument} 
155      * property of this node, or the value {@link JsDOMException#NO_MODIFICATION_ALLOWED_ERR} 
156      * if the node is read-only and does not allow children to be appended, or the node 
157      * being appended is already part of the document tree, and its parent is read-only 
158      * and does not allow children to be removed. See {@link Js#err(Object)} for JS 
159      * Simulation.
160      * @see #removeChild(JsNode, JsNode)
161      * @see jsx.dom.Nodes#appendChild(JsNode, JsNode)
162      * @see JsNode#appendChild(JsNode)
163      * @since 1.0
164      * @javascript Re-compilers must report error on end-users directly using this method.
165      */
166     public static final JsNode appendChild(JsNode p, JsNode c) {
167         Nodes.appendChild(p, c);
168         return domInc(Nodes.lastChild(p));
169     }
170 
171     /**
172      * <p>Internally removes and returns the specified child node from the document tree
173      * with finalization emulation.</p>
174      * <p>This method removes the specified child from the {@link JsNode#childNodes} array of 
175      * this node. It is an error to call this method with a node that is not a child. 
176      * This method returns the <tt>c</tt> node after removing it. <tt>c</tt> 
177      * continues to be a valid node and may be reinserted into the document later.</p>
178      * @param p The current node. 
179      * @param c The child node to remove. 
180      * @return The node that was removed.
181      * @throws RuntimeException JavaScript throws a {@link JsDOMException} object with 
182      * the {@link JsDOMException#code} property of the value {@link JsDOMException#NO_MODIFICATION_ALLOWED_ERR} 
183      * if the node is read-only and does not allow children to be removed, or the value 
184      * {@link JsDOMException#NOT_FOUND_ERR} if <tt>c</tt> is not a child of this 
185      * node. See {@link Js#err(Object)} for JS Simulation.
186      * @see #appendChild(JsNode, JsNode)
187      * @see jsx.dom.Nodes#removeChild(JsNode, JsNode)
188      * @see JsNode#removeChild(JsNode)
189      * @since 1.0
190      * @javascript Re-compilers must report error on end-users directly using this method.
191      */
192     public static final JsNode removeChild(JsNode p, JsNode c) {
193         return domDec(Nodes.removeChild(p, c));
194     }
195 
196     /**
197      * <p>Internally inserts the given HTML text into the element at the location with
198      * finalization emulation.</p>
199      * <p>This method is IE-specific.</p>
200      * @param node The current node.
201      * @param where A string that specifies where to insert the HTML text, using one of
202      * the following values: 
203      * <ul>
204      * <li>"beforeBegin": Inserts <tt>html</tt> immediately before the object.</li>
205      * <li>"afterBegin": Inserts <tt>html</tt> after the start of the object but before all other 
206      * content in the object.</li>
207      * <li>"beforeEnd": Inserts <tt>html</tt> immediately before the end of the object but after 
208      * all other content in the object.</li>
209      * <li>"afterEnd": Inserts <tt>html</tt> immediately after the end of the object.</li>
210      * </ul>
211      * @param html A string that specifies the HTML text to insert. The string can be a 
212      * combination of text and HTML tags. This must be well-formed, valid HTML or this 
213      * method will fail.
214      * @see jsx.dom.Nodes#insertAdjacentHTML(JsNode, String, String)
215      * @see JsNode#insertAdjacentHTML(String, String)
216      * @since 1.0
217      * @javascript Re-compilers must report error on end-users directly using this method.
218      */
219     public static final void insertAdjacentHTML(JsNode node, String where, String html) {
220         Nodes.insertAdjacentHTML(node, where, html);
221         switch (Js.cases().add("BeforeBegin")
222                           .add("AfterEnd")
223                           .add("AfterBegin")
224                           .add("BeforeEnd").indexOf(where)) {
225             case 0:
226                 domInc(Nodes.previousSibling(node));
227                 break;
228             case 1:
229                 domInc(Nodes.nextSibling(node));
230                 break;
231             case 2:
232                 domInc(Nodes.firstChild(node));
233                 break;
234             case 3:
235                 domInc(Nodes.lastChild(node));
236                 break;
237         }
238     }
239 
240     /**
241      * <p>Internally sets {@link JsHTMLElement#innerHTML} of a given element with
242      * finalization emulation.</p>
243      * @param node The given node. 
244      * @param html The new HTML for the element. 
245      * @return The property value.
246      * @since 1.0
247      * @javascript Re-compilers must report error on end-users directly using this method.
248      */
249     public final static String innerHTML(JsNode node, String html) {
250         String ret = Nodes.innerHTML(node, html);
251         JsNode n = Nodes.firstChild(node, 1);
252         if (node == Document.body.var() || ObjectLikes.get(node, COUNT) > 0) {
253             ObjectLikes.inc(n, COUNT);
254         }
255         ObjectLikes.inc(n, REFS);
256         return ret;
257     }
258 
259     /**
260      * <p>Internally inserts a node into the current document tree immediately before the specified 
261      * child node with finalization emulation.</p>
262      * <p>This method inserts the node <tt>newChild</tt> into the document tree as a 
263      * child of this node. The new node is positioned within this node's {@link JsNode#childNodes} 
264      * array so that it comes immediately before the <tt>refChild</tt> node. If <tt>refChild</tt> 
265      * is <tt>null</tt>, <tt>newChild</tt> is inserted at the end of {@link JsNode#childNodes}, 
266      * just as with the {@link JsNode#appendChild(JsNode)} method. Note that it is illegal to 
267      * call this method with a <tt>refChild</tt> that is not a child of this node.</p>
268      * <p>If the node being inserted is already in the tree, it is removed and 
269      * reinserted at its new location.</p>
270      * <p>If <tt>newChild</tt> is a {@link JsDocumentFragment} node, it is not inserted 
271      * itself; instead, each of its children is inserted, in order, at the specified 
272      * location.</p>
273      * @param node The current node. 
274      * @param newChild The node to be inserted into the document. If the node is of 
275      * {@link JsDocumentFragment}, it is not directly inserted, but each of its children 
276      * are.
277      * @param refChild The child of this node before which <tt>newChild</tt> is to be 
278      * inserted. If this argument is <tt>null</tt>, <tt>newChild</tt> is inserted as 
279      * the last child of this node. 
280      * @return The node that was inserted.
281      * @throws RuntimeException JavaScript throws a {@link JsDOMException} object with 
282      * the {@link JsDOMException#code} property of the value {@link JsDOMException#HIERARCHY_REQUEST_ERR} 
283      * if the node does not allow children, it does not allow children of the specified 
284      * type, or <tt>newChild</tt> is an ancestor of this node (or is this node itself), 
285      * the value {@link JsDOMException#WRONG_DOCUMENT_ERR} if the {@link JsNode#ownerDocument} 
286      * property of <tt>newChild</tt> is not the same as the {@link JsNode#ownerDocument} 
287      * property of this node, the value {@link JsDOMException#NO_MODIFICATION_ALLOWED_ERR} 
288      * if the node is read-only and does not allow insertion, or the parent of <tt>newChild</tt> 
289      * is read-only and does not allow deletion, or the value {@link JsDOMException#NOT_FOUND_ERR} 
290      * if <tt>refChild</tt> is not a child of this node. See {@link Js#err(Object)} for JS 
291      * Simulation.
292      * @since 1.0
293      * @see jsx.dom.Nodes#insertBefore(JsNode, JsNode, JsNode)
294      * @see JsNode#insertBefore(JsNode, JsNode)
295      * @javascript Re-compilers must report error on end-users directly using this method.
296      */
297     public static final JsNode insertBefore(JsNode node, JsNode newChild, JsNode refChild) {
298         int t = ObjectLikes.get(newChild, COUNT);
299         JsNode ret = Nodes.insertBefore(node, newChild, refChild);
300         if (Js.not(t)) {
301             domInc(ret);
302         }
303         ObjectLikes.inc(ret, REFS);
304         return ret;
305     }
306 
307     /**
308      * <p>Internally removes and returns the specified child node from the document tree, replacing 
309      * it with another node with finalization emulation.</p>
310      * <p>This method replaces one node of the document tree with another. <tt>oldChild</tt> 
311      * is the node to be replaced and must be a child of this node. <tt>newChild</tt> is 
312      * the node that takes its place in the {@link JsNode#childNodes} array of this node.</p>
313      * <p>If <tt>newChild</tt> is already part of the document, it is first removed from 
314      * the document before being reinserted at its new position.</p>
315      * <p>If <tt>newChild</tt> is a {@link JsDocumentFragment} node, it is not inserted 
316      * itself; instead, each of its children is inserted, in order, at the position 
317      * formerly occupied by <tt>oldChild</tt>.</p>
318      * @param node The current node.
319      * @param newChild The replacement node. 
320      * @param oldChild The node to be replaced. 
321      * @return The node that was removed from the document and replaced.
322      * @throws RuntimeException JavaScript throws a {@link JsDOMException} object with 
323      * the {@link JsDOMException#code} property of the value {@link JsDOMException#HIERARCHY_REQUEST_ERR} 
324      * if the node does not allow children, it does not allow children of the specified 
325      * type, or <tt>newChild</tt> is an ancestor of this node (or is this node itself), 
326      * the value {@link JsDOMException#WRONG_DOCUMENT_ERR} if <tt>newChild</tt> and this 
327      * node have different values for {@link JsNode#ownerDocument} property, the value {@link JsDOMException#NO_MODIFICATION_ALLOWED_ERR} 
328      * if the node is read-only and does not allow replacement, or <tt>newChild</tt> is 
329      * the child of a node that does not allow removals, or the value {@link JsDOMException#NOT_FOUND_ERR} 
330      * if <tt>oldChild</tt> is not a child of this node. See {@link Js#err(Object)} for JS 
331      * Simulation.
332      * @see JsNode#replaceChild(JsNode, JsNode)
333      * @since 1.0
334      * @javascript Re-compilers must report error on end-users directly using this method.
335      */
336     public static final JsNode replaceChild(JsNode node, JsNode newChild, JsNode oldChild) {
337         int t = ObjectLikes.get(newChild, COUNT);
338         JsNode ret = Nodes.replaceChild(node, newChild, oldChild);
339         if (Js.not(t)) {
340             domInc(newChild);
341             domDec(ret);
342         }
343         return ret;
344     }
345 
346     private final static Index<ArrayLike<JsFunction<?>>> GC_LISTENERS = new Index<ArrayLike<JsFunction<?>>>("$GC.LIST");
347 
348     private static final void addListener(EventTarget et, JsFunction<?> listener) {
349         ArrayLike<JsFunction<?>> listeners = ((ObjectLike)et).var(GC_LISTENERS);
350         if (Js.not(listeners)) {
351             listeners = new Vars<JsFunction<?>>().var();
352             ((ObjectLike)et).var(GC_LISTENERS, inc(listeners));
353         }
354         ArrayLikes.push(listeners, inc(listener));
355     }
356 
357     private static final void removeListener(EventTarget et, JsFunction<?> listener) {
358         ArrayLike<JsFunction<?>> listeners = ((ObjectLike)et).var(GC_LISTENERS);
359         if (Js.be(listeners)) {
360             if (ArrayLikes.remove(listeners, listener) != -1) {
361                 dec(listener);
362             }
363         }
364     }
365 
366     /**
367      * <p>Internally adds an event handler function to the set of event handlers for this element
368      * with finalization emulation support. This is a DOM-standard method supported by all modern 
369      * browsers except IE.</p>
370      * <p>This method adds the specified event <tt>listener</tt> function to the set of 
371      * listeners registered on this node to handle events of the specified <tt>type</tt>. 
372      * If <tt>useCapture</tt> is <tt>true</tt>, the <tt>listener</tt> is registered as 
373      * a capturing event listener. If <tt>useCapture</tt> is <tt>false</tt>, it is 
374      * registered as a normal event listener.</p>
375      * <p>This method may be called multiple times to register multiple event handlers 
376      * for the same type of event on the same node. Note, however, that the DOM 
377      * Specification makes no guarantees about the order in which multiple event handlers 
378      * are invoked.</p>
379      * <p>If the same event listener function is registered twice on the same node with 
380      * the same <tt>type</tt> and <tt>useCapture</tt> arguments, the second registration 
381      * is simply ignored. If a new event listener is registered on this node while an 
382      * event is being handled at this node, the new event listener is not invoked for 
383      * that event.</p>
384      * <p>When a node is duplicated with {@link JsNode#cloneNode(Boolean)} or {@link JsDocument#importNode(JsNode, Boolean)}, 
385      * the event listeners registered for the original node are not copied.</p>
386      * <p>The same method is also defined by, and works analogously on, the {@link JsDocument} 
387      * and {@link JsWindow} objects.</p>
388      * @param et The current event target.
389      * @param type The type of event for which the event listener is to be invoked.
390      * @param listener The event listener function that is invoked when an event of the 
391      * specified type is dispatched to this node. When invoked, the listener function 
392      * is passed an {@link JsEvent} object and is invoked as a method of the node on 
393      * which it is registered.
394      * @param useCapture If <tt>true</tt>, the specified <tt>listener</tt> is to be 
395      * invoked only during the capturing phase of event propagation. The more common 
396      * value of <tt>false</tt> means that the <tt>listener</tt> is not invoked during 
397      * the capturing phase but instead is invoked when this node is the actual event 
398      * target or when the event bubbles up to this node from its original target.
399      * @since 1.0
400      * @see #attachEvent(EventTarget, String, JsFunction)
401      * @see #removeEventListener(EventTarget, String, JsFunction, Boolean)
402      * @see EventTarget#addEventListener(String, JsFunction, Boolean)
403      * @see JsDocument#addEventListener(String, JsFunction, Boolean)
404      * @see JsWindow#addEventListener(String, JsFunction, Boolean)
405      * @javascript Re-compilers must report error on end-users directly using this method.
406      */
407     public static final void addEventListener(EventTarget et, String type, JsFunction<?> listener, Boolean useCapture) {
408         et.addEventListener(type, listener, useCapture);
409         addListener(et, listener);
410     }
411 
412     /**
413      * <p>Internally removes an event handler function from the set of handlers for this element
414      * with finalization emulation support. This is a standard DOM method implemented by all modern 
415      * browsers except IE.</p>
416      * <p>This method removes the specified event <tt>listener</tt> function. The <tt>type</tt> 
417      * and <tt>useCapture</tt> arguments must be the same as they are in the 
418      * corresponding call to {@link #addEventListener(EventTarget, String, JsFunction, Boolean)}. If 
419      * no event listener is found that matches the specified arguments, this method does 
420      * nothing.</p>
421      * <p>Once an event <tt>listener</tt> function has been removed by this method, it 
422      * will no longer be invoked for the specified <tt>type</tt> of event on this node. 
423      * This is true even if the event <tt>listener</tt> is removed by another event 
424      * listener registered for the same type of event on the same node.</p>
425      * <p>The same method is also defined by, and works analogously on, the {@link JsDocument} 
426      * and {@link JsWindow} objects</p>
427      * @param et The current event target.
428      * @param type The type of event for which the event listener is to be deleted.
429      * @param listener The event listener function that is to be removed.
430      * @param useCapture <tt>true</tt> if a capturing event listener is to be removed; 
431      * <tt>false</tt> if a normal event listener is to be removed.
432      * @since 1.0
433      * @see #detachEvent(EventTarget, String, JsFunction)
434      * @see #addEventListener(EventTarget, String, JsFunction, Boolean)
435      * @see EventTarget#removeEventListener(String, JsFunction, Boolean)
436      * @see JsDocument#removeEventListener(String, JsFunction, Boolean)
437      * @see JsWindow#removeEventListener(String, JsFunction, Boolean)
438      * @javascript Re-compilers must report error on end-users directly using this method.
439      */
440     public static final void removeEventListener(EventTarget et, String type, JsFunction<?> listener, Boolean useCapture) {
441         et.removeEventListener(type, listener, useCapture);
442         removeListener(et, listener);
443     }
444 
445     /**
446      * <p>Internally adds an event handler function to the set of handlers for this element
447      * with finalization emulation support. This is the IE-specific alternative to 
448      * {@link #addEventListener(EventTarget, String, JsFunction, Boolean)}.</p>
449      * <p>This method is an IE-specific event registration method. It serves the same 
450      * purpose as the standard {@link #addEventListener(EventTarget, String, JsFunction, Boolean)} 
451      * method, which IE does not support, but is different from that function in several 
452      * important ways:
453      * <ul>
454      * <li>Since the IE event model does not support event capturing, this method and 
455      * {@link #detachEvent(EventTarget, String, JsFunction)} expect only two arguments: the event 
456      * type and the handler function.</li>
457      * <li>The event handler names passed to the IE methods should include the "on" 
458      * prefix.</li>
459      * <li>Functions registered with this method are invoked with no {@link JsEvent} 
460      * {@link JsWindow#event} property of 
461      * {@link JsWindow#window} object.</li>
462      * <li>Functions registered with this method are invoked as global functions, rather 
463      * than as methods of the node on which the event occurred. That is, when an event 
464      * handler registered with this method executes, the <tt>this</tt> keyword refers to 
465      * {@link JsWindow#window} object, not to the event's target node.</li>
466      * <li>This method allows the same event handler function to be registered more than 
467      * once. When an event of the specified type occurs, the registered function is 
468      * invoked as many times as it is registered.</li>
469      * </ul>
470      * </p>
471      * <p>The same method is also defined by, and works analogously on, the {@link JsDocument} 
472      * and {@link JsWindow} objects.</p>
473      * @param et The current event target.
474      * @param type The type of event for which the event listener is to be invoked, with 
475      * a leading "on" prefix.
476      * @param listener The event listener function that is invoked when an event of the 
477      * specified type is dispatched to this node. This function is not passed any 
478      * {@link JsWindow#event} 
479      * property of the {@link JsWindow} object.
480      * @since 1.0
481      * @see #addEventListener(EventTarget, String, JsFunction, Boolean)
482      * @see #detachEvent(EventTarget, String, JsFunction)
483      * @see EventTarget#attachEvent(String, JsFunction)
484      * @see JsDocument#attachEvent(String, JsFunction)
485      * @see JsWindow#attachEvent(String, JsFunction)
486      * @javascript Re-compilers must report error on end-users directly using this method.
487      */
488     public static final void attachEvent(EventTarget et, String type, JsFunction<?> listener) {
489         et.attachEvent(type, listener);
490         addListener(et, listener);
491     }
492 
493     /**
494      * <p>Internally removes an event handler function from this element with finalization 
495      * emulation support. This is the IE-specific alternative to the standard 
496      * {@link #removeEventListener(EventTarget, String, JsFunction, Boolean)} method.</p>
497      * <p>This method undoes the event handler function registration performed by the 
498      * {@link #attachEvent(EventTarget, String, JsFunction)} method. It is the IE-specific analog to 
499      * the standard {@link #removeEventListener(EventTarget, String, JsFunction, Boolean)}. To remove 
500      * an event handler function for a node, simply invoke this method with the same 
501      * arguments you originally passed to {@link #attachEvent(EventTarget, String, JsFunction)}.</p>
502      * <p>The same method is also defined by, and works analogously on, the {@link JsDocument} 
503      * and {@link JsWindow} objects</p>
504      * @param et The current event target.
505      * @param type The type of event for which the event listener is to be invoked, with 
506      * a leading "on" prefix.
507      * @param listener The event listener function that is to be removed.
508      * @since 1.0
509      * @see #removeEventListener(EventTarget, String, JsFunction, Boolean)
510      * @see #attachEvent(EventTarget, String, JsFunction)
511      * @see EventTarget#detachEvent(String, JsFunction)
512      * @see JsDocument#detachEvent(String, JsFunction)
513      * @see JsWindow#detachEvent(String, JsFunction)
514      * @javascript Re-compilers must report error on end-users directly using this method.
515      */
516     public static final void detachEvent(EventTarget et, String type, JsFunction<?> listener) {
517         et.detachEvent(type, listener);
518         removeListener(et, listener);
519     }
520 }