| // 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. |
| |
| part of dart.dom.html; |
| |
| _callConstructor(constructor, interceptor) { |
| return (receiver) { |
| setNativeSubclassDispatchRecord(receiver, interceptor); |
| |
| // Mirrors uses the constructor property to cache lookups, so we need it to |
| // be set correctly, including on IE where it is not automatically picked |
| // up from the __proto__. |
| JS('', '#.constructor = #.__proto__.constructor', receiver, receiver); |
| return JS('', '#(#)', constructor, receiver); |
| }; |
| } |
| |
| _callAttached(receiver) { |
| return receiver.attached(); |
| } |
| |
| _callDetached(receiver) { |
| return receiver.detached(); |
| } |
| |
| _callAttributeChanged(receiver, name, oldValue, newValue) { |
| return receiver.attributeChanged(name, oldValue, newValue); |
| } |
| |
| _makeCallbackMethod(callback) { |
| return JS( |
| '', |
| '''((function(invokeCallback) { |
| return function() { |
| return invokeCallback(this); |
| }; |
| })(#))''', |
| convertDartClosureToJS(callback, 1)); |
| } |
| |
| _makeCallbackMethod3(callback) { |
| return JS( |
| '', |
| '''((function(invokeCallback) { |
| return function(arg1, arg2, arg3) { |
| return invokeCallback(this, arg1, arg2, arg3); |
| }; |
| })(#))''', |
| convertDartClosureToJS(callback, 4)); |
| } |
| |
| /// Checks whether the given [element] correctly extends from the native class |
| /// with the given [baseClassName]. This method will throw if the base class |
| /// doesn't match, except when the element extends from `template` and it's base |
| /// class is `HTMLUnknownElement`. This exclusion is needed to support extension |
| /// of template elements (used heavily in Polymer 1.0) on IE11 when using the |
| /// webcomponents-lite.js polyfill. |
| void _checkExtendsNativeClassOrTemplate( |
| Element element, String extendsTag, String baseClassName) { |
| if (!JS('bool', '(# instanceof window[#])', element, baseClassName) && |
| !((extendsTag == 'template' && |
| JS('bool', '(# instanceof window["HTMLUnknownElement"])', |
| element)))) { |
| throw new UnsupportedError('extendsTag does not match base native class'); |
| } |
| } |
| |
| Function _registerCustomElement(context, document, String tag, [Map? options]) { |
| // Function follows the same pattern as the following JavaScript code for |
| // registering a custom element. |
| // |
| // var proto = Object.create(HTMLElement.prototype, { |
| // createdCallback: { |
| // value: function() { |
| // window.console.log('here'); |
| // } |
| // } |
| // }); |
| // document.registerElement('x-foo', { prototype: proto }); |
| // ... |
| // var e = document.createElement('x-foo'); |
| |
| String? extendsTagName = ''; |
| Type? type; |
| if (options != null) { |
| extendsTagName = options['extends']; |
| type = options['prototype']; |
| } |
| |
| var interceptorClass = findInterceptorConstructorForType(type); |
| if (interceptorClass == null) { |
| throw new ArgumentError(type); |
| } |
| |
| var interceptor = JS('=Object', '#.prototype', interceptorClass); |
| |
| var constructor = findConstructorForNativeSubclassType(type, 'created'); |
| if (constructor == null) { |
| throw new ArgumentError("$type has no constructor called 'created'"); |
| } |
| |
| // Workaround for 13190- use an article element to ensure that HTMLElement's |
| // interceptor is resolved correctly. |
| getNativeInterceptor(new Element.tag('article')); |
| |
| String baseClassName = findDispatchTagForInterceptorClass(interceptorClass); |
| if (baseClassName == null) { |
| throw new ArgumentError(type); |
| } |
| |
| if (extendsTagName == null) { |
| if (baseClassName != 'HTMLElement') { |
| throw new UnsupportedError('Class must provide extendsTag if base ' |
| 'native class is not HtmlElement'); |
| } |
| } else { |
| var element = document.createElement(extendsTagName); |
| _checkExtendsNativeClassOrTemplate(element, extendsTagName, baseClassName); |
| } |
| |
| var baseConstructor = JS('=Object', '#[#]', context, baseClassName); |
| |
| var properties = JS('=Object', '{}'); |
| |
| JS( |
| 'void', |
| '#.createdCallback = #', |
| properties, |
| JS('=Object', '{value: #}', |
| _makeCallbackMethod(_callConstructor(constructor, interceptor)))); |
| JS('void', '#.attachedCallback = #', properties, |
| JS('=Object', '{value: #}', _makeCallbackMethod(_callAttached))); |
| JS('void', '#.detachedCallback = #', properties, |
| JS('=Object', '{value: #}', _makeCallbackMethod(_callDetached))); |
| JS('void', '#.attributeChangedCallback = #', properties, |
| JS('=Object', '{value: #}', _makeCallbackMethod3(_callAttributeChanged))); |
| |
| var baseProto = JS('=Object', '#.prototype', baseConstructor); |
| var proto = JS('=Object', 'Object.create(#, #)', baseProto, properties); |
| |
| setNativeSubclassDispatchRecord(proto, interceptor); |
| |
| var opts = JS('=Object', '{prototype: #}', proto); |
| |
| if (extendsTagName != null) { |
| JS('=Object', '#.extends = #', opts, extendsTagName); |
| } |
| |
| return JS( |
| 'JavaScriptFunction', '#.registerElement(#, #)', document, tag, opts); |
| } |
| |
| //// Called by Element.created to do validation & initialization. |
| void _initializeCustomElement(Element e) { |
| // TODO(blois): Add validation that this is only in response to an upgrade. |
| } |
| |
| /// Dart2JS implementation of ElementUpgrader |
| class _JSElementUpgrader implements ElementUpgrader { |
| var _interceptor; |
| var _constructor; |
| var _nativeType; |
| |
| _JSElementUpgrader(Document document, Type type, String? extendsTag) { |
| var interceptorClass = findInterceptorConstructorForType(type); |
| if (interceptorClass == null) { |
| throw new ArgumentError(type); |
| } |
| |
| _constructor = findConstructorForNativeSubclassType(type, 'created'); |
| if (_constructor == null) { |
| throw new ArgumentError("$type has no constructor called 'created'"); |
| } |
| |
| // Workaround for 13190- use an article element to ensure that HTMLElement's |
| // interceptor is resolved correctly. |
| getNativeInterceptor(new Element.tag('article')); |
| |
| var baseClassName = findDispatchTagForInterceptorClass(interceptorClass); |
| if (baseClassName == null) { |
| throw new ArgumentError(type); |
| } |
| |
| if (extendsTag == null) { |
| if (baseClassName != 'HTMLElement') { |
| throw new UnsupportedError('Class must provide extendsTag if base ' |
| 'native class is not HtmlElement'); |
| } |
| _nativeType = HtmlElement; |
| } else { |
| var element = document.createElement(extendsTag); |
| _checkExtendsNativeClassOrTemplate(element, extendsTag, baseClassName); |
| _nativeType = element.runtimeType; |
| } |
| |
| _interceptor = JS('=Object', '#.prototype', interceptorClass); |
| } |
| |
| Element upgrade(Element element) { |
| // Only exact type matches are supported- cannot be a subclass. |
| if (element.runtimeType != _nativeType) { |
| // Some browsers may represent non-upgraded elements <x-foo> as |
| // UnknownElement and not a plain HtmlElement. |
| if (_nativeType != HtmlElement || element.runtimeType != UnknownElement) { |
| throw new ArgumentError('element is not subclass of $_nativeType'); |
| } |
| } |
| |
| setNativeSubclassDispatchRecord(element, _interceptor); |
| JS('', '#(#)', _constructor, element); |
| return element; |
| } |
| } |