From 84951f766fa5783d3a11e1149f7141c98d5d52a9 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Tue, 7 Aug 2018 14:25:59 -0400 Subject: [PATCH] Rewrite event handlers section for more rigor and correctness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move the "When an event handler H of an EventTarget object T is first set to a non-null value" steps into a dedicated "activate an event handler" algorithm. Create a corresponding "deactivate an event handler" algorithm, called in the circumstances outlined in #3836, thereby fixing #3836. * Define "When an event handler content attribute is set" and "When an event handler content attribute is removed" steps rigorously using the attribute change steps hook in DOM. Care is taken to clarify certain edge cases, such as calling removeAttribute() on a nonexistent event handler content attribute but with a corresponding event handler IDL attribute defined, and calling setAttribute() an attribute with the same value as it currently possesses but with the event handler deactivated (exhibit B in #3836). * Formalize multiple definitions: * event handler as a struct containing a value and an event listener * location of event handlers and the concept of event handler target * rigorous set of steps on how to obtain the target of an event handler through the "determine the target of an event handler" algorithm. The latter algorithm supersedes the handwavy and inconsistently-applied "If an event handler IDL attribute exposes an event handler of an object that doesn't exist" steps. * storage of event handlers in the event handler map * More descriptive variable names: H → eventHandler, T → eventTarget, and E → event, in various algorithms. Tests: https://github.com/web-platform-tests/wpt/pull/12201 Follow-up issue to improve clarity by breaking this up into smaller sections: #3861. --- source | 484 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 357 insertions(+), 127 deletions(-) diff --git a/source b/source index 010d8e1072b..86b8f5903bb 100644 --- a/source +++ b/source @@ -2536,6 +2536,8 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute position variable
  • skip ASCII whitespace
  • The ordered map data structure and the associated definitions for + value, + entry, exists, getting the value of an entry, and setting the value of an entry
  • @@ -3194,11 +3196,12 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
  • The find flattened slotables algorithm
  • The assign a slot algorithm
  • The pre-insert, insert, append, replace, replace all, remove, and adopt algorithms for nodes
  • -
  • The change, append, remove, replace, and set value algorithms for attributes
  • The insertion steps, removing steps, adopting steps, and - child text content change steps hooks
  • + child text content change steps hooks for elements +
  • The change, append, remove, replace, and set value algorithms for attributes
  • +
  • The attribute change steps hook for attributes
  • The attribute list concept
  • The data of a text node
  • The child text content of a node
  • @@ -3222,7 +3225,8 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
  • initEvent() method
  • add an event listener
  • addEventListener() method
  • -
  • remove all event listeners
  • +
  • The remove an event listener and + remove all event listeners algorithms
  • EventListener callback interface
  • The type of an event
  • An event listener and its @@ -15258,12 +15262,10 @@ interface HTMLBodyElement : HTMLElement {}; of the event handlers of the Window object. It also mirrors their event handler IDL attributes.

    -

    The onblur, onerror, - onfocus, onload, onresize, and onscroll - event handlers of the Window object, exposed on the body - element, replace the generic event handlers with the same names normally supported by - HTML elements.

    +

    The event handlers of the Window object named by the + Window-reflecting body element event handler set, exposed on the + body element, replace the generic event handlers with the same names + normally supported by HTML elements.

    Thus, for example, a bubbling error event dispatched on a child of the body element of a Document would first @@ -89610,22 +89612,29 @@ dictionary PromiseRejectionEventInit : EventInit {

    Event handlers
    - -

    Many objects can have event handlers - specified. These act as non-capture event listeners for the object on which they are specified. -

    + specified. These act as non-capture event listeners for the + object on which they are specified.

    -

    An event handler has a name, which always starts with - "on" and is followed by the name of the event for which it is intended.

    +

    An event handler is a struct with two items: -

    An event handler has a value, which is either null, or is - a callback object, or is an internal raw uncompiled handler. - The EventHandler callback function type describes how this is exposed to scripts. - Initially, an event handler's value must be set - to null.

    +
      +
    • a value, which is either null, or a callback object, or an internal raw + uncompiled handler. The EventHandler callback function type describes + how this is exposed to scripts. Initially, an event + handler's value must be set to + null.

    • + +
    • a listener, which is either null or an + event listener responsible for running the event handler + processing algorithm. Initially, an event handler's listener must be + set to null.

    • +
    -

    Event handlers are exposed in one of two ways.

    +

    Event handlers are exposed in two ways.

    The first way, common to all event handlers, is as an event handler IDL attribute.

    @@ -89634,39 +89643,146 @@ dictionary PromiseRejectionEventInit : EventInit { attribute. Event handlers on HTML elements and some of the event handlers on Window objects are exposed in this way.

    +

    For both of these two ways, the event handler is exposed + through a name, which is a string that always starts with + "on" and is followed by the name of the event for which the handler is + intended.

    + +
    + +

    Most of the time, the object that exposes an event handler + is the same as the object on which the corresponding event listener is added. + However, the body and frameset elements expose several event + handlers that act upon the element's Window object, if one exists. In either + case, we call the object an event handler acts upon the target of that event + handler.

    +
    +

    To determine the target of an event + handler, given an EventTarget object eventTarget on which the event handler is exposed, and an event handler name + name, the following steps are taken:

    + +
      +
    1. If eventTarget is not a body element or a frameset + element, then return eventTarget.

    2. + +
    3. If name is not the name of an attribute member of the + WindowEventHandlers interface mixin and the Window-reflecting + body element event handler set does not contain + name, then return eventTarget.

    4. + +
    5. +

      If eventTarget's node document is not an active + document, then return null.

      + +

      This could happen if this object is a body element without + a corresponding Window object, for example.

      + +

      This check does not necessarily prevent body and + frameset elements that are not the body element of their node + document from reaching the next step. In particular, a body element created + in an active document (perhaps with document.createElement()) but not + connected will also have its corresponding Window object as the target of several event handlers exposed + through it.

      +
    6. + +
    7. Return eventTarget's node document's relevant global object.

    8. +
    + +
    + +

    Each EventTarget object that has one or more event handlers specified + has an associated event handler map, which is a map + of strings representing names of event + handlers to event handlers.

    + +

    When an EventTarget object that has one or more event handlers + specified is created, its event handler map must be initialized such that it contains + an entry for each event + handler that has that object as target, with + items in those event handlers set to their initial + values.

    + +

    The order of the entries of event handler + map could be arbitrary. It is not observable through any algorithms that operate on the + map.

    + +

    Entries are not created in the event handler + map of an object for event handlers that are merely exposed on that object, + but have some other object as their targets.

    + +
    +

    An event handler IDL attribute is an IDL attribute for a specific event handler. The name of - the IDL attribute is the same as the name of the event - handler.

    + the IDL attribute is the same as the name of the event handler.

    -

    Event handler IDL attributes, on setting, must set the corresponding event handler to their new value, and on getting, must return the - result of getting the current value of the event handler in question.

    +
    -

    If an event handler IDL attribute exposes an - event handler of an object that doesn't exist, it must always - return null on getting and must do nothing on setting.

    +

    The getter of an event handler IDL attribute + with name name, when called, must run these steps:

    -

    This can happen in particular for event - handler IDL attribute on body elements that do not have corresponding - Window objects.

    +
      +
    1. Let eventTarget be the result of determining the target of an event + handler given this object and name.

    2. -

      Certain event handler IDL attributes have additional requirements, in particular - the onmessage attribute of - MessagePort objects.

      +
    3. If eventTarget is null, then return null.

    4. -
      +
    5. Return the result of getting the current value of the event handler given + eventTarget and name.

      +
    + +

    The setter of an event handler IDL attribute + with name name, when called, must run these steps:

    + +
      +
    1. Let eventTarget be the result of determining the target of an event + handler given this object and name.

    2. + +
    3. If eventTarget is null, then return.

    4. + +
    5. If the given value is null, then deactivate an event handler given + eventTarget and name.

    6. + +
    7. +

      Otherwise:

      + +
        +
      1. Let handlerMap be eventTarget's event handler + map.

      2. + +
      3. Let eventHandler be handlerMap[name].

      4. + +
      5. Set eventHandler's value to the + given value.

      6. + +
      7. Activate an event handler given eventTarget and + name.

      8. +
      +
    8. +
    + +

    Certain event handler IDL attributes have additional requirements, in + particular the onmessage attribute of + MessagePort objects.

    +
    +

    An event handler content attribute is a content attribute for a specific event - handler. The name of the content attribute is the same as the name of the event handler.

    + handler. The name of the content attribute is the same as the name of the event handler.

    Event handler content attributes, when specified, must contain valid JavaScript code which, when parsed, would match the FunctionBody @@ -89674,39 +89790,99 @@ dictionary PromiseRejectionEventInit : EventInit {

    -

    When an event handler content attribute - is set, execute the following steps:

    +

    The following attribute change + steps are used to synchronize between event handler content attributes and + event handlers:

      -
    1. If the Should element's inline behavior be blocked by Content Security - Policy? algorithm returns "Blocked" when executed upon the - attribute's element, "script attribute", and the attribute's - value, then return.

    2. +
    3. If namespace is not null, or localName is not the name of an event handler content attribute on + element, then return.

    4. -
    5. Set the corresponding event handler to an - internal raw uncompiled handler consisting of the attribute's new value and the - script location where the attribute was set to this value

    6. +
    7. Let eventTarget be the result of determining the target of an event + handler given element and localName.

    8. + +
    9. If eventTarget is null, then return.

    10. + +
    11. If value is null, then deactivate an event handler given + eventTarget and localName.

    12. + +
    13. +

      Otherwise:

      + +
        +
      1. If the Should element's inline behavior be blocked by Content Security + Policy? algorithm returns "Blocked" when executed upon + element, "script attribute", and value, then + return.

      2. + +
      3. Let handlerMap be eventTarget's event handler + map.

      4. + +
      5. Let eventHandler be handlerMap[localName].

      6. + +
      7. Let location be the script location that triggered the execution of these + steps.

      8. + +
      9. Set eventHandler's value to the + internal raw uncompiled handler value/location.

      10. + +
      11. Activate an event handler given eventTarget and + localName.

      12. +
      +
    -

    When an event handler content attribute is removed, the user agent must set the corresponding - event handler to null.

    - +

    Per the DOM Standard, these steps are run even if oldValue and + value are identical (setting an attribute to its current value), but not if + oldValue and value are both null (removing an attribute that doesn't + currently exist).


    -

    When an event handler H of an - EventTarget object T is first set to a non-null value, then: +

    To deactivate an event handler given an EventTarget object + eventTarget and a string name that is the name of an event handler, take the following + steps:

      +
    1. Let handlerMap be eventTarget's event handler + map.

    2. + +
    3. Let eventHandler be handlerMap[name].

    4. + +
    5. Set eventHandler's value to + null.

    6. + +
    7. Let listener be eventHandler's listener.

    8. + +
    9. If listener is not null, then remove an event listener with + eventTarget and listener.

    10. + +
    11. Set eventHandler's listener to + null.

    12. +
    + +

    To activate an event handler given an EventTarget object + eventTarget and a string name that is the name of an event handler, take the following + steps:

    + +
      +
    1. Let handlerMap be eventTarget's event handler + map.

    2. + +
    3. Let eventHandler be handlerMap[name].

    4. + +
    5. If eventHandler's listener is not + null, then return.

    6. +
    7. Let callback be the result of creating a Web IDL EventListener instance representing a reference to a function of one argument that executes the steps of the event handler processing algorithm, - given H and its argument.

      + given eventTarget, name, and its argument.

      The EventListener's callback context can be arbitrary; it does not impact the steps of the event handler processing @@ -89714,31 +89890,49 @@ dictionary PromiseRejectionEventInit : EventInit {

      The callback is emphatically not the event handler itself. Every event handler ends up registering the same - callback, the algorithm defined below, which takes care of invoking the right callback, and - processing the callback's return value.

      + callback, the algorithm defined below, which takes care of invoking the right code, and + processing the code's return value.

    8. Let listener be a new event listener whose type is the event handler event type - corresponding to H and callback is - callback.

      + corresponding to eventHandler and callback is callback.

      To be clear, an event listener is different from an EventListener.

    9. -
    10. Add an event listener with T and listener.

    11. +
    12. Add an event listener with eventTarget and + listener.

    13. + +
    14. Set eventHandler's listener to + listener.

    -

    This only happens the first time the event - handler's value is set. Since listeners are called in the order they were registered, the - order of event listeners for a particular event type will always be first the event listeners - registered with addEventListener() before - the first time the event handler was set to a non-null value, - then the callback to which it is currently set, if any, and finally the event listeners registered - with addEventListener() after the - first time the event handler was set to a non-null value.

    +
    +

    The event listener registration happens only if the event + handler's value is being set to non-null, and + the event handler is not already activated. Since listeners + are called in the order they were registered, assuming no deactivation occurred, the order of event listeners for a particular event type + will always be:

    + +
      +
    1. the event listeners registered with addEventListener() before the first time the + event handler's value was set to non-null

    2. + +
    3. then the callback to which it is currently set, if any

    4. + +
    5. and finally the event listeners registered with addEventListener() after the first + time the event handler's value was set to non-null.

    6. +
    +
    @@ -89758,6 +89952,23 @@ dictionary PromiseRejectionEventInit : EventInit { button.addEventListener('click', function () { alert('FOUR') }, false); </script> +

    However, in the following example, the event handler is deactivated after its initial activation (and its event listener is removed), + before being reactivated at a later time. The page will show five alerts with "ONE", "TWO", + "THREE", "FOUR", and "FIVE" respectively, in order.

    + +
    <button id="test">Start Demo</button>
    +<script>
    + var button = document.getElementById('test');
    + button.addEventListener('click', function () { alert('ONE') }, false);
    + button.setAttribute('onclick', "alert('NOT CALLED')"); // event handler is activated here
    + button.addEventListener('click', function () { alert('TWO') }, false);
    + button.onclick = null;                                 // but deactivated here
    + button.addEventListener('click', function () { alert('THREE') }, false);
    + button.onclick = function () { alert('FOUR'); };       // and re-activated here
    + button.addEventListener('click', function () { alert('FIVE') }, false);
    +</script>
    +
    @@ -89765,31 +89976,28 @@ dictionary PromiseRejectionEventInit : EventInit {

    The interfaces implemented by the event object do not influence whether an event handler is triggered or not.

    -

    The event handler processing algorithm for an event - handler H and an Event object E is as - follows:

    +

    The event handler processing algorithm for an EventTarget object + eventTarget, a string name representing the name of an event handler, and an + Event object event is as follows:

      -
    1. - -

      Let callback be the result of getting the current value of the - event handler H.

      - -
    2. +
    3. Let callback be the result of getting the current value of the event + handler given eventTarget and name.

    4. If callback is null, then return.

    5. -
    6. Let special error event handling be true if E is an - ErrorEvent object, E's type is error, and E's

      Let special error event handling be true if event is an + ErrorEvent object, event's type is + error, and event's currentTarget implements the WindowOrWorkerGlobalScope mixin. Otherwise, let special error event handling be false.

    7. -

      Process the Event object E as follows:

      +

      Process the Event object event as follows:

      @@ -89798,14 +90006,14 @@ dictionary PromiseRejectionEventInit : EventInit {

      Invoke callback with five - arguments, the first one having the value of E's event's message attribute, the second having the value of - E's filename attribute, the third - having the value of E's lineno - attribute, the fourth having the value of E's event's filename attribute, the third + having the value of event's lineno + attribute, the fourth having the value of event's colno attribute, the fifth having the value of - E's error attribute, and with the callback this value set to E's event's error attribute, and with the callback this value set to event's currentTarget. Let return value be the callback's return value.

      @@ -89816,8 +90024,10 @@ dictionary PromiseRejectionEventInit : EventInit {

      Invoke callback - with one argument, the value of which is the Event object E, - with the callback this value set to E's currentTarget. Let return value be the callback's return value.

      + with one argument, the value of which is the Event object event, + with the callback this value set to event's + currentTarget. Let return value be + the callback's return value.

      @@ -89835,7 +90045,7 @@ dictionary PromiseRejectionEventInit : EventInit {
      -
      If E is a BeforeUnloadEvent object and E's If event is a BeforeUnloadEvent object and event's type is beforeunload
      -
    8. Set H's value to the result of creating a Web IDL callback function whose - object reference is function and whose callback context is - settings object.

    9. +
    10. Set eventHandler's value to the + result of creating a Web IDL EventHandler callback function object whose object + reference is function and whose callback context is settings + object.

  • -
  • Return H's value.

  • +
  • Return eventHandler's value.

  • @@ -90236,6 +90458,10 @@ typedef OnBeforeUnloadEventHandlerNonNull? OnBeforeUnloadEvent onscroll scroll +

    We call the set of the names of the + event handlers listed in the first column of this table the + Window-reflecting body element event handler set.

    +

    The following are the event handlers (and their corresponding onunload unload +

    This list of event handlers is reified as event handler IDL + attributes through the WindowEventHandlers interface mixin.

    +

    The following are the event handlers (and their corresponding OnBeforeUnloadEventHandlerNonNull? OnBeforeUnloadEvent onpaste paste +

    This list of event handlers is reified as event handler IDL + attributes through the DocumentAndElementEventHandlers interface mixin.

    +

    The following are the event handlers (and their corresponding onblur, onerror, - onfocus, onload, onresize, and onscroll - event handlers of the Window object, exposed on the +

    The event handlers of the Window object named by the + Window-reflecting body element event handler set, exposed on the frameset element, replace the generic event handlers with the same names normally supported by HTML elements.