| Index: pkg/polymer/lib/polymer_element.dart
|
| diff --git a/pkg/polymer/lib/polymer_element.dart b/pkg/polymer/lib/polymer_element.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..141a50d73c46c74622c5f414a1149039ae805449
|
| --- /dev/null
|
| +++ b/pkg/polymer/lib/polymer_element.dart
|
| @@ -0,0 +1,555 @@
|
| +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +library polymer.polymer_element;
|
| +
|
| +import 'dart:async';
|
| +import 'dart:html';
|
| +import 'dart:mirrors';
|
| +
|
| +import 'package:custom_element/custom_element.dart';
|
| +import 'package:js/js.dart' as js;
|
| +import 'package:mdv/mdv.dart' show NodeBinding;
|
| +import 'package:observe/observe.dart';
|
| +import 'package:observe/src/microtask.dart';
|
| +import 'package:polymer_expressions/polymer_expressions.dart';
|
| +
|
| +import 'src/utils_observe.dart' show toCamelCase, toHyphenedName;
|
| +
|
| +/**
|
| + * Registers a [PolymerElement]. This is similar to [registerCustomElement]
|
| + * but it is designed to work with the `<element>` element and adds additional
|
| + * features.
|
| + */
|
| +void registerPolymerElement(String localName, PolymerElement create()) {
|
| + registerCustomElement(localName, () => create().._initialize(localName));
|
| +}
|
| +
|
| +/**
|
| + * *Warning*: many features of this class are not fully implemented.
|
| + *
|
| + * The base class for Polymer elements. It provides convience features on top
|
| + * of the custom elements web standard.
|
| + *
|
| + * Currently it supports publishing attributes via:
|
| + *
|
| + * <element name="..." attributes="foo, bar, baz">
|
| + *
|
| + * Any attribute published this way can be used in a data binding expression,
|
| + * and it should contain a corresponding DOM field.
|
| + *
|
| + * *Warning*: due to dart2js mirror limititations, the mapping from HTML
|
| + * attribute to element property is a conversion from `dash-separated-words`
|
| + * to camelCase, rather than searching for a property with the same name.
|
| + */
|
| +// TODO(jmesserly): fix the dash-separated-words issue. Polymer uses lowercase.
|
| +class PolymerElement extends CustomElement with _EventsMixin {
|
| + // This is a partial port of:
|
| + // https://github.com/Polymer/polymer/blob/stable/src/attrs.js
|
| + // https://github.com/Polymer/polymer/blob/stable/src/bindProperties.js
|
| + // https://github.com/Polymer/polymer/blob/7936ff8/src/declaration/events.js
|
| + // https://github.com/Polymer/polymer/blob/7936ff8/src/instance/events.js
|
| + // TODO(jmesserly): we still need to port more of the functionality
|
| +
|
| + /// The one syntax to rule them all.
|
| + static BindingDelegate _polymerSyntax = new PolymerExpressions();
|
| + // TODO(sigmund): delete. The next line is only added to avoid warnings from
|
| + // the analyzer (see http://dartbug.com/11672)
|
| + Element get host => super.host;
|
| +
|
| + bool get applyAuthorStyles => false;
|
| + bool get resetStyleInheritance => false;
|
| +
|
| + /**
|
| + * The declaration of this polymer-element, used to extract template contents
|
| + * and other information.
|
| + */
|
| + static Map<String, Element> _declarations = {};
|
| + static Element getDeclaration(String localName) {
|
| + if (localName == null) return null;
|
| + var element = _declarations[localName];
|
| + if (element == null) {
|
| + element = document.query('polymer-element[name="$localName"]');
|
| + _declarations[localName] = element;
|
| + }
|
| + return element;
|
| + }
|
| +
|
| + Map<String, PathObserver> _publishedAttrs;
|
| + Map<String, StreamSubscription> _bindings;
|
| + final List<String> _localNames = [];
|
| +
|
| + void _initialize(String localName) {
|
| + if (localName == null) return;
|
| +
|
| + var declaration = getDeclaration(localName);
|
| + if (declaration == null) return;
|
| +
|
| + if (declaration.attributes['extends'] != null) {
|
| + var base = declaration.attributes['extends'];
|
| + // Skip normal tags, only initialize parent custom elements.
|
| + if (base.contains('-')) _initialize(base);
|
| + }
|
| +
|
| + _parseHostEvents(declaration);
|
| + _parseLocalEvents(declaration);
|
| + _publishAttributes(declaration);
|
| + _localNames.add(localName);
|
| + }
|
| +
|
| + void _publishAttributes(elementElement) {
|
| + _bindings = {};
|
| + _publishedAttrs = {};
|
| +
|
| + var attrs = elementElement.attributes['attributes'];
|
| + if (attrs != null) {
|
| + // attributes='a b c' or attributes='a,b,c'
|
| + for (var name in attrs.split(attrs.contains(',') ? ',' : ' ')) {
|
| + name = name.trim();
|
| +
|
| + // TODO(jmesserly): PathObserver is overkill here; it helps avoid
|
| + // "new Symbol" and other mirrors-related warnings.
|
| + _publishedAttrs[name] = new PathObserver(this, toCamelCase(name));
|
| + }
|
| + }
|
| + }
|
| +
|
| + void created() {
|
| + // TODO(jmesserly): this breaks until we get some kind of type conversion.
|
| + // _publishedAttrs.forEach((name, propObserver) {
|
| + // var value = attributes[name];
|
| + // if (value != null) propObserver.value = value;
|
| + // });
|
| + _initShadowRoot();
|
| + _addHostListeners();
|
| + }
|
| +
|
| + /**
|
| + * Creates the document fragment to use for each instance of the custom
|
| + * element, given the `<template>` node. By default this is equivalent to:
|
| + *
|
| + * template.createInstance(this, polymerSyntax);
|
| + *
|
| + * Where polymerSyntax is a singleton `PolymerExpressions` instance from the
|
| + * [polymer_expressions](https://pub.dartlang.org/packages/polymer_expressions)
|
| + * package.
|
| + *
|
| + * You can override this method to change the instantiation behavior of the
|
| + * template, for example to use a different data-binding syntax.
|
| + */
|
| + DocumentFragment instanceTemplate(Element template) =>
|
| + template.createInstance(this, _polymerSyntax);
|
| +
|
| + void _initShadowRoot() {
|
| + for (var localName in _localNames) {
|
| + var declaration = getDeclaration(localName);
|
| + var root = createShadowRoot(localName);
|
| + _addInstanceListeners(root, localName);
|
| +
|
| + root.applyAuthorStyles = applyAuthorStyles;
|
| + root.resetStyleInheritance = resetStyleInheritance;
|
| +
|
| + var templateNode = declaration.children.firstWhere(
|
| + (n) => n.localName == 'template', orElse: () => null);
|
| + if (templateNode == null) return;
|
| +
|
| + // Create the contents of the element's ShadowRoot, and add them.
|
| + root.nodes.add(instanceTemplate(templateNode));
|
| +
|
| + var extendsName = declaration.attributes['extends'];
|
| + _shimCss(root, localName, extendsName);
|
| + }
|
| + }
|
| +
|
| + NodeBinding createBinding(String name, model, String path) {
|
| + var propObserver = _publishedAttrs[name];
|
| + if (propObserver != null) {
|
| + return new _PolymerBinding(this, name, model, path, propObserver);
|
| + }
|
| + return super.createBinding(name, model, path);
|
| + }
|
| +
|
| + /**
|
| + * Using Polymer's platform/src/ShadowCSS.js passing the style tag's content.
|
| + */
|
| + void _shimCss(ShadowRoot root, String localName, String extendsName) {
|
| + // TODO(terry): Remove warning, cast js.context to dynamic because of bug
|
| + // https://code.google.com/p/dart/issues/detail?id=6111. The
|
| + // js interop package will be patching this until bug is fixed.
|
| + var platform = (js.context as dynamic).Platform;
|
| + if (platform == null) return;
|
| + var shadowCss = platform.ShadowCSS;
|
| + if (shadowCss == null) return;
|
| +
|
| + // TODO(terry): Remove calls to shimShadowDOMStyling2 and replace with
|
| + // shimShadowDOMStyling when we support unwrapping dart:html
|
| + // Element to a JS DOM node.
|
| + var shimShadowDOMStyling2 = shadowCss.shimShadowDOMStyling2;
|
| + if (shimShadowDOMStyling2 == null) return;
|
| + var style = root.query('style');
|
| + if (style == null) return;
|
| + var scopedCSS = shimShadowDOMStyling2(style.text, localName);
|
| +
|
| + // TODO(terry): Remove when shimShadowDOMStyling is called we don't need to
|
| + // replace original CSS with scoped CSS shimShadowDOMStyling
|
| + // does that.
|
| + style.text = scopedCSS;
|
| + }
|
| +}
|
| +
|
| +class _PolymerBinding extends NodeBinding {
|
| + final PathObserver _publishedAttr;
|
| +
|
| + _PolymerBinding(node, property, model, path, PathObserver this._publishedAttr)
|
| + : super(node, property, model, path);
|
| +
|
| + void boundValueChanged(newValue) {
|
| + _publishedAttr.value = newValue;
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * Polymer features to handle the syntactic sugar on-* to declare to
|
| + * automatically map event handlers to instance methods of the [PolymerElement].
|
| + * This mixin is a port of:
|
| + * https://github.com/Polymer/polymer/blob/7936ff8/src/declaration/events.js
|
| + * https://github.com/Polymer/polymer/blob/7936ff8/src/instance/events.js
|
| + */
|
| +abstract class _EventsMixin {
|
| + // TODO(sigmund): implement the Dart equivalent of 'inheritDelegates'
|
| + // Notes about differences in the implementation below:
|
| + // - _templateDelegates: polymer stores the template delegates directly on
|
| + // the template node (see in parseLocalEvents: 't.delegates = {}'). Here we
|
| + // simply use a separate map, where keys are the name of the
|
| + // custom-element.
|
| + // - _listenLocal we return true/false and propagate that up, JS
|
| + // implementation does't forward the return value.
|
| + // - we don't keep the side-table (weak hash map) of unhandled events (see
|
| + // handleIfNotHandled)
|
| + // - we don't use event.type to dispatch events, instead we save the event
|
| + // name with the event listeners. We do so to avoid translating back and
|
| + // forth between Dom and Dart event names.
|
| +
|
| + // ---------------------------------------------------------------------------
|
| + // The following section was ported from:
|
| + // https://github.com/Polymer/polymer/blob/7936ff8/src/declaration/events.js
|
| + // ---------------------------------------------------------------------------
|
| +
|
| + /** Maps event names and their associated method in the element class. */
|
| + final Map<String, String> _delegates = {};
|
| +
|
| + /** Expected events per element node. */
|
| + // TODO(sigmund): investigate whether we need more than 1 set of local events
|
| + // per element (why does the js implementation stores 1 per template node?)
|
| + final Map<String, Set<String>> _templateDelegates =
|
| + new Map<String, Set<String>>();
|
| +
|
| + /** [host] is needed by this mixin, but not defined here. */
|
| + Element get host;
|
| +
|
| + /** Attribute prefix used for declarative event handlers. */
|
| + static const _eventPrefix = 'on-';
|
| +
|
| + /** Whether an attribute declares an event. */
|
| + static bool _isEvent(String attr) => attr.startsWith(_eventPrefix);
|
| +
|
| + /** Extracts events from the element tag attributes. */
|
| + void _parseHostEvents(elementElement) {
|
| + for (var attr in elementElement.attributes.keys.where(_isEvent)) {
|
| + _delegates[toCamelCase(attr)] = elementElement.attributes[attr];
|
| + }
|
| + }
|
| +
|
| + /** Extracts events under the element's <template>. */
|
| + void _parseLocalEvents(elementElement) {
|
| + var name = elementElement.attributes["name"];
|
| + if (name == null) return;
|
| + var events = null;
|
| + for (var template in elementElement.queryAll('template')) {
|
| + var content = template.content;
|
| + if (content != null) {
|
| + for (var child in content.children) {
|
| + events = _accumulateEvents(child, events);
|
| + }
|
| + }
|
| + }
|
| + if (events != null) {
|
| + _templateDelegates[name] = events;
|
| + }
|
| + }
|
| +
|
| + /** Returns all events names listened by [element] and it's children. */
|
| + static Set<String> _accumulateEvents(Element element, [Set<String> events]) {
|
| + events = events == null ? new Set<String>() : events;
|
| +
|
| + // from: accumulateAttributeEvents, accumulateEvent
|
| + events.addAll(element.attributes.keys.where(_isEvent).map(toCamelCase));
|
| +
|
| + // from: accumulateChildEvents
|
| + for (var child in element.children) {
|
| + _accumulateEvents(child, events);
|
| + }
|
| +
|
| + // from: accumulateTemplatedEvents
|
| + if (element.isTemplate) {
|
| + var content = element.content;
|
| + if (content != null) {
|
| + for (var child in content.children) {
|
| + _accumulateEvents(child, events);
|
| + }
|
| + }
|
| + }
|
| + return events;
|
| + }
|
| +
|
| + // ---------------------------------------------------------------------------
|
| + // The following section was ported from:
|
| + // https://github.com/Polymer/polymer/blob/7936ff8/src/instance/events.js
|
| + // ---------------------------------------------------------------------------
|
| +
|
| + /** Attaches event listeners on the [host] element. */
|
| + void _addHostListeners() {
|
| + for (var eventName in _delegates.keys) {
|
| + _addNodeListener(host, eventName,
|
| + (e) => _hostEventListener(eventName, e));
|
| + }
|
| + }
|
| +
|
| + void _addNodeListener(node, String onEvent, Function listener) {
|
| + // If [node] is an element (typically when listening for host events) we
|
| + // use directly the '.onFoo' event stream of the element instance.
|
| + if (node is Element) {
|
| + reflect(node).getField(new Symbol(onEvent)).reflectee.listen(listener);
|
| + return;
|
| + }
|
| +
|
| + // When [node] is not an element, most commonly when [node] is the
|
| + // shadow-root of the polymer-element, we find the appropriate static event
|
| + // stream providers and attach it to [node].
|
| + var eventProvider = _eventStreamProviders[onEvent];
|
| + if (eventProvider != null) {
|
| + eventProvider.forTarget(node).listen(listener);
|
| + return;
|
| + }
|
| +
|
| + // When no provider is available, mainly because of custom-events, we use
|
| + // the underlying event listeners from the DOM.
|
| + var eventName = onEvent.substring(2).toLowerCase(); // onOneTwo => onetwo
|
| + // Most events names in Dart match those in JS in lowercase except for some
|
| + // few events listed in this map. We expect these cases to be handled above,
|
| + // but just in case we include them as a safety net here.
|
| + var jsNameFixes = const {
|
| + 'animationend': 'webkitAnimationEnd',
|
| + 'animationiteration': 'webkitAnimationIteration',
|
| + 'animationstart': 'webkitAnimationStart',
|
| + 'doubleclick': 'dblclick',
|
| + 'fullscreenchange': 'webkitfullscreenchange',
|
| + 'fullscreenerror': 'webkitfullscreenerror',
|
| + 'keyadded': 'webkitkeyadded',
|
| + 'keyerror': 'webkitkeyerror',
|
| + 'keymessage': 'webkitkeymessage',
|
| + 'needkey': 'webkitneedkey',
|
| + 'speechchange': 'webkitSpeechChange',
|
| + };
|
| + var fixedName = jsNameFixes[eventName];
|
| + node.on[fixedName != null ? fixedName : eventName].listen(listener);
|
| + }
|
| +
|
| + void _addInstanceListeners(ShadowRoot root, String elementName) {
|
| + var events = _templateDelegates[elementName];
|
| + if (events == null) return;
|
| + for (var eventName in events) {
|
| + _addNodeListener(root, eventName,
|
| + (e) => _instanceEventListener(eventName, e));
|
| + }
|
| + }
|
| +
|
| + void _hostEventListener(String eventName, Event event) {
|
| + var method = _delegates[eventName];
|
| + if (event.bubbles && method != null) {
|
| + _dispatchMethod(this, method, event, host);
|
| + }
|
| + }
|
| +
|
| + void _dispatchMethod(Object receiver, String methodName, Event event,
|
| + Node target) {
|
| + var detail = event is CustomEvent ? (event as CustomEvent).detail : null;
|
| + var args = [event, detail, target];
|
| +
|
| + var method = new Symbol(methodName);
|
| + // TODO(sigmund): consider making event listeners list all arguments
|
| + // explicitly. Unless VM mirrors are optimized first, this reflectClass call
|
| + // will be expensive once custom elements extend directly from Element (see
|
| + // dartbug.com/11108).
|
| + var methodDecl = reflectClass(receiver.runtimeType).methods[method];
|
| + if (methodDecl != null) {
|
| + // This will either truncate the argument list or extend it with extra
|
| + // null arguments, so it will match the signature.
|
| + // TODO(sigmund): consider accepting optional arguments when we can tell
|
| + // them appart from named arguments (see http://dartbug.com/11334)
|
| + args.length = methodDecl.parameters.where((p) => !p.isOptional).length;
|
| + }
|
| + reflect(receiver).invoke(method, args);
|
| + performMicrotaskCheckpoint();
|
| + }
|
| +
|
| + bool _instanceEventListener(String eventName, Event event) {
|
| + if (event.bubbles) {
|
| + if (event.path == null || !ShadowRoot.supported) {
|
| + return _listenLocalNoEventPath(eventName, event);
|
| + } else {
|
| + return _listenLocal(eventName, event);
|
| + }
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + bool _listenLocal(String eventName, Event event) {
|
| + var controller = null;
|
| + for (var target in event.path) {
|
| + // if we hit host, stop
|
| + if (target == host) return true;
|
| +
|
| + // find a controller for the target, unless we already found `host`
|
| + // as a controller
|
| + controller = (controller == host) ? controller : _findController(target);
|
| +
|
| + // if we have a controller, dispatch the event, and stop if the handler
|
| + // returns true
|
| + if (controller != null
|
| + && handleEvent(controller, eventName, event, target)) {
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + // TODO(sorvell): remove when ShadowDOM polyfill supports event path.
|
| + // Note that _findController will not return the expected controller when the
|
| + // event target is a distributed node. This is because we cannot traverse
|
| + // from a composed node to a node in shadowRoot.
|
| + // This will be addressed via an event path api
|
| + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=21066
|
| + bool _listenLocalNoEventPath(String eventName, Event event) {
|
| + var target = event.target;
|
| + var controller = null;
|
| + while (target != null && target != host) {
|
| + controller = (controller == host) ? controller : _findController(target);
|
| + if (controller != null
|
| + && handleEvent(controller, eventName, event, target)) {
|
| + return true;
|
| + }
|
| + target = target.parent;
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + // TODO(sigmund): investigate if this implementation is correct. Polymer looks
|
| + // up the shadow-root that contains [node] and uses a weak-hashmap to find the
|
| + // host associated with that root. This implementation assumes that the
|
| + // [node] is under [host]'s shadow-root.
|
| + Element _findController(Node node) => host.xtag;
|
| +
|
| + bool handleEvent(
|
| + Element controller, String eventName, Event event, Element element) {
|
| + // Note: local events are listened only in the shadow root. This dynamic
|
| + // lookup is used to distinguish determine whether the target actually has a
|
| + // listener, and if so, to determine lazily what's the target method.
|
| + var methodName = element.attributes[toHyphenedName(eventName)];
|
| + if (methodName != null) {
|
| + _dispatchMethod(controller, methodName, event, element);
|
| + }
|
| + return event.bubbles;
|
| + }
|
| +}
|
| +
|
| +
|
| +/** Event stream providers per event name. */
|
| +// TODO(sigmund): after dartbug.com/11108 is fixed, consider eliminating this
|
| +// table and using reflection instead.
|
| +const Map<String, EventStreamProvider> _eventStreamProviders = const {
|
| + 'onMouseWheel': Element.mouseWheelEvent,
|
| + 'onTransitionEnd': Element.transitionEndEvent,
|
| + 'onAbort': Element.abortEvent,
|
| + 'onBeforeCopy': Element.beforeCopyEvent,
|
| + 'onBeforeCut': Element.beforeCutEvent,
|
| + 'onBeforePaste': Element.beforePasteEvent,
|
| + 'onBlur': Element.blurEvent,
|
| + 'onChange': Element.changeEvent,
|
| + 'onClick': Element.clickEvent,
|
| + 'onContextMenu': Element.contextMenuEvent,
|
| + 'onCopy': Element.copyEvent,
|
| + 'onCut': Element.cutEvent,
|
| + 'onDoubleClick': Element.doubleClickEvent,
|
| + 'onDrag': Element.dragEvent,
|
| + 'onDragEnd': Element.dragEndEvent,
|
| + 'onDragEnter': Element.dragEnterEvent,
|
| + 'onDragLeave': Element.dragLeaveEvent,
|
| + 'onDragOver': Element.dragOverEvent,
|
| + 'onDragStart': Element.dragStartEvent,
|
| + 'onDrop': Element.dropEvent,
|
| + 'onError': Element.errorEvent,
|
| + 'onFocus': Element.focusEvent,
|
| + 'onInput': Element.inputEvent,
|
| + 'onInvalid': Element.invalidEvent,
|
| + 'onKeyDown': Element.keyDownEvent,
|
| + 'onKeyPress': Element.keyPressEvent,
|
| + 'onKeyUp': Element.keyUpEvent,
|
| + 'onLoad': Element.loadEvent,
|
| + 'onMouseDown': Element.mouseDownEvent,
|
| + 'onMouseMove': Element.mouseMoveEvent,
|
| + 'onMouseOut': Element.mouseOutEvent,
|
| + 'onMouseOver': Element.mouseOverEvent,
|
| + 'onMouseUp': Element.mouseUpEvent,
|
| + 'onPaste': Element.pasteEvent,
|
| + 'onReset': Element.resetEvent,
|
| + 'onScroll': Element.scrollEvent,
|
| + 'onSearch': Element.searchEvent,
|
| + 'onSelect': Element.selectEvent,
|
| + 'onSelectStart': Element.selectStartEvent,
|
| + 'onSubmit': Element.submitEvent,
|
| + 'onTouchCancel': Element.touchCancelEvent,
|
| + 'onTouchEnd': Element.touchEndEvent,
|
| + 'onTouchEnter': Element.touchEnterEvent,
|
| + 'onTouchLeave': Element.touchLeaveEvent,
|
| + 'onTouchMove': Element.touchMoveEvent,
|
| + 'onTouchStart': Element.touchStartEvent,
|
| + 'onFullscreenChange': Element.fullscreenChangeEvent,
|
| + 'onFullscreenError': Element.fullscreenErrorEvent,
|
| + 'onAutocomplete': FormElement.autocompleteEvent,
|
| + 'onAutocompleteError': FormElement.autocompleteErrorEvent,
|
| + 'onSpeechChange': InputElement.speechChangeEvent,
|
| + 'onCanPlay': MediaElement.canPlayEvent,
|
| + 'onCanPlayThrough': MediaElement.canPlayThroughEvent,
|
| + 'onDurationChange': MediaElement.durationChangeEvent,
|
| + 'onEmptied': MediaElement.emptiedEvent,
|
| + 'onEnded': MediaElement.endedEvent,
|
| + 'onLoadStart': MediaElement.loadStartEvent,
|
| + 'onLoadedData': MediaElement.loadedDataEvent,
|
| + 'onLoadedMetadata': MediaElement.loadedMetadataEvent,
|
| + 'onPause': MediaElement.pauseEvent,
|
| + 'onPlay': MediaElement.playEvent,
|
| + 'onPlaying': MediaElement.playingEvent,
|
| + 'onProgress': MediaElement.progressEvent,
|
| + 'onRateChange': MediaElement.rateChangeEvent,
|
| + 'onSeeked': MediaElement.seekedEvent,
|
| + 'onSeeking': MediaElement.seekingEvent,
|
| + 'onShow': MediaElement.showEvent,
|
| + 'onStalled': MediaElement.stalledEvent,
|
| + 'onSuspend': MediaElement.suspendEvent,
|
| + 'onTimeUpdate': MediaElement.timeUpdateEvent,
|
| + 'onVolumeChange': MediaElement.volumeChangeEvent,
|
| + 'onWaiting': MediaElement.waitingEvent,
|
| + 'onKeyAdded': MediaElement.keyAddedEvent,
|
| + 'onKeyError': MediaElement.keyErrorEvent,
|
| + 'onKeyMessage': MediaElement.keyMessageEvent,
|
| + 'onNeedKey': MediaElement.needKeyEvent,
|
| + 'onWebGlContextLost': CanvasElement.webGlContextLostEvent,
|
| + 'onWebGlContextRestored': CanvasElement.webGlContextRestoredEvent,
|
| + 'onPointerLockChange': Document.pointerLockChangeEvent,
|
| + 'onPointerLockError': Document.pointerLockErrorEvent,
|
| + 'onReadyStateChange': Document.readyStateChangeEvent,
|
| + 'onSelectionChange': Document.selectionChangeEvent,
|
| + 'onSecurityPolicyViolation': Document.securityPolicyViolationEvent,
|
| +};
|
|
|