Skip to content

Commit 3ce007e

Browse files
committed
Fixed a bunch of failure cases for custom elements
[email protected] Review URL: https://codereview.chromium.org//1373563004 .
1 parent 6bab7d9 commit 3ce007e

File tree

4 files changed

+215
-126
lines changed

4 files changed

+215
-126
lines changed

sdk/lib/html/dartium/html_dartium.dart

Lines changed: 107 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -20171,6 +20171,39 @@ class HtmlDocument extends Document {
2017120171
return isElement ? jsClassName : null;
2017220172
}
2017320173

20174+
/**
20175+
* Does this CustomElement class have:
20176+
*
20177+
* - a created constructor with no arguments?
20178+
* - a created constructor with a super.created() initializer?
20179+
*
20180+
* e.g., MyCustomClass.created() : super.created();
20181+
*/
20182+
bool _hasCreatedConstructor(ClassMirror classMirror) {
20183+
var createdParametersValid = false;
20184+
var superCreatedCalled = false;
20185+
var className = MirrorSystem.getName(classMirror.simpleName);
20186+
var methodMirror = classMirror.declarations[new Symbol("$className.created")];
20187+
if (methodMirror != null) {
20188+
createdParametersValid = methodMirror.parameters.length == 0;
20189+
20190+
// Get the created constructor source and look at the initializer;
20191+
// Must call super.created() if not its as an error.
20192+
var createdSource = methodMirror.source;
20193+
RegExp regExp = new RegExp(r":(.*?)(;|}|\n)");
20194+
var match = regExp.firstMatch(createdSource);
20195+
superCreatedCalled = match.input.substring(match.start,match.end).contains("super.created()");
20196+
}
20197+
20198+
if (!superCreatedCalled) {
20199+
throw new DomException.jsInterop('created constructor initializer must call super.created()');
20200+
} else if (!createdParametersValid) {
20201+
throw new DomException.jsInterop('created constructor must have no parameters');
20202+
}
20203+
20204+
return true;
20205+
}
20206+
2017420207
@Experimental()
2017520208
/**
2017620209
* Register a custom subclass of Element to be instantiatable by the DOM.
@@ -20225,68 +20258,71 @@ class HtmlDocument extends Document {
2022520258
throw new DomException.jsInterop("HierarchyRequestError: Only HTML elements can be customized.");
2022620259
}
2022720260

20228-
// Start the hookup the JS way create an <x-foo> element that extends the
20229-
// <x-base> custom element. Inherit its prototype and signal what tag is
20230-
// inherited:
20231-
//
20232-
// var myProto = Object.create(HTMLElement.prototype);
20233-
// var myElement = document.registerElement('x-foo', {prototype: myProto});
20234-
var baseElement = js.context[jsClassName];
20235-
if (baseElement == null) {
20236-
// Couldn't find the HTML element so use a generic one.
20237-
baseElement = js.context['HTMLElement'];
20238-
}
20239-
var elemProto = js.context['Object'].callMethod("create", [baseElement['prototype']]);
20240-
20241-
// TODO(terry): Hack to stop recursion re-creating custom element when the
20242-
// created() constructor of the custom element does e.g.,
20243-
//
20244-
// MyElement.created() : super.created() {
20245-
// this.innerHtml = "<b>I'm an x-foo-with-markup!</b>";
20246-
// }
20247-
//
20248-
// sanitizing causes custom element to created recursively
20249-
// until stack overflow.
20250-
//
20251-
// See https://github.com/dart-lang/sdk/issues/23666
20252-
int creating = 0;
20253-
elemProto['createdCallback'] = new js.JsFunction.withThis(($this) {
20254-
if (_getJSClassName(reflectClass(customElementClass).superclass) != null && creating < 2) {
20255-
creating++;
20256-
20257-
var dartClass;
20258-
try {
20259-
dartClass = _blink.Blink_Utils.constructElement(customElementClass, $this);
20260-
} catch (e) {
20261-
dartClass = null;
20262-
} finally {
20263-
// Need to remember the Dart class that was created for this custom so
20264-
// return it and setup the blink_jsObject to the $this that we'll be working
20265-
// with as we talk to blink.
20266-
$this['dart_class'] = dartClass;
20267-
20268-
creating--;
20269-
}
20270-
}
20271-
});
20272-
elemProto['attributeChangedCallback'] = new js.JsFunction.withThis(($this, attrName, oldVal, newVal) {
20273-
if ($this["dart_class"] != null && $this['dart_class'].attributeChanged != null) {
20274-
$this['dart_class'].attributeChanged(attrName, oldVal, newVal);
20275-
}
20276-
});
20277-
elemProto['attachedCallback'] = new js.JsFunction.withThis(($this) {
20278-
if ($this["dart_class"] != null && $this['dart_class'].attached != null) {
20279-
$this['dart_class'].attached();
20261+
if (_hasCreatedConstructor(classMirror)) {
20262+
// Start the hookup the JS way create an <x-foo> element that extends the
20263+
// <x-base> custom element. Inherit its prototype and signal what tag is
20264+
// inherited:
20265+
//
20266+
// var myProto = Object.create(HTMLElement.prototype);
20267+
// var myElement = document.registerElement('x-foo', {prototype: myProto});
20268+
var baseElement = js.context[jsClassName];
20269+
if (baseElement == null) {
20270+
// Couldn't find the HTML element so use a generic one.
20271+
baseElement = js.context['HTMLElement'];
2028020272
}
20281-
});
20282-
elemProto['detachedCallback'] = new js.JsFunction.withThis(($this) {
20283-
if ($this["dart_class"] != null && $this['dart_class'].detached != null) {
20284-
$this['dart_class'].detached();
20285-
}
20286-
});
20287-
// document.registerElement('x-foo', {prototype: elemProto, extends: extendsTag});
20288-
var jsMap = new js.JsObject.jsify({'prototype': elemProto, 'extends': extendsTag});
20289-
js.context['document'].callMethod('registerElement', [tag, jsMap]);
20273+
var elemProto = js.context['Object'].callMethod("create", [baseElement['prototype']]);
20274+
20275+
// TODO(terry): Hack to stop recursion re-creating custom element when the
20276+
// created() constructor of the custom element does e.g.,
20277+
//
20278+
// MyElement.created() : super.created() {
20279+
// this.innerHtml = "<b>I'm an x-foo-with-markup!</b>";
20280+
// }
20281+
//
20282+
// sanitizing causes custom element to created recursively
20283+
// until stack overflow.
20284+
//
20285+
// See https://github.com/dart-lang/sdk/issues/23666
20286+
int creating = 0;
20287+
elemProto['createdCallback'] = new js.JsFunction.withThis(($this) {
20288+
if (_getJSClassName(reflectClass(customElementClass).superclass) != null && creating < 2) {
20289+
creating++;
20290+
20291+
var dartClass;
20292+
try {
20293+
dartClass = _blink.Blink_Utils.constructElement(customElementClass, $this);
20294+
} catch (e) {
20295+
dartClass = HtmlElement.internalCreateHtmlElement();
20296+
throw e;
20297+
} finally {
20298+
// Need to remember the Dart class that was created for this custom so
20299+
// return it and setup the blink_jsObject to the $this that we'll be working
20300+
// with as we talk to blink.
20301+
$this['dart_class'] = dartClass;
20302+
20303+
creating--;
20304+
}
20305+
}
20306+
});
20307+
elemProto['attributeChangedCallback'] = new js.JsFunction.withThis(($this, attrName, oldVal, newVal) {
20308+
if ($this["dart_class"] != null && $this['dart_class'].attributeChanged != null) {
20309+
$this['dart_class'].attributeChanged(attrName, oldVal, newVal);
20310+
}
20311+
});
20312+
elemProto['attachedCallback'] = new js.JsFunction.withThis(($this) {
20313+
if ($this["dart_class"] != null && $this['dart_class'].attached != null) {
20314+
$this['dart_class'].attached();
20315+
}
20316+
});
20317+
elemProto['detachedCallback'] = new js.JsFunction.withThis(($this) {
20318+
if ($this["dart_class"] != null && $this['dart_class'].detached != null) {
20319+
$this['dart_class'].detached();
20320+
}
20321+
});
20322+
// document.registerElement('x-foo', {prototype: elemProto, extends: extendsTag});
20323+
var jsMap = new js.JsObject.jsify({'prototype': elemProto, 'extends': extendsTag});
20324+
js.context['document'].callMethod('registerElement', [tag, jsMap]);
20325+
}
2029020326
}
2029120327

2029220328
/** *Deprecated*: use [registerElement] instead. */
@@ -27928,7 +27964,14 @@ class _ChildNodeListLazy extends ListBase<Node> implements NodeListWrapper {
2792827964
class Node extends EventTarget {
2792927965

2793027966
// Custom element created callback.
27931-
Node._created() : super._created();
27967+
Node._created() : super._created() {
27968+
// By this point blink_jsObject should be setup if it's not then we weren't
27969+
// called by the registerElement createdCallback - probably created() was
27970+
// called directly which is verboten.
27971+
if (this.blink_jsObject == null) {
27972+
throw new DomException.jsInterop("the created constructor cannot be called directly");
27973+
}
27974+
}
2793227975

2793327976
/**
2793427977
* A modifiable list of this node's children.
@@ -37076,10 +37119,10 @@ class Url extends NativeFieldWrapperClass2 implements UrlUtils {
3707637119
if ((blob_OR_source_OR_stream is Blob || blob_OR_source_OR_stream == null)) {
3707737120
return _blink.BlinkURL.instance.createObjectURL_Callback_1_(unwrap_jso(blob_OR_source_OR_stream));
3707837121
}
37079-
if ((blob_OR_source_OR_stream is MediaSource)) {
37122+
if ((blob_OR_source_OR_stream is MediaStream)) {
3708037123
return _blink.BlinkURL.instance.createObjectURL_Callback_1_(unwrap_jso(blob_OR_source_OR_stream));
3708137124
}
37082-
if ((blob_OR_source_OR_stream is MediaStream)) {
37125+
if ((blob_OR_source_OR_stream is MediaSource)) {
3708337126
return _blink.BlinkURL.instance.createObjectURL_Callback_1_(unwrap_jso(blob_OR_source_OR_stream));
3708437127
}
3708537128
throw new ArgumentError("Incorrect number or type of arguments");

tests/html/html.status

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ js_typed_interop_test: Skip # Issue 23676, 23677
1212
[ $compiler == none && $runtime == dartium ]
1313
cross_domain_iframe_test: RuntimeError # Dartium JSInterop failure
1414
crypto_test/functional: RuntimeError # Dartium JSInterop failure
15-
custom/created_callback_test: RuntimeError # Dartium JSInterop failure
1615
custom/document_register_basic_test: RuntimeError # Dartium JSInterop failure
1716
custom/document_register_type_extensions_test/registration: RuntimeError # Dartium JSInterop failure
1817
custom/element_upgrade_test: RuntimeError # Dartium JSInterop failure

tools/dom/templates/html/impl/impl_HTMLDocument.darttemplate

Lines changed: 97 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,39 @@ $if DARTIUM
221221
// If we're an element then everything is okay.
222222
return isElement ? jsClassName : null;
223223
}
224+
225+
/**
226+
* Does this CustomElement class have:
227+
*
228+
* - a created constructor with no arguments?
229+
* - a created constructor with a super.created() initializer?
230+
*
231+
* e.g., MyCustomClass.created() : super.created();
232+
*/
233+
bool _hasCreatedConstructor(ClassMirror classMirror) {
234+
var createdParametersValid = false;
235+
var superCreatedCalled = false;
236+
var className = MirrorSystem.getName(classMirror.simpleName);
237+
var methodMirror = classMirror.declarations[new Symbol("$className.created")];
238+
if (methodMirror != null) {
239+
createdParametersValid = methodMirror.parameters.length == 0;
240+
241+
// Get the created constructor source and look at the initializer;
242+
// Must call super.created() if not its as an error.
243+
var createdSource = methodMirror.source;
244+
RegExp regExp = new RegExp(r":(.*?)(;|}|\n)");
245+
var match = regExp.firstMatch(createdSource);
246+
superCreatedCalled = match.input.substring(match.start,match.end).contains("super.created()");
247+
}
248+
249+
if (!superCreatedCalled) {
250+
throw new DomException.jsInterop('created constructor initializer must call super.created()');
251+
} else if (!createdParametersValid) {
252+
throw new DomException.jsInterop('created constructor must have no parameters');
253+
}
254+
255+
return true;
256+
}
224257
$endif
225258

226259
@Experimental()
@@ -281,68 +314,71 @@ $else
281314
throw new DomException.jsInterop("HierarchyRequestError: Only HTML elements can be customized.");
282315
}
283316

284-
// Start the hookup the JS way create an <x-foo> element that extends the
285-
// <x-base> custom element. Inherit its prototype and signal what tag is
286-
// inherited:
287-
//
288-
// var myProto = Object.create(HTMLElement.prototype);
289-
// var myElement = document.registerElement('x-foo', {prototype: myProto});
290-
var baseElement = js.context[jsClassName];
291-
if (baseElement == null) {
292-
// Couldn't find the HTML element so use a generic one.
293-
baseElement = js.context['HTMLElement'];
294-
}
295-
var elemProto = js.context['Object'].callMethod("create", [baseElement['prototype']]);
296-
297-
// TODO(terry): Hack to stop recursion re-creating custom element when the
298-
// created() constructor of the custom element does e.g.,
299-
//
300-
// MyElement.created() : super.created() {
301-
// this.innerHtml = "<b>I'm an x-foo-with-markup!</b>";
302-
// }
303-
//
304-
// sanitizing causes custom element to created recursively
305-
// until stack overflow.
306-
//
307-
// See https://github.com/dart-lang/sdk/issues/23666
308-
int creating = 0;
309-
elemProto['createdCallback'] = new js.JsFunction.withThis(($this) {
310-
if (_getJSClassName(reflectClass(customElementClass).superclass) != null && creating < 2) {
311-
creating++;
312-
313-
var dartClass;
314-
try {
315-
dartClass = _blink.Blink_Utils.constructElement(customElementClass, $this);
316-
} catch (e) {
317-
dartClass = null;
318-
} finally {
319-
// Need to remember the Dart class that was created for this custom so
320-
// return it and setup the blink_jsObject to the $this that we'll be working
321-
// with as we talk to blink.
322-
$this['dart_class'] = dartClass;
323-
324-
creating--;
325-
}
326-
}
327-
});
328-
elemProto['attributeChangedCallback'] = new js.JsFunction.withThis(($this, attrName, oldVal, newVal) {
329-
if ($this["dart_class"] != null && $this['dart_class'].attributeChanged != null) {
330-
$this['dart_class'].attributeChanged(attrName, oldVal, newVal);
331-
}
332-
});
333-
elemProto['attachedCallback'] = new js.JsFunction.withThis(($this) {
334-
if ($this["dart_class"] != null && $this['dart_class'].attached != null) {
335-
$this['dart_class'].attached();
317+
if (_hasCreatedConstructor(classMirror)) {
318+
// Start the hookup the JS way create an <x-foo> element that extends the
319+
// <x-base> custom element. Inherit its prototype and signal what tag is
320+
// inherited:
321+
//
322+
// var myProto = Object.create(HTMLElement.prototype);
323+
// var myElement = document.registerElement('x-foo', {prototype: myProto});
324+
var baseElement = js.context[jsClassName];
325+
if (baseElement == null) {
326+
// Couldn't find the HTML element so use a generic one.
327+
baseElement = js.context['HTMLElement'];
336328
}
337-
});
338-
elemProto['detachedCallback'] = new js.JsFunction.withThis(($this) {
339-
if ($this["dart_class"] != null && $this['dart_class'].detached != null) {
340-
$this['dart_class'].detached();
341-
}
342-
});
343-
// document.registerElement('x-foo', {prototype: elemProto, extends: extendsTag});
344-
var jsMap = new js.JsObject.jsify({'prototype': elemProto, 'extends': extendsTag});
345-
js.context['document'].callMethod('registerElement', [tag, jsMap]);
329+
var elemProto = js.context['Object'].callMethod("create", [baseElement['prototype']]);
330+
331+
// TODO(terry): Hack to stop recursion re-creating custom element when the
332+
// created() constructor of the custom element does e.g.,
333+
//
334+
// MyElement.created() : super.created() {
335+
// this.innerHtml = "<b>I'm an x-foo-with-markup!</b>";
336+
// }
337+
//
338+
// sanitizing causes custom element to created recursively
339+
// until stack overflow.
340+
//
341+
// See https://github.com/dart-lang/sdk/issues/23666
342+
int creating = 0;
343+
elemProto['createdCallback'] = new js.JsFunction.withThis(($this) {
344+
if (_getJSClassName(reflectClass(customElementClass).superclass) != null && creating < 2) {
345+
creating++;
346+
347+
var dartClass;
348+
try {
349+
dartClass = _blink.Blink_Utils.constructElement(customElementClass, $this);
350+
} catch (e) {
351+
dartClass = HtmlElement.internalCreateHtmlElement();
352+
throw e;
353+
} finally {
354+
// Need to remember the Dart class that was created for this custom so
355+
// return it and setup the blink_jsObject to the $this that we'll be working
356+
// with as we talk to blink.
357+
$this['dart_class'] = dartClass;
358+
359+
creating--;
360+
}
361+
}
362+
});
363+
elemProto['attributeChangedCallback'] = new js.JsFunction.withThis(($this, attrName, oldVal, newVal) {
364+
if ($this["dart_class"] != null && $this['dart_class'].attributeChanged != null) {
365+
$this['dart_class'].attributeChanged(attrName, oldVal, newVal);
366+
}
367+
});
368+
elemProto['attachedCallback'] = new js.JsFunction.withThis(($this) {
369+
if ($this["dart_class"] != null && $this['dart_class'].attached != null) {
370+
$this['dart_class'].attached();
371+
}
372+
});
373+
elemProto['detachedCallback'] = new js.JsFunction.withThis(($this) {
374+
if ($this["dart_class"] != null && $this['dart_class'].detached != null) {
375+
$this['dart_class'].detached();
376+
}
377+
});
378+
// document.registerElement('x-foo', {prototype: elemProto, extends: extendsTag});
379+
var jsMap = new js.JsObject.jsify({'prototype': elemProto, 'extends': extendsTag});
380+
js.context['document'].callMethod('registerElement', [tag, jsMap]);
381+
}
346382
$endif
347383
}
348384

0 commit comments

Comments
 (0)