schepers@4: mbrubeck@74: schepers@4: mbrubeck@102: Touch Events version 2 schepers@4: mbrubeck@74: mbrubeck@20: schepers@4: schepers@4: mbrubeck@20: mbrubeck@66: schepers@4: schepers@4: schepers@4:
mbrubeck@103: The Touch Events specification defines a set of low-level events that josh@85: represent one or more points of contact with a touch-sensitive surface, josh@85: and changes of those points with respect to the surface and any DOM josh@85: elements displayed upon it (e.g. for touch screens) or associated with it josh@85: (e.g. for drawing tablets without displays). It also addresses josh@85: pen-tablet devices, such as drawing tablets, with consideration toward josh@85: stylus capabilities. schepers@4:
schepers@5: smoon@81:
smoon@81:

Introduction

smoon@81: josh@85:

josh@85: User Agents that run on terminals which provide touch input to use web josh@87: applications typically use interpreted mouse events to allow users josh@85: to access interactive web applications. However, these interpreted josh@85: events, being normalized data based on the physical touch input, tend josh@87: to have limitations on delivering the intended user experience. josh@85: Additionally, it is not possible to handle concurrent input regardless josh@87: of device capability, due to constraints of mouse events: both josh@87: system level limitations and legacy compatibility. josh@85:

smoon@81: josh@85:

josh@85: Meanwhile, native applications are capable of handling both cases with josh@85: the provided system APIs. josh@85:

josh@85: josh@85:

josh@85: The Touch Events specification provides a solution to this problem by josh@86: specifying interfaces to allow web applications to directly handle touch josh@85: events, and multiple touch points for capable devices. josh@85:

smoon@81:
smoon@81: art@28:
josh@85:

josh@85: This specification defines conformance criteria that apply to a single josh@85: product: the user agent that implements josh@85: the interfaces that it contains. josh@85:

art@28: josh@85:

josh@85: Implementations that use ECMAScript to implement the APIs defined in josh@85: this specification must implement them in a manner consistent with the josh@85: ECMAScript Bindings defined in the Web IDL specification [[!WEBIDL]] as josh@85: this specification uses that specification and terminology. josh@85:

art@28: josh@85:

josh@85: A conforming implementation is required to implement all fields josh@85: defined in this specification. josh@85:

art@28:
mbrubeck@20: schepers@4:
mbrubeck@46:

Touch Interface

josh@85:

mbrubeck@140: This interface describes an individual touch point for a touch mbrubeck@105: event. Touch objects are immutable; after one is created, its mbrubeck@105: attributes must not change. josh@85:

mbrubeck@20: mbrubeck@46:
schepers@9:
readonly attribute long identifier
schepers@5:
mbrubeck@91: An identification number for each touch point. mbrubeck@20: mbrubeck@91: When a touch point becomes active, it must be assigned an mbrubeck@91: identifier that is distinct from any other active touch mbrubeck@91: point. While the touch point remains active, all events that mbrubeck@91: refer to it must assign it the same identifier. schepers@5:
schepers@4: mbrubeck@140:
readonly attribute EventTarget target
mbrubeck@140:
mbrubeck@140: The Element on which the touch point started when it mbrubeck@140: was first placed on the surface, even if the touch point has mbrubeck@140: since moved outside the interactive area of that element. mbrubeck@140:
mbrubeck@140: schepers@9:
readonly attribute long screenX
schepers@5:
smoon@127: The horizontal coordinate of point relative to the screen in pixels schepers@5:
schepers@9:
readonly attribute long screenY
schepers@5:
smoon@127: The vertical coordinate of point relative to the screen in pixels schepers@5:
schepers@4: schepers@9:
readonly attribute long clientX
schepers@5:
smoon@127: The horizontal coordinate of point relative to the viewport in pixels, smoon@127: excluding any scroll offset schepers@5:
schepers@9:
readonly attribute long clientY
schepers@5:
smoon@127: The vertical coordinate of point relative to the viewport in pixels, smoon@127: excluding any scroll offset schepers@5:
schepers@5: schepers@9:
readonly attribute long pageX
schepers@5:
smoon@127: The horizontal coordinate of point relative to the viewport in pixels, smoon@127: including any scroll offset schepers@5:
schepers@9:
readonly attribute long pageY
schepers@5:
smoon@127: The vertical coordinate of point relative to the viewport in pixels, smoon@127: including any scroll offset schepers@5:
schepers@5: schepers@9:
readonly attribute long radiusX
schepers@5:
josh@85: the radius of the ellipse which most closely circumscribes the josh@85: touching area (e.g. finger, stylus) along the x-axis, in pixels of josh@85: the same scale as screenX; 1 if no value is josh@85: known. The value must be positive. josh@85:

Issue: What are josh@85: units of radiusX/radiusY? CSS Pixels?

schepers@5:
schepers@9:
readonly attribute long radiusY
schepers@5:
josh@85: the radius of the ellipse which most closely circumscribes the josh@85: touching area (e.g. finger, stylus) along the y-axis, in pixels of josh@85: the same scale as screenY; 1 if no value is josh@85: known. The value must be positive. schepers@6:
schepers@6: mbrubeck@18:
readonly attribute float rotationAngle
mbrubeck@18:
mbrubeck@32:

mbrubeck@32: the angle (in degrees) that the ellipse described by radiusX mbrubeck@32: and radiusY is rotated clockwise about its center; mbrubeck@33: 0 if no value is known. The value must be greater mbrubeck@33: than or equal to 0 and less than 90. mbrubeck@32:

mbrubeck@32:

mbrubeck@32: If the ellipse described by radiusX and radiusY is mbrubeck@33: circular, then rotationAngle has no effect. The user agent mbrubeck@33: may use 0 as the value in this case, or it may use any mbrubeck@33: other value in the allowed range. (For example, the user agent may mbrubeck@37: use the rotationAngle value from the previous touch event, mbrubeck@37: to avoid sudden changes.) mbrubeck@32:

mbrubeck@18:
mbrubeck@18: schepers@8:
readonly attribute float force
schepers@8:
josh@85: a relative value of pressure applied, in the range 0 to josh@85: 1, where 0 is no pressure, and josh@85: 1 is the highest level of pressure the touch device is josh@85: capable of sensing; 0 if no value is known. In josh@85: environments where force is known, the absolute pressure josh@85: represented by the force attribute, and the sensitivity in josh@85: levels of pressure, may vary. schepers@10: josh@85:

josh@85: Issue: josh@85: Consider aligning with other "channels" and values from josh@85: Ink Markup josh@85: Language (InkML), in addition to force, e.g. adding josh@85: angle, clientZ, rotation, etc. josh@85:

schepers@8:
schepers@5:
schepers@5:
schepers@5: schepers@5:
schepers@9:

TouchList Interface

josh@85:

josh@85: This interface defines a list of individual points of contact for a mbrubeck@105: touch event. TouchList objects are immutable; after one is mbrubeck@105: created, its contents must not change. josh@85:

mbrubeck@20: schepers@5:
schepers@5:
readonly attribute unsigned long length
schepers@5:
mbrubeck@47: returns the number of Touches in the list schepers@5:
mbrubeck@103:
getter Touch item (in unsigned long index)
schepers@5:
mbrubeck@103: returns the Touch at the specified index in the list schepers@5:
mbrubeck@103:
Touch identifiedTouch (in long identifier)
schepers@5:
mbrubeck@103: returns the first Touch item in the list whose identifier property matches the specified identifier schepers@5:
schepers@5:
schepers@5:
schepers@5: schepers@5:
schepers@9:

TouchEvent Interface

josh@85:

josh@85: This interface defines the touchstart, touchend, josh@85: touchmove, touchenter, touchleave, and mbrubeck@105: touchcancel event types. TouchEvent objects are mbrubeck@105: immutable; after one is created and initialized, its attributes must mbrubeck@105: not change. josh@85:

schepers@5: schepers@6:
schepers@9:
readonly attribute TouchList touches
schepers@4:
josh@85: a list of Touches for every point of contact currently smoon@99: touching the surface. schepers@4:
schepers@9:
readonly attribute TouchList targetTouches
schepers@4:
mbrubeck@96: a list of Touches for every point of contact that is touching mbrubeck@96: the surface and started on the element that is the mbrubeck@96: target of the current event. schepers@4:
schepers@9:
readonly attribute TouchList changedTouches
schepers@4:
mbrubeck@96:

mbrubeck@96: a list of Touches for every point of contact which contributed mbrubeck@96: to the event. mbrubeck@96:

mbrubeck@96:

mbrubeck@96: For the touchstart event this must be a list of the touch mbrubeck@96: points that just became active with the current event. For the mbrubeck@96: touchmove event this must be a list of the touch points that cathy@122: have moved since the last event. For the touchend and cathy@122: touchcancel events this must be a list of the touch points cathy@122: that have just been removed from the surface. For the touchenter cathy@122: and touchleave events, this must be a list of the touch points cathy@122: that have just entered or left the target element. mbrubeck@96:

schepers@4:
mbrubeck@23: mbrubeck@30:
readonly attribute boolean altKey
mbrubeck@30:
josh@85: true if the alt (Alternate) key modifier is activated; josh@85: otherwise false mbrubeck@30:
mbrubeck@30:
readonly attribute boolean metaKey
mbrubeck@30:
josh@85: true if the meta (Meta) key modifier is activated; josh@85: otherwise false. On some platforms this attribute may josh@85: map to a differently-named key modifier. mbrubeck@30:
mbrubeck@30:
readonly attribute boolean ctrlKey
mbrubeck@30:
josh@85: true if the ctrl (Control) key modifier is activated; josh@85: otherwise false mbrubeck@30:
mbrubeck@30:
readonly attribute boolean shiftKey
mbrubeck@30:
josh@85: true if the shift (Shift) key modifier is activated; josh@85: otherwise false mbrubeck@30:
smoon@92:
readonly attribute EventTarget relatedTarget
mbrubeck@43:
mbrubeck@43: identifies a secondary EventTarget related to a touch event. This mbrubeck@43: attribute is used with the touchenter event to indicate the mbrubeck@43: EventTarget the touch point exited, and with the mbrubeck@43: touchleave event to indicate the EventTarget the touch mbrubeck@43: point entered. For other event types, this attribute must be mbrubeck@43: null. mbrubeck@43:
schepers@4:
smoon@100: smoon@100:
smoon@100:

Usage Examples

smoon@100: smoon@100:

smoon@100: The examples below demonstrate the relations between the different smoon@100: TouchList members defined in a TouchEvent. smoon@100:

smoon@100: smoon@100:
smoon@100:

touches and targetTouches of a TouchEvent

smoon@100: smoon@100:

smoon@100: This example demonstrates the utility and relations between the smoon@100: touches and targetTouches members defined in the TouchEvent smoon@100: interface. The following code will generate different output based smoon@100: on the number of touch points on the touchable element and the document: smoon@100:

smoon@100: smoon@100:
smoon@100:                   <div id='touchable'>
smoon@100:                       This element is touchable.
smoon@100:                   </div>
smoon@100:           
smoon@100:                   document.getElementById('touchable').addEventListener('touchstart', function(ev) {
smoon@100: 
smoon@100:                       if (ev.touches.item(0) == ev.targetTouches.item(0))
smoon@100:                       {
smoon@100:                           /**
smoon@100:                            * If the first touch on the surface is also targeting the
smoon@100:                            * "touchable" element, the code below should execute.
smoon@100:                            * Since targetTouches is a subset of touches which covers the
smoon@100:                            * entire surface, TouchEvent.touches >= TouchEvents.targetTouches
smoon@100:                            * is always true.
smoon@100:                            */
smoon@100: 
smoon@100:                           document.write('Hello Touch Events!');
smoon@100:                       }
smoon@100: 
smoon@100:                       if (ev.touches.length == ev.targetTouches.length)
smoon@100:                       {
smoon@100:                           /**
smoon@100:                            * If all of the active touch points are on the "touchable"
smoon@100:                            * element, the length properties should be the same.
smoon@100:                            */
smoon@100: 
smoon@100:                           document.write('All points are on target element')
smoon@100:                       }
smoon@100: 
smoon@100:                       if (ev.touches.length > 1)
smoon@100:                       {
smoon@100:                           /**
smoon@100:                            * On a single touch input device, there can only be one point
smoon@100:                            * of contact on the surface, so the following code can only
smoon@100:                            * execute when the terminal supports multiple touches.
smoon@100:                            */
smoon@100: 
smoon@100:                           document.write('Hello Multiple Touch!');
smoon@100:                       }
smoon@100: 
smoon@100:                   }, false);
smoon@100:               
smoon@100:
smoon@100: smoon@100:
smoon@100:

changedTouches of a TouchEvent

smoon@100: smoon@100:

smoon@100: This example demonstrates the utility of changedTouches and it's relation smoon@100: with the other TouchList members of the TouchEvent interface. smoon@100: The code is a example which triggers whenever a touch point is removed smoon@100: from the defined touchable element: smoon@100:

smoon@100: smoon@100:
smoon@100:                   <div id='touchable'>
smoon@100:                       This element is touchable.
smoon@100:                   </div>
smoon@100:               
smoon@100:                   document.getElementById('touchable').addEventListener('touchend', function(ev) {
smoon@100: 
smoon@100:                       /**
smoon@100:                        * Example output when three touch points are on the surface,
smoon@100:                        * two of them being on the "touchable" element and one point
smoon@100:                        * in the "touchable" element is lifted from the surface:
smoon@100:                        *
smoon@100:                        * Touch points removed: 1
smoon@100:                        * Touch points left on element: 1
smoon@100:                        * Touch points left on document: 2
smoon@100:                        */
smoon@100: 
smoon@100:                       document.write('Removed: ' + ev.changedTouches.length);
smoon@100:                       document.write('Remaining on element: ' + ev.targetTouches.length);
smoon@100:                       document.write('Remaining on document: ' + ev.touches.length);
smoon@100: 
smoon@100:                   }, false);
smoon@100:               
smoon@100:
smoon@100: smoon@100:
schepers@8: smoon@129:
smoon@129:

List of TouchEvent types

smoon@129: smoon@129:

smoon@129: The following table provides a summary of the types of possible smoon@129: TouchEvent types defined in this specification. All events smoon@129: should accomplish the bubbling phase. Some events are not cancelable smoon@129: (see preventDefault). smoon@129:

smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129: smoon@129:
Event TypeSync / AsyncBubbling phaseTrusted proximal event target typesDOM interfaceCancelableDefault Action
touchstartSyncYesDocument, ElementTouchEventYesnone
touchendSyncYesDocument, ElementTouchEventYes smoon@129: Varies: mousemove (If point has been moved), mousedown, smoon@129: mouseup, click smoon@129:
touchmoveSyncYesDocument, ElementTouchEventYesnone
touchcancelSyncYesDocument, ElementTouchEventNonone
smoon@129:
smoon@129: schepers@5:
josh@85:

The touchstart josh@85: event

josh@85:

josh@85: A user agent must dispatch this event type to indicate when the user josh@85: places a touch point on the touch surface. josh@85:

mbrubeck@55: josh@85:

mbrubeck@93: The target of this event must be an Element. If the touch mbrubeck@93: point is within a frame, the event should be dispatched to an element mbrubeck@93: in the child browsing context of that frame. josh@85:

mbrubeck@95: mbrubeck@95:

mbrubeck@95: If the preventDefault method is called on this event, it mbrubeck@95: should prevent any default actions caused by any touch events mbrubeck@95: associated with the same active touch point, including mouse mbrubeck@95: events or scrolling. mbrubeck@95:

schepers@5:
mbrubeck@20: schepers@5:
schepers@9:

The touchend event

josh@85:

josh@85: A user agent must dispatch this event type to indicate when the user josh@85: removes a touch point from the touch surface, also including josh@85: cases where the touch point physically leaves the touch surface, such josh@85: as being dragged off of the screen. josh@85:

mbrubeck@54: josh@85:

cathy@122: The target of this event must be the same Element on cathy@122: which the touch point started when it was first josh@85: placed on the surface, even if the touch point has since moved josh@85: outside the interactive area of the target element. josh@85:

mbrubeck@55: josh@85:

josh@85: The touch point or points that were removed must be included josh@85: in the changedTouches attribute of the TouchEvent, and josh@85: must not be included in the touches and targetTouches josh@85: attributes. josh@85:

schepers@5:
mbrubeck@20: schepers@5:
schepers@9:

The touchmove event

josh@85:

josh@85: A user agent must dispatch this event type to indicate when the user josh@85: moves a touch point along the touch surface. josh@85:

mbrubeck@54: josh@85:

cathy@122: The target of this event must be the same Element on cathy@122: which the touch point started when it was first josh@85: placed on the surface, even if the touch point has since moved josh@85: outside the interactive area of the target element. josh@85:

mbrubeck@54: josh@85:

josh@85: If the values of radiusX, radiusY, josh@85: rotationAngle, or force are known, then the user agent josh@85: also must dispatch this event type to indicate when any of these josh@85: attributes of a touch point have changed. josh@85:

josh@85: josh@85:

josh@85: Note that the rate at which the user agent sends touchmove josh@85: events is implementation-defined, and may depend on hardware josh@85: capabilities and other implementation details. josh@85:

mbrubeck@95: mbrubeck@95:

mbrubeck@95: If the preventDefault method is called on the first mbrubeck@95: touchmove event of an active touch point, it should mbrubeck@95: prevent any default action caused by any touchmove event mbrubeck@95: associated with the same active touch point, such as scrolling. mbrubeck@95:

schepers@5:
mbrubeck@20: schepers@5:
schepers@9:

The touchenter event

josh@85:

josh@85: A user agent must dispatch this event type to indicate when a josh@85: touch point moves onto the interactive area defined by a DOM josh@85: element. Events of this type must not bubble. josh@85:

schepers@5:
mbrubeck@20: schepers@5:
schepers@9:

The touchleave event

josh@85:

josh@85: A user agent must dispatch this event type to indicate when a josh@85: touch point moves off the interactive area defined by a DOM josh@85: element. Events of this type must not bubble. josh@85:

schepers@5:
mbrubeck@20: schepers@5:
mbrubeck@61:

The touchcancel event

josh@85:

josh@85: A user agent must dispatch this event type to indicate when a touch josh@85: point has been disrupted in an implementation-specific manner, such as josh@85: a synchronous event or action originating from the UA canceling the josh@85: touch, or the touch point leaving the document window into a josh@85: non-document area which is capable of handling user interactions. josh@85: (e.g. The UA's native user interface, plug-ins) A user agent may josh@85: also dispatch this event type when the user places more touch josh@85: points on the touch surface than the device or implementation is josh@85: configured to store, in which case the earliest Touch object josh@85: in the TouchList should be removed. josh@85:

cathy@122: cathy@122:

cathy@122: The target of this event must be the same Element on cathy@122: which the touch point started when it was first cathy@122: placed on the surface, even if the touch point has since moved cathy@122: outside the interactive area of the target element. cathy@122:

cathy@122: cathy@122:

cathy@122: The touch point or points that were removed must be included cathy@122: in the changedTouches attribute of the TouchEvent, and cathy@122: must not be included in the touches and targetTouches cathy@122: attributes. cathy@122:

schepers@5:
schepers@4:
mbrubeck@20: mbrubeck@68:
mbrubeck@116:

Extensions to the Document Interface

josh@85:

mbrubeck@116: The Document interface [[!DOM-LEVEL-3-CORE]] contains methods mbrubeck@116: by which the user can create Touch and TouchList mbrubeck@116: objects. josh@85:

mbrubeck@68: mbrubeck@116:
mbrubeck@68:
Touch createTouch()
mbrubeck@68:
mbrubeck@68: Creates a Touch object with the specified attributes. mbrubeck@68:
mbrubeck@68:
AbstractView view
mbrubeck@68:
EventTarget target
mbrubeck@68:
long identifier
mbrubeck@68:
long pageX
mbrubeck@68:
long pageY
mbrubeck@68:
long screenX
mbrubeck@68:
long screenY
mbrubeck@68:
optional long radiusX
mbrubeck@68:
optional long radiusY
mbrubeck@68:
optional float rotationAngle
mbrubeck@68:
optional float force
mbrubeck@68:
mbrubeck@68:
mbrubeck@68: mbrubeck@68:
TouchList createTouchList()
mbrubeck@68:
josh@85: Creates a TouchList object containing the specified josh@85: Touch objects. mbrubeck@68:
mbrubeck@68:
Touch[] touches
mbrubeck@68:
mbrubeck@68:
mbrubeck@68: mbrubeck@68:
TouchList createTouchList()
mbrubeck@68:
mbrubeck@68: Creates a TouchList object containing a single Touch. mbrubeck@68:
mbrubeck@68:
Touch touch
mbrubeck@68:
mbrubeck@68:
mbrubeck@68:
mbrubeck@68:
mbrubeck@68: mbrubeck@35:
mbrubeck@35:

Interaction with Mouse Events

josh@85:

josh@85: The user agent may dispatch both touch events and mouse events josh@85: [[!DOM-LEVEL-2-EVENTS]] in response to the same user input. If the josh@85: user agent dispatches both touch events and mouse events in response to josh@85: a single user action, then the touchstart event type must be josh@85: dispatched before any mouse event types for that action. If the josh@85: preventDefault method of touchstart or touchmove josh@85: is called, the user agent should not dispatch any mouse event that josh@85: would be a consequential result of the the prevented touch event. josh@85:

mbrubeck@58: art@141:

art@141: If a Web application can process touch events, it can intercept them, art@141: and no corresponding mouse events would need to be dispatched by the art@141: user agent. If the Web application is not specifically written for art@141: touch input devices, it can react to the subsequent mouse events instead. art@141:

art@141: josh@85:

mbrubeck@98: If the user agent intreprets a sequence of touch events as a click, mbrubeck@98: then it should dispatch mousemove, mousedown, mbrubeck@98: mouseup, and click events (in that order) at the location mbrubeck@98: of the touchend event for the corresponding touch input. If the mbrubeck@98: contents of the document have changed during processing of the touch mbrubeck@98: events, then the user agent may dispatch the mouse events to a mbrubeck@98: different target than the touch events. mbrubeck@97:

mbrubeck@97: mbrubeck@97:

josh@85: The default actions and ordering of any further touch and mouse events josh@85: are implementation-defined, except as specified elsewhere. josh@85:

mbrubeck@35:
mbrubeck@35: schepers@10:
schepers@10:

Glossary

mbrubeck@20: schepers@10:
mbrubeck@91:
active touch point
josh@85:
mbrubeck@91: A touch point which is currently on the screen and is being mbrubeck@91: tracked by the user agent. The touch point becomes active when the mbrubeck@91: user agent first dispatches a touchstart event indicating its mbrubeck@91: appearance. It ceases to be active after the user agent dispatches a mbrubeck@91: touchend or touchcancel event indicating that the touch mbrubeck@91: point is removed from the surface or no longer tracked. schepers@10:
mbrubeck@20: schepers@12:
touch point
josh@85:
josh@85: The coordinate point at which a pointer (e.g finger or stylus) josh@85: intersects the target surface of an interface. This may apply to a josh@85: finger touching a touch-screen, or an digital pen writing on a piece josh@85: of paper. josh@85:
smoon@128: smoon@128:
preventDefault
smoon@128:
smoon@128: If a event is cancelable, the preventDefault method is used to signify smoon@128: that the event is to be canceled, and any default actions defined in the smoon@128: user agent as a result of this event, or consequential events from the smoon@128: canceled event will not occur. Calling this method on non-cancelable smoon@128: events will have no effect. smoon@128:
schepers@10:
schepers@10:
mbrubeck@20: mbrubeck@71:
mbrubeck@71:

Issues

josh@85:

josh@85: The working group maintains a list of open issues in this specification. These issues may be josh@85: addressed in future revisions of the specification. josh@85:

mbrubeck@71:
mbrubeck@71: mbrubeck@71:
schepers@4:

Acknowledgements

schepers@4:

josh@85: Many thanks to the WebKit engineers for developing the model used as a josh@85: basis for this spec, Neil Roberts (SitePen) for his summary of WebKit josh@86: touch events, Peter-Paul Koch (PPK) for his write-ups and suggestions, josh@85: Robin Berjon for developing the ReSpec.js spec authoring tool, and the WebEvents WG for their many josh@85: contributions. schepers@4:

mbrubeck@20: josh@85:

josh@85: Many others have made additional comments as the spec developed, which josh@85: have led to steady improvements. Among them are Matthew Schinckel, josh@85: Andrew Grieve, and Cathy Chan. If I inadvertently omitted your name, josh@85: please let me know. josh@85:

schepers@4:
schepers@4: schepers@4: