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