| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library polymer.polymer_element; | 5 library polymer.polymer_element; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 export 'polymer.dart' show PolymerElement, registerPolymerElement; |
| 8 import 'dart:html'; | |
| 9 import 'dart:mirrors'; | |
| 10 import 'dart:js' as js; | |
| 11 | |
| 12 import 'package:custom_element/custom_element.dart'; | |
| 13 import 'package:mdv/mdv.dart' show NodeBinding; | |
| 14 import 'package:observe/observe.dart'; | |
| 15 import 'package:observe/src/microtask.dart'; | |
| 16 import 'package:polymer_expressions/polymer_expressions.dart'; | |
| 17 | |
| 18 import 'src/utils.dart' show toCamelCase, toHyphenedName; | |
| 19 | |
| 20 /** | |
| 21 * Registers a [PolymerElement]. This is similar to [registerCustomElement] | |
| 22 * but it is designed to work with the `<element>` element and adds additional | |
| 23 * features. | |
| 24 */ | |
| 25 void registerPolymerElement(String localName, PolymerElement create()) { | |
| 26 registerCustomElement(localName, () => create().._initialize(localName)); | |
| 27 } | |
| 28 | |
| 29 /** | |
| 30 * *Warning*: many features of this class are not fully implemented. | |
| 31 * | |
| 32 * The base class for Polymer elements. It provides convience features on top | |
| 33 * of the custom elements web standard. | |
| 34 * | |
| 35 * Currently it supports publishing attributes via: | |
| 36 * | |
| 37 * <element name="..." attributes="foo, bar, baz"> | |
| 38 * | |
| 39 * Any attribute published this way can be used in a data binding expression, | |
| 40 * and it should contain a corresponding DOM field. | |
| 41 * | |
| 42 * *Warning*: due to dart2js mirror limititations, the mapping from HTML | |
| 43 * attribute to element property is a conversion from `dash-separated-words` | |
| 44 * to camelCase, rather than searching for a property with the same name. | |
| 45 */ | |
| 46 // TODO(jmesserly): fix the dash-separated-words issue. Polymer uses lowercase. | |
| 47 class PolymerElement extends CustomElement with _EventsMixin { | |
| 48 // This is a partial port of: | |
| 49 // https://github.com/Polymer/polymer/blob/stable/src/attrs.js | |
| 50 // https://github.com/Polymer/polymer/blob/stable/src/bindProperties.js | |
| 51 // https://github.com/Polymer/polymer/blob/7936ff8/src/declaration/events.js | |
| 52 // https://github.com/Polymer/polymer/blob/7936ff8/src/instance/events.js | |
| 53 // TODO(jmesserly): we still need to port more of the functionality | |
| 54 | |
| 55 /// The one syntax to rule them all. | |
| 56 static BindingDelegate _polymerSyntax = new PolymerExpressions(); | |
| 57 // TODO(sigmund): delete. The next line is only added to avoid warnings from | |
| 58 // the analyzer (see http://dartbug.com/11672) | |
| 59 Element get host => super.host; | |
| 60 | |
| 61 bool get applyAuthorStyles => false; | |
| 62 bool get resetStyleInheritance => false; | |
| 63 | |
| 64 /** | |
| 65 * The declaration of this polymer-element, used to extract template contents | |
| 66 * and other information. | |
| 67 */ | |
| 68 static Map<String, Element> _declarations = {}; | |
| 69 static Element getDeclaration(String localName) { | |
| 70 if (localName == null) return null; | |
| 71 var element = _declarations[localName]; | |
| 72 if (element == null) { | |
| 73 element = document.query('polymer-element[name="$localName"]'); | |
| 74 _declarations[localName] = element; | |
| 75 } | |
| 76 return element; | |
| 77 } | |
| 78 | |
| 79 Map<String, PathObserver> _publishedAttrs; | |
| 80 Map<String, StreamSubscription> _bindings; | |
| 81 final List<String> _localNames = []; | |
| 82 | |
| 83 void _initialize(String localName) { | |
| 84 if (localName == null) return; | |
| 85 | |
| 86 var declaration = getDeclaration(localName); | |
| 87 if (declaration == null) return; | |
| 88 | |
| 89 var extendee = declaration.attributes['extends']; | |
| 90 if (extendee != null) { | |
| 91 // Skip normal tags, only initialize parent custom elements. | |
| 92 if (extendee.contains('-')) _initialize(extendee); | |
| 93 } | |
| 94 | |
| 95 _parseHostEvents(declaration); | |
| 96 _parseLocalEvents(declaration); | |
| 97 _publishAttributes(declaration); | |
| 98 | |
| 99 var templateContent = declaration.query('template').content; | |
| 100 _shimStyling(templateContent, localName); | |
| 101 | |
| 102 _localNames.add(localName); | |
| 103 } | |
| 104 | |
| 105 void _publishAttributes(elementElement) { | |
| 106 _bindings = {}; | |
| 107 _publishedAttrs = {}; | |
| 108 | |
| 109 var attrs = elementElement.attributes['attributes']; | |
| 110 if (attrs != null) { | |
| 111 // attributes='a b c' or attributes='a,b,c' | |
| 112 for (var name in attrs.split(attrs.contains(',') ? ',' : ' ')) { | |
| 113 name = name.trim(); | |
| 114 | |
| 115 // TODO(jmesserly): PathObserver is overkill here; it helps avoid | |
| 116 // "new Symbol" and other mirrors-related warnings. | |
| 117 _publishedAttrs[name] = new PathObserver(this, toCamelCase(name)); | |
| 118 } | |
| 119 } | |
| 120 } | |
| 121 | |
| 122 void created() { | |
| 123 // TODO(jmesserly): this breaks until we get some kind of type conversion. | |
| 124 // _publishedAttrs.forEach((name, propObserver) { | |
| 125 // var value = attributes[name]; | |
| 126 // if (value != null) propObserver.value = value; | |
| 127 // }); | |
| 128 _initShadowRoot(); | |
| 129 _addHostListeners(); | |
| 130 } | |
| 131 | |
| 132 /** | |
| 133 * Creates the document fragment to use for each instance of the custom | |
| 134 * element, given the `<template>` node. By default this is equivalent to: | |
| 135 * | |
| 136 * template.createInstance(this, polymerSyntax); | |
| 137 * | |
| 138 * Where polymerSyntax is a singleton `PolymerExpressions` instance from the | |
| 139 * [polymer_expressions](https://pub.dartlang.org/packages/polymer_expressions
) | |
| 140 * package. | |
| 141 * | |
| 142 * You can override this method to change the instantiation behavior of the | |
| 143 * template, for example to use a different data-binding syntax. | |
| 144 */ | |
| 145 DocumentFragment instanceTemplate(Element template) => | |
| 146 template.createInstance(this, _polymerSyntax); | |
| 147 | |
| 148 void _initShadowRoot() { | |
| 149 for (var localName in _localNames) { | |
| 150 var declaration = getDeclaration(localName); | |
| 151 var root = createShadowRoot(localName); | |
| 152 _addInstanceListeners(root, localName); | |
| 153 | |
| 154 root.applyAuthorStyles = applyAuthorStyles; | |
| 155 root.resetStyleInheritance = resetStyleInheritance; | |
| 156 | |
| 157 var templateNode = declaration.query('template'); | |
| 158 if (templateNode == null) return; | |
| 159 | |
| 160 // Create the contents of the element's ShadowRoot, and add them. | |
| 161 root.nodes.add(instanceTemplate(templateNode)); | |
| 162 } | |
| 163 } | |
| 164 | |
| 165 NodeBinding createBinding(String name, model, String path) { | |
| 166 var propObserver = _publishedAttrs[name]; | |
| 167 if (propObserver != null) { | |
| 168 return new _PolymerBinding(this, name, model, path, propObserver); | |
| 169 } | |
| 170 return super.createBinding(name, model, path); | |
| 171 } | |
| 172 | |
| 173 /** | |
| 174 * Using Polymer's platform/src/ShadowCSS.js passing the style tag's content. | |
| 175 */ | |
| 176 void _shimStyling(DocumentFragment template, String localName) { | |
| 177 if (js.context == null) return; | |
| 178 | |
| 179 var platform = js.context['Platform']; | |
| 180 if (platform == null) return; | |
| 181 | |
| 182 var style = template.query('style'); | |
| 183 if (style == null) return; | |
| 184 | |
| 185 var shadowCss = platform['ShadowCSS']; | |
| 186 if (shadowCss == null) return; | |
| 187 | |
| 188 // TODO(terry): Remove calls to shimShadowDOMStyling2 and replace with | |
| 189 // shimShadowDOMStyling when we support unwrapping dart:html | |
| 190 // Element to a JS DOM node. | |
| 191 var shimShadowDOMStyling2 = shadowCss['shimShadowDOMStyling2']; | |
| 192 if (shimShadowDOMStyling2 == null) return; | |
| 193 | |
| 194 var scopedCSS = shimShadowDOMStyling2.apply(shadowCss, | |
| 195 [style.text, localName]); | |
| 196 | |
| 197 // TODO(terry): Remove when shimShadowDOMStyling is called we don't need to | |
| 198 // replace original CSS with scoped CSS shimShadowDOMStyling | |
| 199 // does that. | |
| 200 style.text = scopedCSS; | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 class _PolymerBinding extends NodeBinding { | |
| 205 final PathObserver _publishedAttr; | |
| 206 | |
| 207 _PolymerBinding(node, property, model, path, PathObserver this._publishedAttr) | |
| 208 : super(node, property, model, path); | |
| 209 | |
| 210 void boundValueChanged(newValue) { | |
| 211 _publishedAttr.value = newValue; | |
| 212 } | |
| 213 } | |
| 214 | |
| 215 /** | |
| 216 * Polymer features to handle the syntactic sugar on-* to declare to | |
| 217 * automatically map event handlers to instance methods of the [PolymerElement]. | |
| 218 * This mixin is a port of: | |
| 219 * https://github.com/Polymer/polymer/blob/7936ff8/src/declaration/events.js | |
| 220 * https://github.com/Polymer/polymer/blob/7936ff8/src/instance/events.js | |
| 221 */ | |
| 222 abstract class _EventsMixin { | |
| 223 // TODO(sigmund): implement the Dart equivalent of 'inheritDelegates' | |
| 224 // Notes about differences in the implementation below: | |
| 225 // - _templateDelegates: polymer stores the template delegates directly on | |
| 226 // the template node (see in parseLocalEvents: 't.delegates = {}'). Here we | |
| 227 // simply use a separate map, where keys are the name of the | |
| 228 // custom-element. | |
| 229 // - _listenLocal we return true/false and propagate that up, JS | |
| 230 // implementation does't forward the return value. | |
| 231 // - we don't keep the side-table (weak hash map) of unhandled events (see | |
| 232 // handleIfNotHandled) | |
| 233 // - we don't use event.type to dispatch events, instead we save the event | |
| 234 // name with the event listeners. We do so to avoid translating back and | |
| 235 // forth between Dom and Dart event names. | |
| 236 | |
| 237 // --------------------------------------------------------------------------- | |
| 238 // The following section was ported from: | |
| 239 // https://github.com/Polymer/polymer/blob/7936ff8/src/declaration/events.js | |
| 240 // --------------------------------------------------------------------------- | |
| 241 | |
| 242 /** Maps event names and their associated method in the element class. */ | |
| 243 final Map<String, String> _delegates = {}; | |
| 244 | |
| 245 /** Expected events per element node. */ | |
| 246 // TODO(sigmund): investigate whether we need more than 1 set of local events | |
| 247 // per element (why does the js implementation stores 1 per template node?) | |
| 248 final Map<String, Set<String>> _templateDelegates = | |
| 249 new Map<String, Set<String>>(); | |
| 250 | |
| 251 /** [host] is needed by this mixin, but not defined here. */ | |
| 252 Element get host; | |
| 253 | |
| 254 /** Attribute prefix used for declarative event handlers. */ | |
| 255 static const _eventPrefix = 'on-'; | |
| 256 | |
| 257 /** Whether an attribute declares an event. */ | |
| 258 static bool _isEvent(String attr) => attr.startsWith(_eventPrefix); | |
| 259 | |
| 260 /** Extracts events from the element tag attributes. */ | |
| 261 void _parseHostEvents(elementElement) { | |
| 262 for (var attr in elementElement.attributes.keys.where(_isEvent)) { | |
| 263 _delegates[toCamelCase(attr)] = elementElement.attributes[attr]; | |
| 264 } | |
| 265 } | |
| 266 | |
| 267 /** Extracts events under the element's <template>. */ | |
| 268 void _parseLocalEvents(elementElement) { | |
| 269 var name = elementElement.attributes["name"]; | |
| 270 if (name == null) return; | |
| 271 var events = null; | |
| 272 for (var template in elementElement.queryAll('template')) { | |
| 273 var content = template.content; | |
| 274 if (content != null) { | |
| 275 for (var child in content.children) { | |
| 276 events = _accumulateEvents(child, events); | |
| 277 } | |
| 278 } | |
| 279 } | |
| 280 if (events != null) { | |
| 281 _templateDelegates[name] = events; | |
| 282 } | |
| 283 } | |
| 284 | |
| 285 /** Returns all events names listened by [element] and it's children. */ | |
| 286 static Set<String> _accumulateEvents(Element element, [Set<String> events]) { | |
| 287 events = events == null ? new Set<String>() : events; | |
| 288 | |
| 289 // from: accumulateAttributeEvents, accumulateEvent | |
| 290 events.addAll(element.attributes.keys.where(_isEvent).map(toCamelCase)); | |
| 291 | |
| 292 // from: accumulateChildEvents | |
| 293 for (var child in element.children) { | |
| 294 _accumulateEvents(child, events); | |
| 295 } | |
| 296 | |
| 297 // from: accumulateTemplatedEvents | |
| 298 if (element.isTemplate) { | |
| 299 var content = element.content; | |
| 300 if (content != null) { | |
| 301 for (var child in content.children) { | |
| 302 _accumulateEvents(child, events); | |
| 303 } | |
| 304 } | |
| 305 } | |
| 306 return events; | |
| 307 } | |
| 308 | |
| 309 // --------------------------------------------------------------------------- | |
| 310 // The following section was ported from: | |
| 311 // https://github.com/Polymer/polymer/blob/7936ff8/src/instance/events.js | |
| 312 // --------------------------------------------------------------------------- | |
| 313 | |
| 314 /** Attaches event listeners on the [host] element. */ | |
| 315 void _addHostListeners() { | |
| 316 for (var eventName in _delegates.keys) { | |
| 317 _addNodeListener(host, eventName, | |
| 318 (e) => _hostEventListener(eventName, e)); | |
| 319 } | |
| 320 } | |
| 321 | |
| 322 void _addNodeListener(node, String onEvent, Function listener) { | |
| 323 // If [node] is an element (typically when listening for host events) we | |
| 324 // use directly the '.onFoo' event stream of the element instance. | |
| 325 if (node is Element) { | |
| 326 reflect(node).getField(new Symbol(onEvent)).reflectee.listen(listener); | |
| 327 return; | |
| 328 } | |
| 329 | |
| 330 // When [node] is not an element, most commonly when [node] is the | |
| 331 // shadow-root of the polymer-element, we find the appropriate static event | |
| 332 // stream providers and attach it to [node]. | |
| 333 var eventProvider = _eventStreamProviders[onEvent]; | |
| 334 if (eventProvider != null) { | |
| 335 eventProvider.forTarget(node).listen(listener); | |
| 336 return; | |
| 337 } | |
| 338 | |
| 339 // When no provider is available, mainly because of custom-events, we use | |
| 340 // the underlying event listeners from the DOM. | |
| 341 var eventName = onEvent.substring(2).toLowerCase(); // onOneTwo => onetwo | |
| 342 // Most events names in Dart match those in JS in lowercase except for some | |
| 343 // few events listed in this map. We expect these cases to be handled above, | |
| 344 // but just in case we include them as a safety net here. | |
| 345 var jsNameFixes = const { | |
| 346 'animationend': 'webkitAnimationEnd', | |
| 347 'animationiteration': 'webkitAnimationIteration', | |
| 348 'animationstart': 'webkitAnimationStart', | |
| 349 'doubleclick': 'dblclick', | |
| 350 'fullscreenchange': 'webkitfullscreenchange', | |
| 351 'fullscreenerror': 'webkitfullscreenerror', | |
| 352 'keyadded': 'webkitkeyadded', | |
| 353 'keyerror': 'webkitkeyerror', | |
| 354 'keymessage': 'webkitkeymessage', | |
| 355 'needkey': 'webkitneedkey', | |
| 356 'speechchange': 'webkitSpeechChange', | |
| 357 }; | |
| 358 var fixedName = jsNameFixes[eventName]; | |
| 359 node.on[fixedName != null ? fixedName : eventName].listen(listener); | |
| 360 } | |
| 361 | |
| 362 void _addInstanceListeners(ShadowRoot root, String elementName) { | |
| 363 var events = _templateDelegates[elementName]; | |
| 364 if (events == null) return; | |
| 365 for (var eventName in events) { | |
| 366 _addNodeListener(root, eventName, | |
| 367 (e) => _instanceEventListener(eventName, e)); | |
| 368 } | |
| 369 } | |
| 370 | |
| 371 void _hostEventListener(String eventName, Event event) { | |
| 372 var method = _delegates[eventName]; | |
| 373 if (event.bubbles && method != null) { | |
| 374 _dispatchMethod(this, method, event, host); | |
| 375 } | |
| 376 } | |
| 377 | |
| 378 void _dispatchMethod(Object receiver, String methodName, Event event, | |
| 379 Node target) { | |
| 380 var detail = event is CustomEvent ? (event as CustomEvent).detail : null; | |
| 381 var args = [event, detail, target]; | |
| 382 | |
| 383 var method = new Symbol(methodName); | |
| 384 // TODO(sigmund): consider making event listeners list all arguments | |
| 385 // explicitly. Unless VM mirrors are optimized first, this reflectClass call | |
| 386 // will be expensive once custom elements extend directly from Element (see | |
| 387 // dartbug.com/11108). | |
| 388 var methodDecl = reflectClass(receiver.runtimeType).methods[method]; | |
| 389 if (methodDecl != null) { | |
| 390 // This will either truncate the argument list or extend it with extra | |
| 391 // null arguments, so it will match the signature. | |
| 392 // TODO(sigmund): consider accepting optional arguments when we can tell | |
| 393 // them appart from named arguments (see http://dartbug.com/11334) | |
| 394 args.length = methodDecl.parameters.where((p) => !p.isOptional).length; | |
| 395 } | |
| 396 reflect(receiver).invoke(method, args); | |
| 397 performMicrotaskCheckpoint(); | |
| 398 } | |
| 399 | |
| 400 bool _instanceEventListener(String eventName, Event event) { | |
| 401 if (event.bubbles) { | |
| 402 if (event.path == null || !ShadowRoot.supported) { | |
| 403 return _listenLocalNoEventPath(eventName, event); | |
| 404 } else { | |
| 405 return _listenLocal(eventName, event); | |
| 406 } | |
| 407 } | |
| 408 return false; | |
| 409 } | |
| 410 | |
| 411 bool _listenLocal(String eventName, Event event) { | |
| 412 var controller = null; | |
| 413 for (var target in event.path) { | |
| 414 // if we hit host, stop | |
| 415 if (target == host) return true; | |
| 416 | |
| 417 // find a controller for the target, unless we already found `host` | |
| 418 // as a controller | |
| 419 controller = (controller == host) ? controller : _findController(target); | |
| 420 | |
| 421 // if we have a controller, dispatch the event, and stop if the handler | |
| 422 // returns true | |
| 423 if (controller != null | |
| 424 && handleEvent(controller, eventName, event, target)) { | |
| 425 return true; | |
| 426 } | |
| 427 } | |
| 428 return false; | |
| 429 } | |
| 430 | |
| 431 // TODO(sorvell): remove when ShadowDOM polyfill supports event path. | |
| 432 // Note that _findController will not return the expected controller when the | |
| 433 // event target is a distributed node. This is because we cannot traverse | |
| 434 // from a composed node to a node in shadowRoot. | |
| 435 // This will be addressed via an event path api | |
| 436 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=21066 | |
| 437 bool _listenLocalNoEventPath(String eventName, Event event) { | |
| 438 var target = event.target; | |
| 439 var controller = null; | |
| 440 while (target != null && target != host) { | |
| 441 controller = (controller == host) ? controller : _findController(target); | |
| 442 if (controller != null | |
| 443 && handleEvent(controller, eventName, event, target)) { | |
| 444 return true; | |
| 445 } | |
| 446 target = target.parent; | |
| 447 } | |
| 448 return false; | |
| 449 } | |
| 450 | |
| 451 // TODO(sigmund): investigate if this implementation is correct. Polymer looks | |
| 452 // up the shadow-root that contains [node] and uses a weak-hashmap to find the | |
| 453 // host associated with that root. This implementation assumes that the | |
| 454 // [node] is under [host]'s shadow-root. | |
| 455 Element _findController(Node node) => host.xtag; | |
| 456 | |
| 457 bool handleEvent( | |
| 458 Element controller, String eventName, Event event, Element element) { | |
| 459 // Note: local events are listened only in the shadow root. This dynamic | |
| 460 // lookup is used to distinguish determine whether the target actually has a | |
| 461 // listener, and if so, to determine lazily what's the target method. | |
| 462 var methodName = element.attributes[toHyphenedName(eventName)]; | |
| 463 if (methodName != null) { | |
| 464 _dispatchMethod(controller, methodName, event, element); | |
| 465 } | |
| 466 return event.bubbles; | |
| 467 } | |
| 468 } | |
| 469 | |
| 470 | |
| 471 /** Event stream providers per event name. */ | |
| 472 // TODO(sigmund): after dartbug.com/11108 is fixed, consider eliminating this | |
| 473 // table and using reflection instead. | |
| 474 const Map<String, EventStreamProvider> _eventStreamProviders = const { | |
| 475 'onMouseWheel': Element.mouseWheelEvent, | |
| 476 'onTransitionEnd': Element.transitionEndEvent, | |
| 477 'onAbort': Element.abortEvent, | |
| 478 'onBeforeCopy': Element.beforeCopyEvent, | |
| 479 'onBeforeCut': Element.beforeCutEvent, | |
| 480 'onBeforePaste': Element.beforePasteEvent, | |
| 481 'onBlur': Element.blurEvent, | |
| 482 'onChange': Element.changeEvent, | |
| 483 'onClick': Element.clickEvent, | |
| 484 'onContextMenu': Element.contextMenuEvent, | |
| 485 'onCopy': Element.copyEvent, | |
| 486 'onCut': Element.cutEvent, | |
| 487 'onDoubleClick': Element.doubleClickEvent, | |
| 488 'onDrag': Element.dragEvent, | |
| 489 'onDragEnd': Element.dragEndEvent, | |
| 490 'onDragEnter': Element.dragEnterEvent, | |
| 491 'onDragLeave': Element.dragLeaveEvent, | |
| 492 'onDragOver': Element.dragOverEvent, | |
| 493 'onDragStart': Element.dragStartEvent, | |
| 494 'onDrop': Element.dropEvent, | |
| 495 'onError': Element.errorEvent, | |
| 496 'onFocus': Element.focusEvent, | |
| 497 'onInput': Element.inputEvent, | |
| 498 'onInvalid': Element.invalidEvent, | |
| 499 'onKeyDown': Element.keyDownEvent, | |
| 500 'onKeyPress': Element.keyPressEvent, | |
| 501 'onKeyUp': Element.keyUpEvent, | |
| 502 'onLoad': Element.loadEvent, | |
| 503 'onMouseDown': Element.mouseDownEvent, | |
| 504 'onMouseMove': Element.mouseMoveEvent, | |
| 505 'onMouseOut': Element.mouseOutEvent, | |
| 506 'onMouseOver': Element.mouseOverEvent, | |
| 507 'onMouseUp': Element.mouseUpEvent, | |
| 508 'onPaste': Element.pasteEvent, | |
| 509 'onReset': Element.resetEvent, | |
| 510 'onScroll': Element.scrollEvent, | |
| 511 'onSearch': Element.searchEvent, | |
| 512 'onSelect': Element.selectEvent, | |
| 513 'onSelectStart': Element.selectStartEvent, | |
| 514 'onSubmit': Element.submitEvent, | |
| 515 'onTouchCancel': Element.touchCancelEvent, | |
| 516 'onTouchEnd': Element.touchEndEvent, | |
| 517 'onTouchEnter': Element.touchEnterEvent, | |
| 518 'onTouchLeave': Element.touchLeaveEvent, | |
| 519 'onTouchMove': Element.touchMoveEvent, | |
| 520 'onTouchStart': Element.touchStartEvent, | |
| 521 'onFullscreenChange': Element.fullscreenChangeEvent, | |
| 522 'onFullscreenError': Element.fullscreenErrorEvent, | |
| 523 'onAutocomplete': FormElement.autocompleteEvent, | |
| 524 'onAutocompleteError': FormElement.autocompleteErrorEvent, | |
| 525 'onSpeechChange': InputElement.speechChangeEvent, | |
| 526 'onCanPlay': MediaElement.canPlayEvent, | |
| 527 'onCanPlayThrough': MediaElement.canPlayThroughEvent, | |
| 528 'onDurationChange': MediaElement.durationChangeEvent, | |
| 529 'onEmptied': MediaElement.emptiedEvent, | |
| 530 'onEnded': MediaElement.endedEvent, | |
| 531 'onLoadStart': MediaElement.loadStartEvent, | |
| 532 'onLoadedData': MediaElement.loadedDataEvent, | |
| 533 'onLoadedMetadata': MediaElement.loadedMetadataEvent, | |
| 534 'onPause': MediaElement.pauseEvent, | |
| 535 'onPlay': MediaElement.playEvent, | |
| 536 'onPlaying': MediaElement.playingEvent, | |
| 537 'onProgress': MediaElement.progressEvent, | |
| 538 'onRateChange': MediaElement.rateChangeEvent, | |
| 539 'onSeeked': MediaElement.seekedEvent, | |
| 540 'onSeeking': MediaElement.seekingEvent, | |
| 541 'onShow': MediaElement.showEvent, | |
| 542 'onStalled': MediaElement.stalledEvent, | |
| 543 'onSuspend': MediaElement.suspendEvent, | |
| 544 'onTimeUpdate': MediaElement.timeUpdateEvent, | |
| 545 'onVolumeChange': MediaElement.volumeChangeEvent, | |
| 546 'onWaiting': MediaElement.waitingEvent, | |
| 547 'onKeyAdded': MediaElement.keyAddedEvent, | |
| 548 'onKeyError': MediaElement.keyErrorEvent, | |
| 549 'onKeyMessage': MediaElement.keyMessageEvent, | |
| 550 'onNeedKey': MediaElement.needKeyEvent, | |
| 551 'onWebGlContextLost': CanvasElement.webGlContextLostEvent, | |
| 552 'onWebGlContextRestored': CanvasElement.webGlContextRestoredEvent, | |
| 553 'onPointerLockChange': Document.pointerLockChangeEvent, | |
| 554 'onPointerLockError': Document.pointerLockErrorEvent, | |
| 555 'onReadyStateChange': Document.readyStateChangeEvent, | |
| 556 'onSelectionChange': Document.selectionChangeEvent, | |
| 557 'onSecurityPolicyViolation': Document.securityPolicyViolationEvent, | |
| 558 }; | |
| OLD | NEW |