summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/custom-elements/reactions
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/custom-elements/reactions')
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/Animation.html66
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/AriaMixin-element-attributes.html65
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/AriaMixin-string-attributes.html66
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/Attr.html23
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/CSSStyleDeclaration.html89
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/ChildNode.html35
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/DOMStringMap.html96
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/DOMTokenList.html210
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/Document.html156
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/Element.html91
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/ElementContentEditable.html21
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/HTMLAnchorElement.html32
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/HTMLElement.html38
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/HTMLOptionElement.html35
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/HTMLOptionsCollection.html122
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/HTMLOutputElement.html45
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/HTMLSelectElement.html122
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/HTMLTableElement.html173
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/HTMLTableRowElement.html34
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/HTMLTableSectionElement.html45
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/HTMLTitleElement.html35
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/NamedNodeMap.html39
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/Node.html49
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/ParentNode.html27
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/Range.html54
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/Selection.html32
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/ShadowRoot.html52
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLAreaElement.html69
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLBaseElement.html21
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLButtonElement.html80
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLCanvasElement.html16
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLDataElement.html15
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLDetailsElement.html15
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLEmbedElement.html35
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLFieldSetElement.html32
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLImageElement.html89
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLInputElement.html66
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLLIElement.html38
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLLabelElement.html29
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLMapElement.html17
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLMediaElement.html107
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLMetaElement.html77
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLMeterElement.html55
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLModElement.html18
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLOListElement.html17
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLOptGroupElement.html38
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLParamElement.html41
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLProgressElement.html25
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLQuoteElement.html16
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLSlotElement.html15
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLSourceElement.html191
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLStyleElement.html23
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLTableCellElement.html63
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLTableColElement.html25
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLTimeElement.html15
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/resources/reactions.js452
-rw-r--r--testing/web-platform/tests/custom-elements/reactions/with-exceptions.html35
57 files changed, 3587 insertions, 0 deletions
diff --git a/testing/web-platform/tests/custom-elements/reactions/Animation.html b/testing/web-platform/tests/custom-elements/reactions/Animation.html
new file mode 100644
index 0000000000..f8d3bb86ed
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/Animation.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on Element interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="commitStyles of Animation interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#element">
+<meta name="help" content="https://w3c.github.io/DOM-Parsing/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+test(function () {
+ const element = define_new_custom_element(['style']);
+ const instance = document.createElement(element.name);
+ document.body.appendChild(instance);
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+
+ const animation = instance.animate([{'borderColor': 'rgb(0, 0, 255)'}], 1);
+ animation.commitStyles();
+
+ const logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_equals(logEntries.last().name, 'style');
+ assert_equals(logEntries.last().namespace, null);
+}, 'Animation.animate must enqueue an attributeChanged reaction when it adds the observed style attribute');
+
+test(function () {
+ const element = define_new_custom_element(['style']);
+ const instance = document.createElement(element.name);
+ document.body.appendChild(instance);
+
+ let animation = instance.animate([{'borderColor': 'rgb(0, 0, 255)'}], 1);
+ animation.commitStyles();
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected', 'attributeChanged']);
+
+ animation = instance.animate([{'borderColor': 'rgb(0, 255, 0)'}]);
+ animation.commitStyles();
+
+ const logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_equals(logEntries.last().name, 'style');
+ assert_equals(logEntries.last().namespace, null);
+}, 'Animation.animate must enqueue an attributeChanged reaction when it mutates the observed style attribute');
+
+test(function () {
+ const element = define_new_custom_element([]);
+ const instance = document.createElement(element.name);
+ document.body.appendChild(instance);
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+
+ const animation = instance.animate([{'borderColor': 'rgb(0, 0, 255)'}], 1);
+ animation.commitStyles();
+
+ assert_array_equals(element.takeLog().types(), []);
+}, 'Animation.animate must not enqueue an attributeChanged reaction when it mutates the style attribute but the style attribute is not observed');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/AriaMixin-element-attributes.html b/testing/web-platform/tests/custom-elements/reactions/AriaMixin-element-attributes.html
new file mode 100644
index 0000000000..eec6dee03b
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/AriaMixin-element-attributes.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on Element interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="Element attributes of AriaAttributes interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#element">
+<meta name="help" content="https://w3c.github.io/DOM-Parsing/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<div id="parentElement"></div>
+<script>
+
+function testElementReflectAttribute(jsAttributeName, contentAttributeName, validValue1, validValue2, name, getParentElement) {
+ test(function () {
+ let element = define_new_custom_element([contentAttributeName]);
+ let instance = document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ parentElement.appendChild(instance);
+ assert_array_equals(element.takeLog().types(), ['connected']);
+ instance[jsAttributeName] = validValue1;
+ let logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+
+ assert_attribute_log_entry(logEntries.last(), {name: contentAttributeName, oldValue: null, newValue: "", namespace: null});
+ }, name + ' must enqueue an attributeChanged reaction when adding ' + contentAttributeName + ' content attribute');
+
+ test(function () {
+ let element = define_new_custom_element([contentAttributeName]);
+ let instance = document.createElement(element.name);
+ parentElement.appendChild(instance);
+ instance[jsAttributeName] = validValue1;
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected', 'attributeChanged']);
+ instance[jsAttributeName] = validValue2;
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: contentAttributeName, oldValue: "", newValue: "", namespace: null});
+ }, name + ' must enqueue an attributeChanged reaction when replacing an existing attribute');
+}
+
+const dummy1 = document.createElement('div');
+dummy1.id = 'dummy1';
+document.body.appendChild(dummy1);
+
+const dummy2 = document.createElement('div');
+dummy2.id = 'dummy2';
+document.body.appendChild(dummy2);
+
+testElementReflectAttribute('ariaActiveDescendantElement', 'aria-activedescendant', dummy1, dummy2, 'ariaActiveDescendantElement in Element');
+testElementReflectAttribute('ariaControlsElements', 'aria-controls', [dummy1], [dummy2], 'ariaControlsElements in Element');
+testElementReflectAttribute('ariaDescribedByElements', 'aria-describedby', [dummy1], [dummy2], 'ariaDescribedByElements in Element');
+testElementReflectAttribute('ariaDetailsElements', 'aria-details', [dummy1], [dummy2], 'ariaDetailsElements in Element');
+testElementReflectAttribute('ariaErrorMessageElements', 'aria-errormessage', [dummy1], [dummy2], 'ariaErrorMessageElements in Element');
+testElementReflectAttribute('ariaFlowToElements', 'aria-flowto', [dummy1], [dummy2], 'ariaFlowToElements in Element');
+testElementReflectAttribute('ariaLabelledByElements', 'aria-labelledby', [dummy1], [dummy2], 'ariaLabelledByElements in Element')
+testElementReflectAttribute('ariaOwnsElements', 'aria-owns', [dummy1], [dummy2], 'ariaOwnsElements in Element')
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/AriaMixin-string-attributes.html b/testing/web-platform/tests/custom-elements/reactions/AriaMixin-string-attributes.html
new file mode 100644
index 0000000000..f71bf2daa9
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/AriaMixin-string-attributes.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on Element interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="String attributes of AriaAttributes interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#element">
+<meta name="help" content="https://w3c.github.io/DOM-Parsing/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<div id="container"></div>
+<script>
+
+testReflectAttribute('ariaAtomic', 'aria-atomic', 'foo', 'bar', 'ariaAtomic on Element');
+testReflectAttribute('ariaAutoComplete', 'aria-autocomplete', 'foo', 'bar', 'ariaAutoComplete on Element');
+testReflectAttribute('ariaBusy', 'aria-busy', 'foo', 'bar', 'ariaBusy on Element');
+testReflectAttribute('ariaChecked', 'aria-checked', 'foo', 'bar', 'ariaChecked on Element');
+testReflectAttribute('ariaColCount', 'aria-colcount', 'foo', 'bar', 'ariaColCount on Element');
+testReflectAttribute('ariaColIndex', 'aria-colindex', 'foo', 'bar', 'ariaColIndex on Element');
+testReflectAttribute('ariaColSpan', 'aria-colspan', 'foo', 'bar', 'ariaColSpan on Element');
+
+testReflectAttribute('ariaCurrent', 'aria-current', 'foo', 'bar', 'ariaCurrent on Element');
+
+testReflectAttribute('ariaDisabled', 'aria-disabled', 'foo', 'bar', 'ariaDisabled on Element');
+
+testReflectAttribute('ariaExpanded', 'aria-expanded', 'foo', 'bar', 'ariaExpanded on Element');
+
+testReflectAttribute('ariaHasPopup', 'aria-haspopup', 'foo', 'bar', 'ariaHasPopup on Element');
+testReflectAttribute('ariaHidden', 'aria-hidden', 'foo', 'bar', 'ariaHidden on Element');
+testReflectAttribute('ariaInvalid', 'aria-invalid', 'foo', 'bar', 'ariaInvalid on Element');
+testReflectAttribute('ariaKeyShortcuts', 'aria-keyshortcuts', 'foo', 'bar', 'ariaKeyShortcuts on Element');
+testReflectAttribute('ariaLabel', 'aria-label', 'foo', 'bar', 'ariaLabel on Element');
+
+testReflectAttribute('ariaLevel', 'aria-level', 'foo', 'bar', 'ariaLevel on Element');
+testReflectAttribute('ariaLive', 'aria-live', 'foo', 'bar', 'ariaLive on Element');
+testReflectAttribute('ariaModal', 'aria-modal', 'foo', 'bar', 'ariaModal on Element');
+testReflectAttribute('ariaMultiLine', 'aria-multiline', 'foo', 'bar', 'ariaMultiLine on Element');
+testReflectAttribute('ariaMultiSelectable', 'aria-multiselectable', 'foo', 'bar', 'ariaMultiSelectable on Element');
+testReflectAttribute('ariaOrientation', 'aria-orientation', 'foo', 'bar', 'ariaOrientation on Element');
+
+testReflectAttribute('ariaPlaceholder', 'aria-placeholder', 'foo', 'bar', 'ariaPlaceholder on Element');
+testReflectAttribute('ariaPosInSet', 'aria-posinset', 'foo', 'bar', 'ariaPosInSet on Element');
+testReflectAttribute('ariaPressed', 'aria-pressed', 'foo', 'bar', 'ariaPressed on Element');
+testReflectAttribute('ariaReadOnly', 'aria-readonly', 'foo', 'bar', 'ariaReadOnly on Element');
+testReflectAttribute('ariaRelevant', 'aria-relevant', 'foo', 'bar', 'ariaRelevant on Element');
+testReflectAttribute('ariaRequired', 'aria-required', 'foo', 'bar', 'ariaRequired on Element');
+testReflectAttribute('ariaRoleDescription', 'aria-roledescription', 'foo', 'bar', 'ariaRoleDescription on Element');
+testReflectAttribute('ariaRowCount', 'aria-rowcount', 'foo', 'bar', 'ariaRowCount on Element');
+testReflectAttribute('ariaRowIndex', 'aria-rowindex', 'foo', 'bar', 'ariaRowIndex on Element');
+testReflectAttribute('ariaRowSpan', 'aria-rowspan', 'foo', 'bar', 'ariaRowSpan on Element');
+testReflectAttribute('ariaSelected', 'aria-selected', 'foo', 'bar', 'ariaSelected on Element');
+testReflectAttribute('ariaSetSize', 'aria-setsize', 'foo', 'bar', 'ariaSetSize on Element');
+testReflectAttribute('ariaSort', 'aria-sort', 'foo', 'bar', 'ariaSort on Element');
+testReflectAttribute('ariaValueMax', 'aria-valuemax', 'foo', 'bar', 'ariaValueMax on Element');
+testReflectAttribute('ariaValueMin', 'aria-valuemin', 'foo', 'bar', 'ariaValueMin on Element');
+testReflectAttribute('ariaValueNow', 'aria-valuenow', 'foo', 'bar', 'ariaValueNow on Element');
+testReflectAttribute('ariaValueText', 'aria-valuetext', 'foo', 'bar', 'ariaValueText on Element');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/Attr.html b/testing/web-platform/tests/custom-elements/reactions/Attr.html
new file mode 100644
index 0000000000..c9fa37f961
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/Attr.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on Attr interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="value of Attr interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#node">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+testAttributeMutator(function (element, name, value) {
+ element.attributes[name].value = value;
+}, 'value on Attr');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/CSSStyleDeclaration.html b/testing/web-platform/tests/custom-elements/reactions/CSSStyleDeclaration.html
new file mode 100644
index 0000000000..95274d8c75
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/CSSStyleDeclaration.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on CSSStyleDeclaration interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="cssText, setProperty, setPropertyValue, setPropertyPriority, removeProperty, cssFloat, and all camel cased attributes of CSSStyleDeclaration interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#node">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+test_mutating_style_property_value(function (instance, propertyName, idlName, value) {
+ instance.style.cssText = `${propertyName}: ${value}`;
+}, 'cssText on CSSStyleDeclaration');
+
+test_mutating_style_property_value(function (instance, propertyName, idlName, value) {
+ instance.style.setProperty(propertyName, value);
+}, 'setProperty on CSSStyleDeclaration');
+
+test_mutating_style_property_priority(function (instance, propertyName, idlName, isImportant) {
+ instance.style.setProperty(propertyName, instance.style[idlName], isImportant ? 'important': '');
+}, 'setProperty on CSSStyleDeclaration');
+
+if (CSSStyleDeclaration.prototype.setPropertyValue) {
+ test_mutating_style_property_value(function (instance, propertyName, idlName, value) {
+ instance.style.setPropertyValue(propertyName, value);
+ }, 'setPropertyValue on CSSStyleDeclaration');
+}
+
+if (CSSStyleDeclaration.prototype.setPropertyPriority) {
+ test_mutating_style_property_priority(function (instance, propertyName, idlName, isImportant) {
+ instance.style.setPropertyPriority(propertyName, isImportant ? 'important': '');
+ }, 'setPropertyPriority on CSSStyleDeclaration');
+}
+
+test_removing_style_property_value(function (instance, propertyName, idlName) {
+ instance.style.removeProperty(propertyName);
+}, 'removeProperty on CSSStyleDeclaration');
+
+test(function () {
+ var element = define_new_custom_element(['style']);
+ var instance = document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ instance.style.cssFloat = 'left';
+ assert_equals(instance.getAttribute('style'), 'float: left;');
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'style', oldValue: null, newValue: 'float: left;', namespace: null});
+}, 'cssFloat on CSSStyleDeclaration must enqueue an attributeChanged reaction when it adds the observed style attribute');
+
+test(function () {
+ var element = define_new_custom_element([]);
+ var instance = document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ instance.style.cssFloat = 'left';
+ assert_equals(instance.getAttribute('style'), 'float: left;');
+ assert_array_equals(element.takeLog().types(), []);
+}, 'cssFloat on CSSStyleDeclaration must not enqueue an attributeChanged reaction when it adds the style attribute but the style attribute is not observed');
+
+test_mutating_style_property_value(function (instance, propertyName, idlName, value) {
+ assert_equals(idlName, 'borderWidth');
+ instance.style.borderWidth = value;
+}, 'A camel case attribute (borderWidth) on CSSStyleDeclaration',
+ {propertyName: 'border-width', idlName: 'borderWidth', value1: '2px', value2: '4px'});
+
+test_mutating_style_property_value(function (instance, propertyName, idlName, value) {
+ assert_equals(propertyName, 'border-width');
+ instance.style['border-width'] = value;
+}, 'A dashed property (border-width) on CSSStyleDeclaration',
+ {propertyName: 'border-width', idlName: 'borderWidth', value1: '1px', value2: '5px'});
+
+test_mutating_style_property_value(function (instance, propertyName, idlName, value) {
+ instance.style.webkitFilter = value;
+}, 'A webkit prefixed camel case attribute (webkitFilter) on CSSStyleDeclaration',
+ {propertyName: 'filter', idlName: 'filter', value1: 'grayscale(20%)', value2: 'grayscale(30%)'});
+
+test_mutating_style_property_value(function (instance, propertyName, idlName, value) {
+ instance.style['-webkit-filter'] = value;
+}, 'A webkit prefixed dashed property (-webkit-filter) on CSSStyleDeclaration',
+ {propertyName: 'filter', idlName: 'filter', value1: 'grayscale(20%)', value2: 'grayscale(30%)'});
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/ChildNode.html b/testing/web-platform/tests/custom-elements/reactions/ChildNode.html
new file mode 100644
index 0000000000..f808b67a80
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/ChildNode.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on ChildNode interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="before, after, replaceWith, and remove of ChildNode interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#childnode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+testNodeConnector(function (newContainer, customElement) {
+ newContainer.firstChild.before(customElement);
+}, 'before on ChildNode');
+
+testNodeConnector(function (newContainer, customElement) {
+ newContainer.firstChild.after(customElement);
+}, 'after on ChildNode');
+
+testNodeConnector(function (newContainer, customElement) {
+ newContainer.firstChild.replaceWith(customElement);
+}, 'replaceWith on ChildNode');
+
+testNodeDisconnector(function (customElement) {
+ customElement.remove();
+}, 'remove on ChildNode');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/DOMStringMap.html b/testing/web-platform/tests/custom-elements/reactions/DOMStringMap.html
new file mode 100644
index 0000000000..5e34dfe2ba
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/DOMStringMap.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on DOMStringMap interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="setter and deleter of DOMStringMap interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#domstringmap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+test(function () {
+ var element = define_new_custom_element(['data-foo']);
+ var instance = document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ instance.dataset.foo = 'bar';
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'data-foo', oldValue: null, newValue: 'bar', namespace: null});
+}, 'setter on DOMStringMap must enqueue an attributeChanged reaction when adding an observed data attribute');
+
+test(function () {
+ var element = define_new_custom_element(['data-bar']);
+ var instance = document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ instance.dataset.foo = 'bar';
+ assert_array_equals(element.takeLog().types(), []);
+}, 'setter on DOMStringMap must not enqueue an attributeChanged reaction when adding an unobserved data attribute');
+
+test(function () {
+ var element = define_new_custom_element(['data-foo']);
+ var instance = document.createElement(element.name);
+ instance.dataset.foo = 'bar';
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ instance.dataset.foo = 'baz';
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'data-foo', oldValue: 'bar', newValue: 'baz', namespace: null});
+}, 'setter on DOMStringMap must enqueue an attributeChanged reaction when mutating the value of an observed data attribute');
+
+test(function () {
+ var element = define_new_custom_element(['data-foo']);
+ var instance = document.createElement(element.name);
+ instance.dataset.foo = 'bar';
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ instance.dataset.foo = 'bar';
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'data-foo', oldValue: 'bar', newValue: 'bar', namespace: null});
+}, 'setter on DOMStringMap must enqueue an attributeChanged reaction when mutating the value of an observed data attribute to the same value');
+
+test(function () {
+ var element = define_new_custom_element(['data-zero']);
+ var instance = document.createElement(element.name);
+ instance.dataset.foo = 'bar';
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ instance.dataset.foo = 'baz';
+ assert_array_equals(element.takeLog().types(), []);
+}, 'setter on DOMStringMap must not enqueue an attributeChanged reaction when mutating the value of an unobserved data attribute');
+
+test(function () {
+ var element = define_new_custom_element(['data-foo']);
+ var instance = document.createElement(element.name);
+ instance.dataset.foo = 'bar';
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ delete instance.dataset.foo;
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'data-foo', oldValue: 'bar', newValue: null, namespace: null});
+}, 'deleter on DOMStringMap must enqueue an attributeChanged reaction when removing an observed data attribute');
+
+test(function () {
+ var element = define_new_custom_element(['data-bar']);
+ var instance = document.createElement(element.name);
+ instance.dataset.foo = 'bar';
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ delete instance.dataset.foo;
+ assert_array_equals(element.takeLog().types(), []);
+}, 'deleter on DOMStringMap must not enqueue an attributeChanged reaction when removing an unobserved data attribute');
+
+test(function () {
+ var element = define_new_custom_element(['data-foo']);
+ var instance = document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ delete instance.dataset.foo;
+ assert_array_equals(element.takeLog().types(), []);
+}, 'deleter on DOMStringMap must not enqueue an attributeChanged reaction when it does not remove a data attribute');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/DOMTokenList.html b/testing/web-platform/tests/custom-elements/reactions/DOMTokenList.html
new file mode 100644
index 0000000000..14a643c4a8
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/DOMTokenList.html
@@ -0,0 +1,210 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on DOMTokenList interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="add, remove, toggle, replace, and the stringifier of DOMTokenList interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#node">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<script>
+
+test(function () {
+ var element = define_new_custom_element(['class']);
+ var instance = document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ instance.classList.add('foo');
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'class', oldValue: null, newValue: 'foo', namespace: null});
+}, 'add on DOMTokenList must enqueue an attributeChanged reaction when adding an attribute');
+
+test(function () {
+ var element = define_new_custom_element(['style']);
+ var instance = document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ instance.classList.add('foo');
+ assert_array_equals(element.takeLog().types(), []);
+}, 'add on DOMTokenList must not enqueue an attributeChanged reaction when adding an unobserved attribute');
+
+test(function () {
+ var element = define_new_custom_element(['class']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('class', 'hello');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ instance.classList.add('world');
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'class', oldValue: 'hello', newValue: 'hello world', namespace: null});
+}, 'add on DOMTokenList must enqueue an attributeChanged reaction when adding a value to an existing attribute');
+
+test(function () {
+ var element = define_new_custom_element(['contenteditable']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('class', 'hello');
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ instance.classList.add('world');
+ assert_array_equals(element.takeLog().types(), []);
+}, 'add on DOMTokenList must not enqueue an attributeChanged reaction when adding a value to an unobserved attribute');
+
+test(function () {
+ var element = define_new_custom_element(['class']);
+ var instance = document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ instance.classList.add('hello', 'world');
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'class', oldValue: null, newValue: 'hello world', namespace: null});
+}, 'add on DOMTokenList must enqueue exactly one attributeChanged reaction when adding multiple values to an attribute');
+
+test(function () {
+ var element = define_new_custom_element(['class']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('class', 'hello world');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ instance.classList.remove('world');
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'class', oldValue: 'hello world', newValue: 'hello', namespace: null});
+}, 'remove on DOMTokenList must enqueue an attributeChanged reaction when removing a value from an attribute');
+
+test(function () {
+ var element = define_new_custom_element(['class']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('class', 'hello foo world bar');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ instance.classList.remove('hello', 'world');
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'class', oldValue: 'hello foo world bar', newValue: 'foo bar', namespace: null});
+}, 'remove on DOMTokenList must enqueue exactly one attributeChanged reaction when removing multiple values to an attribute');
+
+test(function () {
+ var element = define_new_custom_element(['class']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('class', 'hello world');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ instance.classList.remove('foo');
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'class', oldValue: 'hello world', newValue: 'hello world', namespace: null});
+}, 'remove on DOMTokenList must enqueue an attributeChanged reaction even when removing a non-existent value from an attribute');
+
+test(function () {
+ var element = define_new_custom_element(['title']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('class', 'hello world');
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ instance.classList.remove('world');
+ assert_array_equals(element.takeLog().types(), []);
+}, 'remove on DOMTokenList must not enqueue an attributeChanged reaction when removing a value from an unobserved attribute');
+
+test(function () {
+ var element = define_new_custom_element(['class']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('class', 'hello');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ instance.classList.toggle('world');
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'class', oldValue: 'hello', newValue: 'hello world', namespace: null});
+}, 'toggle on DOMTokenList must enqueue an attributeChanged reaction when adding a value to an attribute');
+
+test(function () {
+ var element = define_new_custom_element(['class']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('class', 'hello world');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ instance.classList.toggle('world');
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'class', oldValue: 'hello world', newValue: 'hello', namespace: null});
+}, 'toggle on DOMTokenList must enqueue an attributeChanged reaction when removing a value from an attribute');
+
+test(function () {
+ var element = define_new_custom_element(['class']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('class', 'hello');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ instance.classList.replace('hello', 'world');
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'class', oldValue: 'hello', newValue: 'world', namespace: null});
+}, 'replace on DOMTokenList must enqueue an attributeChanged reaction when replacing a value in an attribute');
+
+test(function () {
+ var element = define_new_custom_element(['class']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('class', 'hello world');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ instance.classList.replace('foo', 'bar');
+ assert_array_equals(element.takeLog().types(), []);
+}, 'replace on DOMTokenList must not enqueue an attributeChanged reaction when the token to replace does not exist in the attribute');
+
+test(function () {
+ var element = define_new_custom_element(['title']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('class', 'hello');
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ instance.classList.replace('hello', 'world');
+ assert_array_equals(element.takeLog().types(), []);
+}, 'replace on DOMTokenList must not enqueue an attributeChanged reaction when replacing a value in an unobserved attribute');
+
+test(function () {
+ var element = define_new_custom_element(['class']);
+ var instance = document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ instance.classList = 'hello';
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'class', oldValue: null, newValue: 'hello', namespace: null});
+}, 'the stringifier of DOMTokenList must enqueue an attributeChanged reaction when adding an observed attribute');
+
+test(function () {
+ var element = define_new_custom_element(['id']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('class', 'hello');
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ instance.classList = 'hello';
+ var logEntries = element.takeLog();
+ assert_array_equals(element.takeLog().types(), []);
+}, 'the stringifier of DOMTokenList must not enqueue an attributeChanged reaction when adding an unobserved attribute');
+
+test(function () {
+ var element = define_new_custom_element(['class']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('class', 'hello');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ instance.classList = 'world';
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'class', oldValue: 'hello', newValue: 'world', namespace: null});
+}, 'the stringifier of DOMTokenList must enqueue an attributeChanged reaction when mutating the value of an observed attribute');
+
+test(function () {
+ var element = define_new_custom_element([]);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('class', 'hello');
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ instance.classList = 'world';
+ assert_array_equals(element.takeLog().types(), []);
+}, 'the stringifier of DOMTokenList must not enqueue an attributeChanged reaction when mutating the value of an unobserved attribute');
+
+test(function () {
+ var element = define_new_custom_element(['class']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('class', 'hello');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ instance.classList = 'hello';
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'class', oldValue: 'hello', newValue: 'hello', namespace: null});
+}, 'the stringifier of DOMTokenList must enqueue an attributeChanged reaction when the setter is called with the original value of the attribute');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/Document.html b/testing/web-platform/tests/custom-elements/reactions/Document.html
new file mode 100644
index 0000000000..1f05982a90
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/Document.html
@@ -0,0 +1,156 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on Document interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="importNode and adoptNode of Document interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#document">
+<meta name="help" content="https://html.spec.whatwg.org/#document">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ const instance = contentDocument.createElement('custom-element');
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+
+ const newDoc = contentDocument.implementation.createHTMLDocument();
+ newDoc.importNode(instance);
+
+ assert_array_equals(element.takeLog().types(), []);
+}, 'importNode on Document must not construct a new custom element when importing a custom element into a window-less document');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ const template = contentDocument.createElement('template');
+ template.innerHTML = '<custom-element></custom-element>';
+ assert_array_equals(element.takeLog().types(), []);
+ contentDocument.importNode(template.content, true);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+}, 'importNode on Document must construct a new custom element when importing a custom element from a template');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ const instance = contentDocument.createElement('custom-element');
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+
+ const newDoc = contentDocument.implementation.createHTMLDocument();
+ newDoc.adoptNode(instance);
+
+ const logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['adopted']);
+ assert_equals(logEntries.last().oldDocument, contentDocument);
+ assert_equals(logEntries.last().newDocument, newDoc);
+}, 'adoptNode on Document must enqueue an adopted reaction when importing a custom element');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ const instance = contentDocument.createElement('custom-element');
+
+ const container = contentDocument.createElement('div');
+ container.contentEditable = true;
+ container.appendChild(instance);
+ contentDocument.body.appendChild(container);
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+
+ container.focus();
+ contentDocument.execCommand('delete', false, null);
+
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'execCommand on Document must enqueue a disconnected reaction when deleting a custom element from a contenteditable element');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+
+ contentDocument.title = '';
+ const title = contentDocument.querySelector('title');
+ const instance = contentDocument.createElement('custom-element');
+ title.appendChild(instance);
+ instance.textContent = 'hello';
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(title.innerHTML, '<custom-element>hello</custom-element>');
+
+ title.text = 'world';
+ assert_equals(title.innerHTML, 'world');
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'title on Document must enqueue disconnectedCallback when removing a custom element');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+
+ const body = contentDocument.body;
+ body.innerHTML = '<custom-element>hello</custom-element>';
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(body.innerHTML, '<custom-element>hello</custom-element>');
+
+ contentDocument.body = contentDocument.createElement('body');
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'body on Document must enqueue disconnectedCallback when removing a custom element');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+
+ const instance = contentDocument.createElement('custom-element');
+ const body = contentDocument.createElement('body');
+ body.appendChild(instance);
+
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ assert_equals(body.innerHTML, '<custom-element></custom-element>');
+
+ contentDocument.body = body;
+ assert_array_equals(element.takeLog().types(), ['connected']);
+}, 'body on Document must enqueue connectedCallback when inserting a custom element');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = '<custom-element></custom-element>';
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+
+ contentDocument.open();
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'open on Document must enqueue disconnectedCallback when removing a custom element');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = '<custom-element></custom-element>';
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+
+ contentDocument.write('');
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'write on Document must enqueue disconnectedCallback when removing a custom element');
+
+test_with_window(function (contentWindow, contentDocument) {
+ contentWindow.document.open();
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentWindow.document.write('<custom-element></custom-element>');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+}, 'write on Document must enqueue connectedCallback after constructing a custom element');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = '<custom-element></custom-element>';
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+
+ contentDocument.writeln('');
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'writeln on Document must enqueue disconnectedCallback when removing a custom element');
+
+test_with_window(function (contentWindow) {
+ contentWindow.document.open();
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentWindow.document.writeln('<custom-element></custom-element>');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+}, 'writeln on Document must enqueue connectedCallback after constructing a custom element');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/Element.html b/testing/web-platform/tests/custom-elements/reactions/Element.html
new file mode 100644
index 0000000000..e1576734d0
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/Element.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on Element interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="id, className, slot, setAttribute, setAttributeNS, removeAttribute, removeAttributeNS, setAttributeNode, setAttributeNodeNS, removeAttributeNode, insertAdjacentElement, innerHTML, outerHTML, and insertAdjacentHTML of Element interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#element">
+<meta name="help" content="https://w3c.github.io/DOM-Parsing/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+testReflectAttribute('id', 'id', 'foo', 'bar', 'id on Element');
+testReflectAttribute('className', 'class', 'foo', 'bar', 'className on Element');
+testReflectAttribute('slot', 'slot', 'foo', 'bar', 'slot on Element');
+
+testAttributeAdder(function (element, name, value) {
+ element.setAttribute(name, value);
+}, 'setAttribute on Element');
+
+testAttributeAdder(function (element, name, value) {
+ element.setAttributeNS(null, name, value);
+}, 'setAttributeNS on Element');
+
+testAttributeRemover(function (element, name) {
+ element.removeAttribute(name);
+}, 'removeAttribute on Element');
+
+testAttributeRemover(function (element, name) {
+ element.removeAttributeNS(null, name);
+}, 'removeAttributeNS on Element');
+
+testAttributeRemover(function (element, name, value) {
+ if (element.hasAttribute(name))
+ element.toggleAttribute(name);
+}, 'toggleAttribute (only removes) on Element');
+
+testAttributeRemover(function (element, name, value) {
+ element.toggleAttribute(name, false);
+}, 'toggleAttribute (force false) on Element');
+
+testAttributeAdder(function (element, name, value) {
+ var attr = document.createAttribute(name);
+ attr.value = value;
+ element.setAttributeNode(attr);
+}, 'setAttributeNode on Element');
+
+testAttributeAdder(function (element, name, value) {
+ var attr = document.createAttribute(name);
+ attr.value = value;
+ element.setAttributeNodeNS(attr);
+}, 'setAttributeNodeNS on Element');
+
+testAttributeRemover(function (element, name) {
+ var attr = element.getAttributeNode(name);
+ if (attr)
+ element.removeAttributeNode(element.getAttributeNode(name));
+}, 'removeAttributeNode on Element');
+
+testNodeConnector(function (newContainer, element) {
+ newContainer.insertAdjacentElement('afterBegin', element);
+}, 'insertAdjacentElement on Element');
+
+testInsertingMarkup(function (newContainer, markup) {
+ newContainer.innerHTML = markup;
+}, 'innerHTML on Element');
+
+testNodeDisconnector(function (customElement) {
+ customElement.parentNode.innerHTML = '';
+}, 'innerHTML on Element');
+
+testInsertingMarkup(function (newContainer, markup) {
+ newContainer.firstChild.outerHTML = markup;
+}, 'outerHTML on Element');
+
+testNodeDisconnector(function (customElement) {
+ customElement.outerHTML = '';
+}, 'outerHTML on Element');
+
+testInsertingMarkup(function (newContainer, markup) {
+ newContainer.insertAdjacentHTML('afterBegin', markup);
+}, 'insertAdjacentHTML on Element');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/ElementContentEditable.html b/testing/web-platform/tests/custom-elements/reactions/ElementContentEditable.html
new file mode 100644
index 0000000000..bdb10761cb
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/ElementContentEditable.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on ElementContentEditable interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="contentEditable of ElementContentEditable interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#elementcontenteditable">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+testReflectAttribute('contentEditable', 'contenteditable', 'true', 'false', 'contentEditable on ElementContentEditable');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/HTMLAnchorElement.html b/testing/web-platform/tests/custom-elements/reactions/HTMLAnchorElement.html
new file mode 100644
index 0000000000..c6eeb1dce9
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/HTMLAnchorElement.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on HTMLAnchorElement interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="text of HTMLAnchorElement interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#node">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<a><custom-element>hello</custom-element></a>`;
+ const anchor = contentDocument.querySelector('a');
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(anchor.innerHTML, '<custom-element>hello</custom-element>');
+
+ anchor.text = 'world';
+ assert_equals(anchor.innerHTML, 'world');
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'text on HTMLAnchorElement must enqueue disconnectedCallback when removing a custom element');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/HTMLElement.html b/testing/web-platform/tests/custom-elements/reactions/HTMLElement.html
new file mode 100644
index 0000000000..0a1d40199e
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/HTMLElement.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on HTMLElement interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="title, lang, translate, dir, hidden, tabIndex, accessKey, draggable, dropzone, contextMenu, spellcheck, popover, innerText, and outerText of HTMLElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#htmlelement">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+testReflectAttribute('title', 'title', 'foo', 'bar', 'title on HTMLElement');
+testReflectAttribute('lang', 'lang', 'en', 'zh', 'lang on HTMLElement');
+testReflectAttributeWithContentValues('translate', 'translate', true, 'yes', false, 'no', 'translate on HTMLElement');
+testReflectAttribute('dir', 'dir', 'ltr', 'rtl', 'dir on HTMLElement');
+testReflectBooleanAttribute('hidden', 'hidden', 'hidden on HTMLElement');
+testReflectAttribute('tabIndex', 'tabindex', '0', '1', 'tabIndex on HTMLElement');
+testReflectAttribute('accessKey', 'accesskey', 'a', 'b', 'accessKey on HTMLElement');
+testReflectAttributeWithContentValues('draggable', 'draggable', true, 'true', false, 'false', 'draggable on HTMLElement');
+testReflectAttributeWithContentValues('spellcheck', 'spellcheck', true, 'true', false, 'false', 'spellcheck on HTMLElement');
+testReflectAttribute('popover', 'popover', 'auto', 'manual', 'popover on HTMLElement');
+
+testNodeDisconnector(function (customElement) {
+ customElement.parentNode.innerText = '';
+}, 'innerText on HTMLElement');
+
+testNodeDisconnector(function (customElement) {
+ customElement.outerText = '';
+}, 'outerText on HTMLElement');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/HTMLOptionElement.html b/testing/web-platform/tests/custom-elements/reactions/HTMLOptionElement.html
new file mode 100644
index 0000000000..418ef282b3
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/HTMLOptionElement.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on HTMLOptionElement interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="text of HTMLOptionElement interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#node">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<select><option></option></select>`;
+ const option = contentDocument.querySelector('option');
+ const instance = document.createElement('custom-element');
+ option.appendChild(instance);
+ instance.textContent = 'hello';
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(option.innerHTML, '<custom-element>hello</custom-element>');
+
+ option.text = 'world';
+ assert_equals(option.innerHTML, 'world');
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'text on HTMLOptionElement must enqueue disconnectedCallback when removing a custom element');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/HTMLOptionsCollection.html b/testing/web-platform/tests/custom-elements/reactions/HTMLOptionsCollection.html
new file mode 100644
index 0000000000..0d64259d06
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/HTMLOptionsCollection.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on HTMLOptionsCollection interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="length, the indexed setter, add, and remove of HTMLOptionsCollection interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#node">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<select><option></option></select>`;
+ const option = contentDocument.querySelector('option');
+
+ const instance = contentDocument.createElement(element.name);
+ option.appendChild(instance);
+ instance.textContent = 'hello';
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(option.innerHTML, '<custom-element>hello</custom-element>');
+
+ const select = contentDocument.querySelector('select');
+ assert_equals(select.options[0], option);
+ select.options.length = 0;
+ assert_equals(select.firstChild, null);
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'length on HTMLOptionsCollection must enqueue disconnectedCallback when removing a custom element');
+
+test_with_window(function (contentWindow) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+
+ const contentDocument = contentWindow.document;
+ contentDocument.body.innerHTML = `<select></select>`;
+ const select = contentDocument.querySelector('select');
+
+ const option = contentDocument.createElement('option');
+ const instance = contentDocument.createElement(element.name);
+ option.appendChild(instance);
+ instance.textContent = 'hello';
+
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ assert_equals(option.innerHTML, '<custom-element>hello</custom-element>');
+
+ assert_equals(select.options.length, 0);
+ select.options[0] = option;
+ assert_equals(select.options.length, 1);
+ assert_array_equals(element.takeLog().types(), ['connected']);
+}, 'The indexed setter on HTMLOptionsCollection must enqueue connectedCallback when inserting a custom element');
+
+test_with_window(function (contentWindow) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+
+ const contentDocument = contentWindow.document;
+ contentDocument.body.innerHTML = `<select><option></option></select>`;
+ const option = contentDocument.querySelector('option');
+
+ const instance = contentDocument.createElement(element.name);
+ option.appendChild(instance);
+ instance.textContent = 'hello';
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(option.innerHTML, '<custom-element>hello</custom-element>');
+
+ const select = contentDocument.querySelector('select');
+ assert_equals(select.options[0], option);
+ select.options[0] = null;
+ assert_equals(select.options.length, 0);
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'The indexed setter on HTMLOptionsCollection must enqueue disconnectedCallback when removing a custom element');
+
+test_with_window(function (contentWindow) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+
+ const contentDocument = contentWindow.document;
+ contentDocument.body.innerHTML = `<select></select>`;
+ const select = contentDocument.querySelector('select');
+
+ const option = contentDocument.createElement('option');
+ const instance = contentDocument.createElement(element.name);
+ option.appendChild(instance);
+ instance.textContent = 'hello';
+
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ assert_equals(option.innerHTML, '<custom-element>hello</custom-element>');
+
+ assert_equals(select.options.length, 0);
+ select.options.add(option);
+ assert_equals(select.options.length, 1);
+ assert_array_equals(element.takeLog().types(), ['connected']);
+}, 'add on HTMLOptionsCollection must enqueue connectedCallback when inserting a custom element');
+
+test_with_window(function (contentWindow) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+
+ const contentDocument = contentWindow.document;
+ contentDocument.body.innerHTML = `<select><option></option></select>`;
+ const option = contentDocument.querySelector('option');
+
+ const instance = contentDocument.createElement(element.name);
+ option.appendChild(instance);
+ instance.textContent = 'hello';
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(option.innerHTML, '<custom-element>hello</custom-element>');
+
+ const select = contentDocument.querySelector('select');
+ assert_equals(select.options[0], option);
+ select.options.remove(0);
+ assert_equals(select.options.length, 0);
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'remove on HTMLOptionsCollection must enqueue disconnectedCallback when removing a custom element');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/HTMLOutputElement.html b/testing/web-platform/tests/custom-elements/reactions/HTMLOutputElement.html
new file mode 100644
index 0000000000..02e669bc7a
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/HTMLOutputElement.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on HTMLOutputElement interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="value and defaultValue of HTMLOutputElement interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#node">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<output><custom-element>hello</custom-element></output>`;
+ const anchor = contentDocument.querySelector('output');
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(anchor.innerHTML, '<custom-element>hello</custom-element>');
+
+ anchor.value = 'world';
+ assert_equals(anchor.innerHTML, 'world');
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'value on HTMLOutputElement must enqueue disconnectedCallback when removing a custom element');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<output><custom-element>hello</custom-element></output>`;
+ const anchor = contentDocument.querySelector('output');
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(anchor.innerHTML, '<custom-element>hello</custom-element>');
+
+ anchor.defaultValue = 'world';
+ assert_equals(anchor.innerHTML, 'world');
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'defaultValue on HTMLOutputElement must enqueue disconnectedCallback when removing a custom element');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/HTMLSelectElement.html b/testing/web-platform/tests/custom-elements/reactions/HTMLSelectElement.html
new file mode 100644
index 0000000000..7c79634f66
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/HTMLSelectElement.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on HTMLSelectElement interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="length, add, remove, and the setter of HTMLSelectElement interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#node">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<select><option></option></select>`;
+ const option = contentDocument.querySelector('option');
+
+ const instance = contentDocument.createElement(element.name);
+ option.appendChild(instance);
+ instance.textContent = 'hello';
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(option.innerHTML, '<custom-element>hello</custom-element>');
+
+ const select = contentDocument.querySelector('select');
+ assert_equals(select.length, 1);
+ select.length = 0;
+ assert_equals(select.firstChild, null);
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'length on HTMLSelectElement must enqueue disconnectedCallback when removing a custom element');
+
+test_with_window(function (contentWindow) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+
+ const contentDocument = contentWindow.document;
+ contentDocument.body.innerHTML = `<select></select>`;
+ const select = contentDocument.querySelector('select');
+
+ const option = contentDocument.createElement('option');
+ const instance = contentDocument.createElement(element.name);
+ option.appendChild(instance);
+ instance.textContent = 'hello';
+
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ assert_equals(option.innerHTML, '<custom-element>hello</custom-element>');
+
+ assert_equals(select.options.length, 0);
+ select[0] = option;
+ assert_equals(select.options.length, 1);
+ assert_array_equals(element.takeLog().types(), ['connected']);
+}, 'The indexed setter on HTMLSelectElement must enqueue connectedCallback when inserting a custom element');
+
+test_with_window(function (contentWindow) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+
+ const contentDocument = contentWindow.document;
+ contentDocument.body.innerHTML = `<select><option></option></select>`;
+ const option = contentDocument.querySelector('option');
+
+ const instance = contentDocument.createElement(element.name);
+ option.appendChild(instance);
+ instance.textContent = 'hello';
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(option.innerHTML, '<custom-element>hello</custom-element>');
+
+ const select = contentDocument.querySelector('select');
+ assert_equals(select.options[0], option);
+ select[0] = null;
+ assert_equals(select.options.length, 0);
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'The indexed setter on HTMLSelectElement must enqueue disconnectedCallback when removing a custom element');
+
+test_with_window(function (contentWindow) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+
+ const contentDocument = contentWindow.document;
+ contentDocument.body.innerHTML = `<select></select>`;
+ const select = contentDocument.querySelector('select');
+
+ const option = contentDocument.createElement('option');
+ const instance = contentDocument.createElement(element.name);
+ option.appendChild(instance);
+ instance.textContent = 'hello';
+
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ assert_equals(option.innerHTML, '<custom-element>hello</custom-element>');
+
+ assert_equals(select.options.length, 0);
+ select.add(option);
+ assert_equals(select.options.length, 1);
+ assert_array_equals(element.takeLog().types(), ['connected']);
+}, 'add on HTMLSelectElement must enqueue connectedCallback when inserting a custom element');
+
+test_with_window(function (contentWindow) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+
+ const contentDocument = contentWindow.document;
+ contentDocument.body.innerHTML = `<select><option></option></select>`;
+ const option = contentDocument.querySelector('option');
+
+ const instance = contentDocument.createElement(element.name);
+ option.appendChild(instance);
+ instance.textContent = 'hello';
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(option.innerHTML, '<custom-element>hello</custom-element>');
+
+ const select = contentDocument.querySelector('select');
+ assert_equals(select.options[0], option);
+ select.remove(0);
+ assert_equals(select.options.length, 0);
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'remove on HTMLSelectElement must enqueue disconnectedCallback when removing a custom element');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/HTMLTableElement.html b/testing/web-platform/tests/custom-elements/reactions/HTMLTableElement.html
new file mode 100644
index 0000000000..6adf2623d6
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/HTMLTableElement.html
@@ -0,0 +1,173 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on HTMLTableElement interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="caption, deleteCaption, thead, deleteTHead, tFoot, deleteTFoot, and deleteRow of HTMLTableElement interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#node">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<table></table>`;
+ const table = contentDocument.querySelector('table');
+
+ const caption = contentDocument.createElement('caption');
+ caption.innerHTML = '<custom-element>hello</custom-element>';
+
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ assert_equals(caption.innerHTML, '<custom-element>hello</custom-element>');
+
+ assert_equals(table.caption, null);
+ table.caption = caption;
+ assert_array_equals(element.takeLog().types(), ['connected']);
+}, 'caption on HTMLTableElement must enqueue connectedCallback when inserting a custom element');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<table><caption><custom-element>hello</custom-element></caption></table>`;
+ const caption = contentDocument.querySelector('caption');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(caption.innerHTML, '<custom-element>hello</custom-element>');
+
+ const table = contentDocument.querySelector('table');
+ assert_equals(table.caption, caption);
+ const newCaption = contentDocument.createElement('caption');
+ table.caption = newCaption; // Chrome doesn't support setting to null.
+ assert_equals(table.caption, newCaption);
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'caption on HTMLTableElement must enqueue disconnectedCallback when removing a custom element');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<table><caption><custom-element>hello</custom-element></caption></table>`;
+ const caption = contentDocument.querySelector('caption');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(caption.innerHTML, '<custom-element>hello</custom-element>');
+
+ const table = contentDocument.querySelector('table');
+ assert_equals(table.caption, caption);
+ const newCaption = contentDocument.createElement('caption');
+ table.deleteCaption();
+ assert_equals(table.caption, null);
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'deleteCaption() on HTMLTableElement must enqueue disconnectedCallback when removing a custom element');
+
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<table></table>`;
+ const table = contentDocument.querySelector('table');
+
+ const thead = contentDocument.createElement('thead');
+ thead.innerHTML = '<tr><td><custom-element>hello</custom-element></td></tr>';
+
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ assert_equals(thead.innerHTML, '<tr><td><custom-element>hello</custom-element></td></tr>');
+
+ assert_equals(table.tHead, null);
+ table.tHead = thead;
+ assert_array_equals(element.takeLog().types(), ['connected']);
+}, 'tHead on HTMLTableElement must enqueue connectedCallback when inserting a custom element');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<table><thead><tr><td><custom-element>hello</custom-element></td></tr></thead></table>`;
+ const thead = contentDocument.querySelector('thead');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(thead.innerHTML, '<tr><td><custom-element>hello</custom-element></td></tr>');
+
+ const table = contentDocument.querySelector('table');
+ assert_equals(table.tHead, thead);
+ const newThead = contentDocument.createElement('thead');
+ table.tHead = newThead; // Chrome doesn't support setting to null.
+ assert_equals(table.tHead, newThead);
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'tHead on HTMLTableElement must enqueue disconnectedCallback when removing a custom element');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<table><thead><tr><td><custom-element>hello</custom-element></td></tr></thead></table>`;
+ const thead = contentDocument.querySelector('thead');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(thead.innerHTML, '<tr><td><custom-element>hello</custom-element></td></tr>');
+
+ const table = contentDocument.querySelector('table');
+ assert_equals(table.tHead, thead);
+ table.deleteTHead();
+ assert_equals(table.tHead, null);
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'deleteTHead() on HTMLTableElement must enqueue disconnectedCallback when removing a custom element');
+
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<table></table>`;
+ const table = contentDocument.querySelector('table');
+
+ const tfoot = contentDocument.createElement('tfoot');
+ tfoot.innerHTML = '<tr><td><custom-element>hello</custom-element></td></tr>';
+
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ assert_equals(tfoot.innerHTML, '<tr><td><custom-element>hello</custom-element></td></tr>');
+
+ assert_equals(table.tFoot, null);
+ table.tFoot = tfoot;
+ assert_array_equals(element.takeLog().types(), ['connected']);
+}, 'tFoot on HTMLTableElement must enqueue connectedCallback when inserting a custom element');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<table><tfoot><tr><td><custom-element>hello</custom-element></td></tr></tfoot></table>`;
+ const tfoot = contentDocument.querySelector('tfoot');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(tfoot.innerHTML, '<tr><td><custom-element>hello</custom-element></td></tr>');
+
+ const table = contentDocument.querySelector('table');
+ assert_equals(table.tFoot, tfoot);
+ const newThead = contentDocument.createElement('tfoot');
+ table.tFoot = newThead; // Chrome doesn't support setting to null.
+ assert_equals(table.tFoot, newThead);
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'tFoot on HTMLTableElement must enqueue disconnectedCallback when removing a custom element');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<table><tfoot><tr><td><custom-element>hello</custom-element></td></tr></tfoot></table>`;
+ const tfoot = contentDocument.querySelector('tfoot');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(tfoot.innerHTML, '<tr><td><custom-element>hello</custom-element></td></tr>');
+
+ const table = contentDocument.querySelector('table');
+ assert_equals(table.tFoot, tfoot);
+ table.deleteTFoot();
+ assert_equals(table.tFoot, null);
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'deleteTFoot() on HTMLTableElement must enqueue disconnectedCallback when removing a custom element');
+
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<table><tr><td><custom-element>hello</custom-element></td></tr></table>`;
+ const tr = contentDocument.querySelector('tr');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(tr.innerHTML, '<td><custom-element>hello</custom-element></td>');
+
+ const table = contentDocument.querySelector('table');
+ assert_equals(table.rows.length, 1);
+ assert_equals(table.rows[0], tr);
+ table.deleteRow(0);
+ assert_equals(table.rows.length, 0);
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'deleteRow() on HTMLTableElement must enqueue disconnectedCallback when removing a custom element');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/HTMLTableRowElement.html b/testing/web-platform/tests/custom-elements/reactions/HTMLTableRowElement.html
new file mode 100644
index 0000000000..a9a00a5da3
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/HTMLTableRowElement.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on HTMLTableRowElement interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="deleteCell of HTMLTableRowElement interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#node">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<table><tr><td><custom-element>hello</custom-element></td></tr></table>`;
+ const td = contentDocument.querySelector('td');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(td.innerHTML, '<custom-element>hello</custom-element>');
+
+ const table = contentDocument.querySelector('table');
+ const row = table.rows[0];
+ assert_equals(row.cells[0], td);
+ row.deleteCell(0);
+ assert_equals(row.cells.length, 0);
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'deleteCell() on HTMLTableRowElement must enqueue disconnectedCallback when removing a custom element');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/HTMLTableSectionElement.html b/testing/web-platform/tests/custom-elements/reactions/HTMLTableSectionElement.html
new file mode 100644
index 0000000000..cbb0a146e8
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/HTMLTableSectionElement.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on HTMLTableSectionElement interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="deleteRow of HTMLTableSectionElement interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#node">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<table><thead><tr><td><custom-element>hello</custom-element></td></tr></thead></table>`;
+ const thead = contentDocument.querySelector('thead');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(thead.innerHTML, '<tr><td><custom-element>hello</custom-element></td></tr>');
+
+ const table = contentDocument.querySelector('table');
+ assert_equals(table.tHead, thead);
+ table.tHead.deleteRow(0);
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'deleteRow() on HTMLTableSectionElement on thead must enqueue disconnectedCallback when removing a custom element');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ contentDocument.body.innerHTML = `<table><tfoot><tr><td><custom-element>hello</custom-element></td></tr></tfoot></table>`;
+ const tfoot = contentDocument.querySelector('tfoot');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ assert_equals(tfoot.innerHTML, '<tr><td><custom-element>hello</custom-element></td></tr>');
+
+ const table = contentDocument.querySelector('table');
+ assert_equals(table.tFoot, tfoot);
+ table.tFoot.deleteRow(0);
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'deleteRow() on HTMLTableSectionElement on tfoot must enqueue disconnectedCallback when removing a custom element');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/HTMLTitleElement.html b/testing/web-platform/tests/custom-elements/reactions/HTMLTitleElement.html
new file mode 100644
index 0000000000..6678944c91
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/HTMLTitleElement.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on HTMLTitleElement interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="text of HTMLTitleElement interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#node">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ const instance = contentWindow.document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+
+ contentWindow.document.title = 'hello';
+ const titleElement = contentDocument.querySelector('title');
+ titleElement.appendChild(instance);
+ assert_array_equals(element.takeLog().types(), ['connected']);
+ assert_equals(titleElement.childNodes.length, 2);
+
+ titleElement.text = 'world';
+ assert_equals(titleElement.childNodes.length, 1);
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+}, 'text on HTMLTitleElement must enqueue disconnectedCallback when removing a custom element');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/NamedNodeMap.html b/testing/web-platform/tests/custom-elements/reactions/NamedNodeMap.html
new file mode 100644
index 0000000000..fa21b3ada9
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/NamedNodeMap.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on NamedNodeMap interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="setNamedItem, setNamedItemNS, removeNameditem, and removeNamedItemNS of NamedNodeMap interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#node">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+testAttributeAdder(function (element, name, value) {
+ var attr = element.ownerDocument.createAttribute(name);
+ attr.value = value;
+ element.attributes.setNamedItem(attr);
+}, 'setNamedItem on NamedNodeMap');
+
+testAttributeAdder(function (element, name, value) {
+ var attr = element.ownerDocument.createAttribute(name);
+ attr.value = value;
+ element.attributes.setNamedItemNS(attr);
+}, 'setNamedItemNS on NamedNodeMap');
+
+testAttributeRemover(function (element, name) {
+ element.attributes.removeNamedItem(name);
+}, 'removeNamedItem on NamedNodeMap', {onlyExistingAttribute: true});
+
+testAttributeRemover(function (element, name) {
+ element.attributes.removeNamedItemNS(null, name);
+}, 'removeNamedItemNS on NamedNodeMap', {onlyExistingAttribute: true});
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/Node.html b/testing/web-platform/tests/custom-elements/reactions/Node.html
new file mode 100644
index 0000000000..94da3d020e
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/Node.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on Node interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="nodeValue, textContent, normalize, cloneNode, insertBefore, appendChild, replaceChild, and removeChild of Node interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#node">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+testAttributeMutator(function (element, name, value) {
+ element.getAttributeNode(name).nodeValue = value;
+}, 'nodeValue on Node');
+
+testAttributeMutator(function (element, name, value) {
+ element.getAttributeNode(name).textContent = value;
+}, 'textContent on Node');
+
+// FIXME: Add a test for normalize()
+
+testCloner(function (customElement) {
+ return customElement.cloneNode(false);
+}, 'cloneNode on Node');
+
+testNodeConnector(function (newContainer, customElement) {
+ newContainer.insertBefore(customElement, newContainer.firstChild);
+}, 'insertBefore on ChildNode');
+
+testNodeConnector(function (newContainer, customElement) {
+ newContainer.appendChild(customElement);
+}, 'appendChild on ChildNode');
+
+testNodeConnector(function (newContainer, customElement) {
+ newContainer.replaceChild(customElement, newContainer.firstChild);
+}, 'replaceChild on ChildNode');
+
+testNodeDisconnector(function (customElement) {
+ customElement.parentNode.removeChild(customElement);
+}, 'removeChild on ChildNode');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/ParentNode.html b/testing/web-platform/tests/custom-elements/reactions/ParentNode.html
new file mode 100644
index 0000000000..b143b5a982
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/ParentNode.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on ParentNode interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="prepend and append of ParentNode interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#parentnode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+testNodeConnector(function (newContainer, customElement) {
+ newContainer.prepend(customElement);
+}, 'prepend on ParentNode');
+
+testNodeConnector(function (newContainer, customElement) {
+ newContainer.append(customElement);
+}, 'append on ParentNode');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/Range.html b/testing/web-platform/tests/custom-elements/reactions/Range.html
new file mode 100644
index 0000000000..c4a8252ff6
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/Range.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on Range interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="deleteContents, extractContents, cloneContents, insertNode, and surroundContents of Range interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#node">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+testNodeDisconnector(function (customElement) {
+ var range = document.createRange();
+ range.selectNode(customElement);
+ range.deleteContents();
+}, 'deleteContents on Range');
+
+testNodeDisconnector(function (customElement) {
+ var range = document.createRange();
+ range.selectNode(customElement);
+ range.extractContents();
+}, 'extractContents on Range');
+
+testCloner(function (customElement) {
+ var range = document.createRange();
+ range.selectNode(customElement);
+ range.cloneContents();
+}, 'cloneContents on Range')
+
+testNodeConnector(function (container, customElement) {
+ var range = document.createRange();
+ range.selectNodeContents(container);
+ range.insertNode(customElement);
+}, 'insertNode on Range');
+
+testNodeConnector(function (container, customElement) {
+ var range = document.createRange();
+ range.selectNodeContents(container);
+ range.surroundContents(customElement);
+}, 'surroundContents on Range');
+
+testParsingMarkup(function (document, markup) {
+ var range = document.createRange();
+ return range.createContextualFragment(markup);
+}, 'createContextualFragment on Range');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/Selection.html b/testing/web-platform/tests/custom-elements/reactions/Selection.html
new file mode 100644
index 0000000000..84214201aa
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/Selection.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on Selection interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="deleteFromDocument of Selection interface must have CEReactions">
+<meta name="help" content="http://w3c.github.io/selection-api/#selection-interface">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+testNodeDisconnector(function (customElement, window) {
+ let selection = window.getSelection();
+ let parent = customElement.parentNode;
+
+ // WebKit and Blink "normalizes" selection in selectAllChildren and not select the empty customElement.
+ // Workaround this orthogonal non-standard behavior by inserting text nodes around the custom element.
+ parent.prepend(document.createTextNode('start'));
+ parent.append(document.createTextNode('end'));
+
+ selection.selectAllChildren(parent);
+ selection.deleteFromDocument();
+}, 'deleteFromDocument on Selection');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/ShadowRoot.html b/testing/web-platform/tests/custom-elements/reactions/ShadowRoot.html
new file mode 100644
index 0000000000..9997d9c836
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/ShadowRoot.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CEReactions on ShadowRoot interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="innerHTML of ShadowRoot interface must have CEReactions">
+<meta name="help" content="https://dom.spec.whatwg.org/#node">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ const host = contentDocument.createElement('div');
+ const shadowRoot = host.attachShadow({mode: 'closed'});
+ shadowRoot.innerHTML = '<custom-element></custom-element>';
+
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+}, 'innerHTML on ShadowRoot must upgrade a custom element');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ const host = contentDocument.createElement('div');
+ contentDocument.body.appendChild(host);
+ const shadowRoot = host.attachShadow({mode: 'closed'});
+ shadowRoot.innerHTML = '<custom-element></custom-element>';
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+}, 'innerHTML on ShadowRoot must enqueue connectedCallback on newly upgraded custom elements when the shadow root is connected');
+
+test_with_window(function (contentWindow, contentDocument) {
+ const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
+ const host = contentDocument.createElement('div');
+ contentDocument.body.appendChild(host);
+
+ const shadowRoot = host.attachShadow({mode: 'closed'});
+ shadowRoot.innerHTML = '<custom-element></custom-element>';
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+
+ shadowRoot.innerHTML = '';
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+
+}, 'innerHTML on ShadowRoot must enqueue disconnectedCallback when removing a custom element');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLAreaElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLAreaElement.html
new file mode 100644
index 0000000000..3d53ff87ff
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLAreaElement.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLAreaElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="alt, coords, shape, target, download, ping, rel,
+ referrerPolicy of HTMLAreaElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-area-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<map name="yellow" id="map">
+</map>
+<img usemap="#yellow" src="/images/yellow.png" alt="yellow pic">
+
+<script>
+
+function getParentElement() {
+ let map = document.getElementById('map');
+ return map;
+}
+
+function setAttributes(instance) {
+ instance.setAttribute('href', '/images/yellow.png');
+}
+
+testReflectAttributeWithDependentAttributes(
+ 'alt', 'alt', 'yellow pic',
+ 'yellow pic2', 'alt on HTMLAreaElement', 'area',
+ getParentElement, instance => setAttributes(instance), HTMLAreaElement
+);
+testReflectAttributeWithParentNode(
+ 'coords', 'coords', '1, 1, 5, 5',
+ '2, 2, 6, 6', 'coords on HTMLAreaElement', 'area',
+ getParentElement, HTMLAreaElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'shape', 'shape', 'rectangle',
+ 'default', 'shape on HTMLAreaElement', 'area',
+ getParentElement, instance => instance.setAttribute('coords', '1, 1, 5, 5'),
+ HTMLAreaElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'target', 'target', '_blank',
+ '_top', 'target on HTMLAreaElement', 'area',
+ getParentElement, instance => setAttributes(instance), HTMLAreaElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'download', 'download', 'pic1',
+ 'pic2', 'download on HTMLAreaElement', 'area',
+ getParentElement, instance => setAttributes(instance), HTMLAreaElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'ping', 'ping', 'location.href',
+ `${location.protocol}\/\/${location.host}`, 'ping on HTMLAreaElement', 'area',
+ getParentElement, instance => setAttributes(instance), HTMLAreaElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'rel', 'rel', 'help',
+ 'noreferrer', 'rel on HTMLAreaElement', 'area',
+ getParentElement, instance => setAttributes(instance), HTMLAreaElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'referrerPolicy', 'referrerpolicy', 'same-origin',
+ 'origin', 'referrerPolicy on HTMLAreaElement', 'area',
+ getParentElement, instance => setAttributes(instance), HTMLAreaElement
+);
+
+</script>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLBaseElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLBaseElement.html
new file mode 100644
index 0000000000..8d8470074c
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLBaseElement.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<head>
+<title>Custom Elements: CEReactions on HTMLBaseElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="href, target of HTMLBaseElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-base-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+</head>
+<script>
+
+function getParentElement() {
+ return document.head;
+}
+
+testReflectAttributeWithParentNode('href', 'href', '/', 'http://example.com/', 'href on HTMLBaseElement', 'base', getParentElement, HTMLBaseElement);
+testReflectAttributeWithParentNode('target', 'target', '_blank', '_self', 'target on HTMLBaseElement', 'base', getParentElement, HTMLBaseElement);
+
+</script>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLButtonElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLButtonElement.html
new file mode 100644
index 0000000000..62f8b7b0c9
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLButtonElement.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLButtonElement interface</title>
+<meta name="author" title="Zhang Xiaoyu" href="xiaoyux.zhang@intel.com">
+<meta name="assert" content=" autofocus, disabled, formAction, formEnctype,
+ formMethod, formNoValidate, formTarget, name, type, value
+ of HTMLButtonElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#htmlbuttonelement">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+<body>
+<script>
+
+function getParentElement(parentElementName) {
+ let parentElement = document.createElement(parentElementName);
+ document.body.appendChild(parentElement);
+ return parentElement;
+}
+
+function setAttributes(instance) {
+ instance.setAttribute('type', 'submit');
+}
+
+testReflectBooleanAttribute(
+ 'autofocus', 'autofocus', 'autofocus on HTMLButtonElement',
+ 'button', HTMLButtonElement
+);
+testReflectBooleanAttribute(
+ 'disabled', 'disabled','disabled on HTMLButtonElement',
+ 'button', HTMLButtonElement
+);
+testReflectAttribute(
+ 'name', 'name', 'intel',
+ 'intel1', 'name on HTMLButtonElement', 'button',
+ HTMLButtonElement
+);
+testReflectAttribute(
+ 'value', 'value', 'HTML',
+ 'CSS', 'value on HTMLButtonElement', 'button',
+ HTMLButtonElement
+);
+testReflectAttributeWithParentNode(
+ 'type', 'type', 'submit',
+ 'reset', 'type on HTMLButtonElement', 'button',
+ () => getParentElement('form'), HTMLButtonElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'formAction', 'formaction', 'intel.asp',
+ 'intel1.asp', 'formAction on HTMLButtonElement', 'button',
+ () => getParentElement('form'), instance => setAttributes(instance),
+ HTMLButtonElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'formEnctype', 'formenctype', 'text/plain', 'multipart/form-data',
+ 'formEnctype on HTMLButtonElement', 'button', () => getParentElement('form'),
+ instance => setAttributes(instance),
+ HTMLButtonElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'formMethod', 'formmethod', 'get',
+ 'post', 'formMethod on HTMLButtonElement', 'button',
+ () => getParentElement('form'), instance => setAttributes(instance),
+ HTMLButtonElement
+);
+testReflectBooleanAttributeWithDependentAttributes(
+ 'formNoValidate', 'formnovalidate', 'formNoValidate on HTMLButtonElement',
+ 'button', () => getParentElement('form'),
+ instance => setAttributes(instance),
+ HTMLButtonElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'formTarget', 'formtarget', '_blank',
+ '_self', 'formTarget on HTMLButtonElement', 'button',
+ () => getParentElement('form'), instance => setAttributes(instance),
+ HTMLButtonElement
+);
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLCanvasElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLCanvasElement.html
new file mode 100644
index 0000000000..6c7119252e
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLCanvasElement.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLCanvasElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="width, height of HTMLCanvasElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-canvas-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<script>
+
+testReflectAttribute('width', 'width', '15', '20', 'width on HTMLCanvasElement', 'canvas', HTMLCanvasElement);
+testReflectAttribute('height', 'height', '23', '45', 'height on HTMLCanvasElement', 'canvas', HTMLCanvasElement);
+
+</script>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLDataElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLDataElement.html
new file mode 100644
index 0000000000..f078c6aa02
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLDataElement.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLDataElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="value of HTMLDataElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-data-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<script>
+
+testReflectAttribute('value', 'value', '1234', '2345', 'name on HTMLDataElement', 'data', HTMLDataElement);
+
+</script>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLDetailsElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLDetailsElement.html
new file mode 100644
index 0000000000..4d81e3e627
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLDetailsElement.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLDetailsElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="open of HTMLDetailsElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-details-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<script>
+
+testReflectBooleanAttribute('open', 'open', 'open on HTMLDetailsElement', 'details', HTMLDetailsElement);
+
+</script>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLEmbedElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLEmbedElement.html
new file mode 100644
index 0000000000..923bde7583
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLEmbedElement.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLEmbedElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="src, type, width, height of
+ HTMLEmbedElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-embed-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<script>
+
+testReflectAttribute(
+ 'src', 'src', '/media/movie_5.mp4',
+ '/media/sound_5.mp3', 'src on HTMLEmbedElement', 'embed',
+ HTMLEmbedElement
+);
+testReflectAttribute(
+ 'type', 'type', 'video/webm',
+ 'video/mp4', 'type on HTMLEmbedElement', 'embed',
+ HTMLEmbedElement
+);
+testReflectAttribute(
+ 'width', 'width', '100',
+ '120', 'width on HTMLEmbedElement', 'embed',
+ HTMLEmbedElement
+);
+testReflectAttribute(
+ 'height', 'height', '100',
+ '120', 'height on HTMLEmbedElement', 'embed',
+ HTMLEmbedElement
+);
+
+</script>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLFieldSetElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLFieldSetElement.html
new file mode 100644
index 0000000000..517523551b
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLFieldSetElement.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLFieldSetElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="disabled, name of
+ HTMLFieldSetElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-fieldset-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<body>
+<script>
+
+function getParentElement() {
+ let form = document.createElement("form");
+ document.body.appendChild(form);
+ return form;
+}
+
+testReflectBooleanAttributeWithParentNode(
+ 'disabled', 'disabled', 'disabled on HTMLFieldSetElement',
+ 'fieldset', getParentElement, HTMLFieldSetElement
+);
+testReflectAttributeWithParentNode(
+ 'name', 'name', 'fieldset1',
+ 'fieldset2', 'name on HTMLFieldSetElement', 'fieldset',
+ getParentElement, HTMLFieldSetElement
+);
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLImageElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLImageElement.html
new file mode 100644
index 0000000000..656e29eb17
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLImageElement.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLImageElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="alt, src, srcset, sizes, crossOrigin, useMap,
+ isMap, width, height, referrerPolicy, decoding of
+ HTMLImageElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-img-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<map name="yellow"></map>
+<map name="green"></map>
+<a href="/" id="a">
+</a>
+<body>
+<script>
+
+function getParentElement() {
+ return document.body;
+}
+
+function setAttributes(instance) {
+ instance.setAttribute('src', '/images/green-1x1.png');
+}
+
+testReflectAttributeWithDependentAttributes(
+ 'alt', 'alt', 'image1',
+ 'image2', 'alt on HTMLImageElement', 'img',
+ getParentElement, instance => setAttributes(instance), HTMLImageElement
+);
+testReflectAttributeWithParentNode(
+ 'src', 'src', '/images/green-1x1.png',
+ '/images/green-2x2.png', 'src on HTMLImageElement', 'img',
+ getParentElement, HTMLImageElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'srcset', 'srcset', '/images/green.png',
+ '/images/green-2x2.png', 'srcset on HTMLImageElement', 'img',
+ getParentElement, instance => setAttributes(instance), HTMLImageElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'sizes', 'sizes', '(max-width: 32px) 28px',
+ '(max-width: 48px) 44px', 'sizes on HTMLImageElement', 'img',
+ getParentElement, instance => {
+ instance.setAttribute('src', '/images/green-1x1.png');
+ instance.setAttribute('srcset', '/images/green-2x2.png 1x');
+ }, HTMLImageElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'crossOrigin', 'crossorigin', 'use-credentials',
+ 'anonymous', 'crossOrigin on HTMLImageElement', 'img',
+ getParentElement, instance => setAttributes(instance), HTMLImageElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'useMap', 'usemap', '#yellow',
+ '#green', 'useMap on HTMLImageElement', 'img',
+ getParentElement, instance => setAttributes(instance), HTMLImageElement
+);
+testReflectBooleanAttributeWithDependentAttributes(
+ 'isMap', 'ismap', 'isMap on HTMLImageElement',
+ 'img', () => { return document.getElementById('a') },
+ instance => setAttributes(instance),
+ HTMLImageElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'width', 'width', '1',
+ '2', 'width on HTMLImageElement', 'img',
+ getParentElement, instance => setAttributes(instance), HTMLImageElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'height', 'height', '1',
+ '2', 'height on HTMLImageElement', 'img',
+ getParentElement, instance => setAttributes(instance), HTMLImageElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'referrerPolicy', 'referrerpolicy', 'same-origin',
+ 'origin', 'referrerPolicy on HTMLImageElement', 'img',
+ getParentElement, instance => setAttributes(instance), HTMLImageElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'decoding', 'decoding', 'async',
+ 'sync', 'decoding on HTMLImageElement', 'img',
+ getParentElement, instance => setAttributes(instance), HTMLImageElement
+);
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLInputElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLInputElement.html
new file mode 100644
index 0000000000..adf43ee74d
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLInputElement.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Custom Elements: CEReactions on HTMLInputElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="author" title="Wanming Lin" href="mailto:wanming.lin@intel.com">
+<meta name="assert" content="capture of HTMLInputElement interface must have CEReactions">
+<meta name="help" content="https://www.w3.org/TR/html-media-capture/#the-capture-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+<body>
+<script>
+if ('capture' in HTMLInputElement.prototype) {
+ test(() => {
+ const element = define_build_in_custom_element(['capture'], HTMLInputElement, 'input');
+ const instance = document.createElement('input', { is: element.name });
+
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ instance['capture'] = 'user';
+ const logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'capture', oldValue: null, newValue: 'user', namespace: null});
+ }, 'capture on HTMLInputElement must enqueue an attributeChanged reaction when adding new attribute');
+
+ test(() => {
+ const element = define_build_in_custom_element(['capture'], HTMLInputElement, 'input');
+ const instance = document.createElement('input', { is: element.name });
+
+ instance['capture'] = 'user';
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ instance['capture'] = 'environment';
+ const logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'capture', oldValue: 'user', newValue: 'environment', namespace: null});
+ }, 'capture on HTMLInputElement must enqueue an attributeChanged reaction when replacing an existing attribute');
+
+ test(() => {
+ const element = define_build_in_custom_element(['capture'], HTMLInputElement, 'input');
+ const instance = document.createElement('input', { is: element.name });
+
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ instance['capture'] = 'asdf';
+ const logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'capture', oldValue: null, newValue: 'asdf', namespace: null});
+ }, 'capture on HTMLInputElement must enqueue an attributeChanged reaction when adding invalid value default');
+
+ test(() => {
+ const element = define_build_in_custom_element(['capture'], HTMLInputElement, 'input');
+ const instance = document.createElement('input', { is: element.name });
+
+ instance['capture'] = 'user';
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ instance['capture'] = '';
+ const logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'capture', oldValue: 'user', newValue: '', namespace: null});
+ }, 'capture on HTMLInputElement must enqueue an attributeChanged reaction when removing the attribute');
+} else {
+ // testharness.js doesn't allow a test file with no tests.
+ test(() => {
+ }, 'No tests if HTMLInputEement has no "capture" IDL attribute');
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLLIElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLLIElement.html
new file mode 100644
index 0000000000..adba2addf6
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLLIElement.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLLIElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert"
+ content="value of HTMLLIElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-li-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<body>
+<script>
+
+function getParentElement(parentElementName) {
+ let parentElement = document.createElement(parentElementName);
+ document.body.appendChild(parentElement);
+ return parentElement;
+}
+
+testReflectAttributeWithParentNode(
+ 'value', 'value', '3',
+ '5', 'value on HTMLLIElement in ol', 'li',
+ () => getParentElement('ol'), HTMLLIElement
+);
+testReflectAttributeWithParentNode(
+ 'value', 'value', '3',
+ '5', 'value on HTMLLIElement in ul', 'li',
+ () => getParentElement('ul'), HTMLLIElement
+);
+testReflectAttributeWithParentNode(
+ 'value', 'value', '3',
+ '5', 'value on HTMLLIElement in menu', 'li',
+ () => getParentElement('menu'), HTMLLIElement
+);
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLLabelElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLLabelElement.html
new file mode 100644
index 0000000000..2fe2741dad
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLLabelElement.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLLabelElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert"
+ content="htmlFor of HTMLLabelElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-label-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<form id="form">
+ <input type="radio" name="gender" id="male" value="male">
+ <input type="radio" name="gender" id="female" value="female">
+</form>
+<script>
+
+function getParentElement() {
+ let parentElement = document.getElementById("form");
+ return parentElement;
+}
+
+testReflectAttributeWithParentNode(
+ 'htmlFor', 'for', 'male',
+ 'female', 'htmlFor on HTMLLabelElement', 'label',
+ getParentElement, HTMLLabelElement
+);
+
+</script>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLMapElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLMapElement.html
new file mode 100644
index 0000000000..5b2e674afb
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLMapElement.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLMapElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="name of HTMLMapElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-map-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<img usemap="#yellow" src="/images/yellow.png" alt="yellow pic">
+<img usemap="#green" src="/images/green.png" alt="green pic">
+<script>
+
+testReflectAttribute('name', 'name', 'yellow', 'green', 'name on HTMLMapElement', 'map', HTMLMapElement);
+
+</script>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLMediaElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLMediaElement.html
new file mode 100644
index 0000000000..58e002c52c
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLMediaElement.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLMediaElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="src, crossOrigin, preload, autoplay, loop,
+ controls, defaultMuted of HTMLMediaElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#media-elements">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<body>
+<script>
+
+function getParentElement() {
+ return document.body;
+}
+
+function setAttributes(instance, value) {
+ instance.setAttribute('src', value);
+}
+
+testReflectAttribute(
+ 'src', 'src', '/media/sound_0.mp3',
+ '/media/sound_5.mp3', 'src on HTMLMediaElement in audio', 'audio',
+ HTMLAudioElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'crossOrigin', 'crossorigin', 'use-credentials',
+ 'anonymous', 'crossOrigin on HTMLMediaElement in audio', 'audio',
+ getParentElement, instance => setAttributes(instance, '/media/sound_5.mp3'),
+ HTMLAudioElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'preload', 'preload', 'auto',
+ 'none', 'preload on HTMLMediaElement in audio', 'audio',
+ getParentElement, instance => setAttributes(instance, '/media/sound_5.mp3'),
+ HTMLAudioElement
+);
+testReflectBooleanAttributeWithDependentAttributes(
+ 'autoplay', 'autoplay', 'autoplay on HTMLMediaElement in audio',
+ 'audio', getParentElement,
+ instance => setAttributes(instance, '/media/sound_5.mp3'),
+ HTMLAudioElement
+);
+testReflectBooleanAttributeWithDependentAttributes(
+ 'loop', 'loop', 'loop on HTMLMediaElement in audio',
+ 'audio', getParentElement,
+ instance => setAttributes(instance, '/media/sound_5.mp3'), HTMLAudioElement
+);
+testReflectBooleanAttributeWithDependentAttributes(
+ 'controls', 'controls', 'controls on HTMLMediaElement in audio',
+ 'audio', getParentElement,
+ instance => setAttributes(instance, '/media/sound_5.mp3'),
+ HTMLAudioElement
+);
+testReflectBooleanAttributeWithDependentAttributes(
+ 'defaultMuted', 'muted', 'defaultMuted on HTMLMediaElement in audio',
+ 'audio', getParentElement,
+ instance => setAttributes(instance, '/media/sound_5.mp3'),
+ HTMLAudioElement
+);
+
+testReflectAttribute(
+ 'src', 'src', '/media/video.ogv',
+ '/media/movie_5.mp4', 'src on HTMLMediaElement in video', 'video',
+ HTMLVideoElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'crossOrigin', 'crossorigin', 'use-credentials',
+ 'anonymous', 'crossOrigin on HTMLMediaElement in video', 'video',
+ getParentElement, instance => setAttributes(instance, '/media/movie_5.mp4'),
+ HTMLVideoElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'preload', 'preload', 'auto',
+ 'none', 'preload on HTMLMediaElement in video', 'video',
+ getParentElement, instance => setAttributes(instance, '/media/movie_5.mp4'),
+ HTMLVideoElement
+);
+testReflectBooleanAttributeWithDependentAttributes(
+ 'autoplay', 'autoplay', 'autoplay on HTMLMediaElement in video',
+ 'video', getParentElement,
+ instance => setAttributes(instance, '/media/movie_5.mp4'),
+ HTMLVideoElement
+);
+testReflectBooleanAttributeWithDependentAttributes(
+ 'loop', 'loop', 'loop on HTMLMediaElement in video',
+ 'video', getParentElement,
+ instance => setAttributes(instance, '/media/movie_5.mp4'),
+ HTMLVideoElement
+);
+testReflectBooleanAttributeWithDependentAttributes(
+ 'controls', 'controls', 'controls on HTMLMediaElement in video',
+ 'video', getParentElement,
+ instance => setAttributes(instance, '/media/movie_5.mp4'),
+ HTMLVideoElement
+);
+testReflectBooleanAttributeWithDependentAttributes(
+ 'defaultMuted', 'muted', 'defaultMuted on HTMLMediaElement in video',
+ 'video', getParentElement,
+ instance => setAttributes(instance, '/media/movie_5.mp4'),
+ HTMLVideoElement
+);
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLMetaElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLMetaElement.html
new file mode 100644
index 0000000000..b6e8c06546
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLMetaElement.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLMetaElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="name, httpEquiv, content of
+ HTMLMetaElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-meta-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<script>
+
+function getParentElement() {
+ return document.head;
+}
+
+function setAttributes(instance, attribute, value) {
+ instance.setAttribute(attribute, value);
+}
+
+testReflectAttributeWithDependentAttributes(
+ 'name', 'name', 'description',
+ 'keywords', 'name on HTMLMetaElement', 'meta',
+ getParentElement,
+ instance => setAttributes(instance, 'content', 'HTMLMetaElement'),
+ HTMLMetaElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'content', 'content', 'name1',
+ 'name2', 'content on HTMLMetaElement', 'meta',
+ getParentElement, instance => setAttributes(instance, 'name', 'author'),
+ HTMLMetaElement
+);
+
+test(() => {
+ let element = define_build_in_custom_element(
+ ['http-equiv'], HTMLMetaElement, 'meta'
+ );
+ let instance = document.createElement('meta', { is: element.name });
+
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ document.head.appendChild(instance);
+ assert_array_equals(element.takeLog().types(), ['connected']);
+ instance['content'] = '300';
+ instance['httpEquiv'] = 'refresh';
+ let logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {
+ name: 'http-equiv', oldValue: null, newValue: 'refresh', namespace: null
+ });
+}, 'httpEquiv on HTMLMetaElement must enqueue an attributeChanged'
+ + ' reaction when adding a new attribute');
+
+test(() => {
+ let element = define_build_in_custom_element(
+ ['http-equiv'], HTMLMetaElement, 'meta'
+ );
+ let instance = document.createElement('meta', { is: element.name });
+ document.head.appendChild(instance);
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ instance['content'] = 'text/html; charset=UTF-8';
+ instance['httpEquiv'] = 'content-type';
+ assert_array_equals(element.takeLog().types(), ['attributeChanged']);
+ instance['content'] = '300';
+ instance['httpEquiv'] = 'refresh';
+ let logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {
+ name: 'http-equiv', oldValue: 'content-type',
+ newValue: 'refresh', namespace: null
+ });
+}, 'httpEquiv on HTMLMetaElement must enqueue an attributeChanged'
+ + ' reaction when replacing an existing attribute');
+
+</script>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLMeterElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLMeterElement.html
new file mode 100644
index 0000000000..707e56c605
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLMeterElement.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLMeterElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="value, min, max, low, high, optimum of
+ HTMLMeterElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-meter-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<body>
+<script>
+
+function getParentElement() {
+ return document.body;
+}
+
+function setAttributes(instance) {
+ instance.setAttribute('value', '0.6');
+}
+
+testReflectAttribute(
+ 'value', 'value', '0.3',
+ '0.4', 'value on HTMLMeterElement', 'meter',
+ HTMLMeterElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'min', 'min', '0.1',
+ '0.2', 'min on HTMLMeterElement', 'meter',
+ getParentElement, instance => setAttributes(instance), HTMLMeterElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'max', 'max', '2',
+ '3', 'max on HTMLMeterElement', 'meter',
+ getParentElement, instance => setAttributes(instance), HTMLMeterElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'low', 'low', '0.1',
+ '0.2', 'low on HTMLMeterElement', 'meter',
+ getParentElement, instance => setAttributes(instance), HTMLMeterElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'high', 'high', '2',
+ '3', 'high on HTMLMeterElement', 'meter',
+ getParentElement, instance => setAttributes(instance), HTMLMeterElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'optimum', 'optimum', '0.3',
+ '0.4', 'optimum on HTMLMeterElement', 'meter',
+ getParentElement, instance => setAttributes(instance), HTMLMeterElement
+);
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLModElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLModElement.html
new file mode 100644
index 0000000000..850fe170a5
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLModElement.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLModElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="cite, dateTime of HTMLModElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#attributes-common-to-ins-and-del-elements">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+
+<script>
+
+testReflectAttribute('cite', 'cite', '../resources/custom-elements-helpers.js', './resources/reactions.js', 'cite on ins use HTMLModElement', 'ins', HTMLModElement);
+testReflectAttribute('dateTime', 'datetime', '2018-12-19 00:00Z', '2018-12-20 00:00Z', 'dateTime on ins use HTMLModElement', 'ins', HTMLModElement);
+testReflectAttribute('cite', 'cite', '../resources/custom-elements-helpers.js', './resources/reactions.js', 'cite on del use HTMLModElement', 'del', HTMLModElement);
+testReflectAttribute('dateTime', 'datetime', '2018-10-11T01:25-07:00', '2018-10-12T01:25-07:00', 'dateTime on del use HTMLModElement', 'del', HTMLModElement);
+
+</script>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLOListElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLOListElement.html
new file mode 100644
index 0000000000..b62f31b489
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLOListElement.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLOListElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="reversed, start, type of HTMLOListElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-ol-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<script>
+
+testReflectBooleanAttribute('reversed', 'reversed', 'reversed on HTMLOListElement', 'ol', HTMLOListElement);
+testReflectAttribute('start', 'start', '2', '5', 'start on HTMLOListElement', 'ol', HTMLOListElement);
+testReflectAttribute('type', 'type', '1', 'a', 'type on HTMLOListElement', 'ol', HTMLOListElement);
+
+</script>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLOptGroupElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLOptGroupElement.html
new file mode 100644
index 0000000000..afa31bb465
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLOptGroupElement.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLOptGroupElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="disabled, label of
+ HTMLOptGroupElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-optgroup-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<script src="./resources/reactions.js"></script>
+
+<body>
+<script>
+
+function getParentElement() {
+ let element = document.createElement('select');
+ document.body.appendChild(element);
+ return element;
+}
+
+function setAttributes(instance) {
+ instance.setAttribute('label', 'group1');
+}
+
+testReflectBooleanAttributeWithDependentAttributes(
+ 'disabled', 'disabled', 'disabled on HTMLOptGroupElement',
+ 'optgroup', getParentElement, instance => setAttributes(instance),
+ HTMLOptGroupElement
+);
+
+testReflectAttributeWithParentNode(
+ 'label', 'label', 'group1',
+ 'group2', 'label on HTMLOptGroupElement', 'optgroup',
+ getParentElement, HTMLOptGroupElement
+);
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLParamElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLParamElement.html
new file mode 100644
index 0000000000..eb3a13962f
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLParamElement.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLParamElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="name, value of HTMLParamElement
+ interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-param-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<body>
+<script>
+
+function getParentElement() {
+ let element = document.createElement('object');
+ element['type'] = 'image/png';
+ element['data'] = '/images/blue.png';
+ document.body.appendChild(element);
+ return element;
+}
+
+function setAttributes(instance, attribute, value) {
+ instance.setAttribute(attribute, value);
+}
+
+testReflectAttributeWithDependentAttributes(
+ 'name', 'name', 'image1',
+ 'image2', 'name on HTMLParamElement', 'param',
+ getParentElement, instance => setAttributes(instance, 'value', 'blue'),
+ HTMLParamElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'value', 'value', 'blue1',
+ 'blue2', 'value on HTMLParamElement', 'param',
+ getParentElement, instance => setAttributes(instance, 'name', 'image'),
+ HTMLParamElement
+);
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLProgressElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLProgressElement.html
new file mode 100644
index 0000000000..42683f4ede
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLProgressElement.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLProgressElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="value, max of HTMLProgressElement
+ interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-progress-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<script>
+
+testReflectAttribute(
+ 'value', 'value', '0.15',
+ '0.2', 'value on HTMLProgressElement', 'progress',
+ HTMLProgressElement
+);
+testReflectAttribute(
+ 'max', 'max', '2',
+ '4', 'max on HTMLProgressElement', 'progress',
+ HTMLProgressElement
+);
+
+</script>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLQuoteElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLQuoteElement.html
new file mode 100644
index 0000000000..f9c3d7538c
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLQuoteElement.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLQuoteElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="cite of HTMLQuoteElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-blockquote-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<script>
+
+testReflectAttribute('cite', 'cite', '../resources/custom-elements-helpers.js', './resources/reactions.js', 'cite on blockquote use HTMLQuoteElement', 'blockquote', HTMLQuoteElement);
+testReflectAttribute('cite', 'cite', '../resources/custom-elements-helpers.js', './resources/reactions.js', 'cite on q use HTMLQuoteElement', 'q', HTMLQuoteElement);
+
+</script>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLSlotElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLSlotElement.html
new file mode 100644
index 0000000000..56871873b4
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLSlotElement.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLSlotElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="name of HTMLSlotElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-slot-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<script>
+
+testReflectAttribute('name', 'name', 'slot1', 'slot2', 'name on HTMLSlotElement', 'slot', HTMLSlotElement);
+
+</script>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLSourceElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLSourceElement.html
new file mode 100644
index 0000000000..f7d567ebcb
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLSourceElement.html
@@ -0,0 +1,191 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLSourceElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="src, type, srcset, sizes, media of
+ HTMLSourceElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-source-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<video controls id='video' width='5' height='5'></video>
+<picture id='pic'>
+ <img src='/images/green-1x1.png'>
+</picture>
+<body>
+<script>
+
+function getParentElement(id) {
+ let element = document.getElementById(id);
+ return element;
+}
+
+testReflectAttributeWithParentNode(
+ 'src', 'src', '/media/video.ogv',
+ '/media/white.mp4', 'src on HTMLSourceElement', 'source',
+ () => getParentElement('video'), HTMLSourceElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'type', 'type', 'video/mp4; codecs="mp4v.20.240, mp4a.40.2"',
+ 'video/mp4; codecs="mp4v.20.8, mp4a.40.2"', 'type on HTMLSourceElement',
+ 'source',
+ () => getParentElement('video'),
+ instance => instance.setAttribute('src', '/media/white.mp4'), HTMLSourceElement
+);
+
+function testReflectAttributeWithContentValuesAndParentNode(
+ jsAttributeName, contentAttributeName, validValue1,
+ contentValue1, validValue2, contentValue2,
+ name, elementName, getParentElement,
+ interfaceName) {
+
+ let parentElement = getParentElement();
+
+ test(() => {
+ let element = define_build_in_custom_element(
+ [contentAttributeName], interfaceName, elementName
+ );
+ let instance = document.createElement(elementName, { is: element.name });
+
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ // source element as a child of a picture element, before the img element
+ parentElement.prepend(instance);
+ assert_array_equals(element.takeLog().types(), ['connected']);
+ instance[jsAttributeName] = validValue1;
+ let logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(
+ logEntries.last(),
+ { name: contentAttributeName, oldValue: null,
+ newValue: contentValue1, namespace: null
+ }
+ );
+ }, name + ' must enqueue an attributeChanged reaction when adding a new attribute');
+
+ test(() => {
+ let element = define_build_in_custom_element(
+ [contentAttributeName], interfaceName, elementName
+ );
+ let instance = document.createElement(elementName, { is: element.name });
+ // source element as a child of a picture element, before the img element
+ parentElement.prepend(instance);
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ instance[jsAttributeName] = validValue1;
+ assert_array_equals(element.takeLog().types(), ['attributeChanged']);
+ instance[jsAttributeName] = validValue2;
+ let logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(
+ logEntries.last(),
+ { name: contentAttributeName, oldValue: contentValue1,
+ newValue: contentValue2, namespace: null
+ }
+ );
+ }, name + ' must enqueue an attributeChanged reaction when replacing an existing attribute');
+}
+
+function testReflectAttributeWithParentNode(
+ jsAttributeName, contentAttributeName, validValue1,
+ validValue2, name, elementName,
+ getParentElement, interfaceName) {
+
+ testReflectAttributeWithContentValuesAndParentNode(
+ jsAttributeName, contentAttributeName, validValue1,
+ validValue1, validValue2, validValue2,
+ name, elementName, getParentElement,
+ interfaceName
+ );
+}
+
+function testReflectAttributeWithContentValuesAndDependentAttributes(
+ jsAttributeName, contentAttributeName, validValue1,
+ contentValue1, validValue2, contentValue2,
+ name, elementName, getParentElement,
+ setAttributes, interfaceName) {
+
+ let parentElement = getParentElement();
+
+ test(() => {
+ let element = define_build_in_custom_element(
+ [contentAttributeName], interfaceName, elementName
+ );
+ let instance = document.createElement(elementName, { is: element.name });
+
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ // source element as a child of a picture element, before the img element
+ parentElement.prepend(instance);
+ assert_array_equals(element.takeLog().types(), ['connected']);
+ setAttributes(instance);
+ instance[jsAttributeName] = validValue1;
+ let logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(
+ logEntries.last(),
+ { name: contentAttributeName, oldValue: null,
+ newValue: contentValue1, namespace: null
+ }
+ );
+ }, name + ' must enqueue an attributeChanged reaction when adding a new attribute');
+
+ test(() => {
+ let element = define_build_in_custom_element(
+ [contentAttributeName], interfaceName, elementName
+ );
+ let instance = document.createElement(elementName, { is: element.name });
+ // source element as a child of a picture element, before the img element
+ parentElement.prepend(instance);
+ setAttributes(instance);
+ instance[jsAttributeName] = validValue1;
+
+ assert_array_equals(
+ element.takeLog().types(),
+ ['constructed', 'connected', 'attributeChanged']
+ );
+ instance[jsAttributeName] = validValue2;
+ let logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(
+ logEntries.last(),
+ { name: contentAttributeName, oldValue: contentValue1,
+ newValue: contentValue2, namespace: null
+ }
+ );
+ }, name + ' must enqueue an attributeChanged reaction when replacing an existing attribute');
+}
+
+function testReflectAttributeWithDependentAttributes(
+ jsAttributeName, contentAttributeName, validValue1,
+ validValue2, name, elementName,
+ getParentElement, setAttributes, interfaceName) {
+
+ testReflectAttributeWithContentValuesAndDependentAttributes(
+ jsAttributeName, contentAttributeName, validValue1,
+ validValue1, validValue2, validValue2,
+ name, elementName, getParentElement,
+ setAttributes, interfaceName);
+}
+
+testReflectAttributeWithParentNode(
+ 'srcset', 'srcset', '/images/green.png',
+ '/images/green-1x1.png', 'srcset on HTMLSourceElement', 'source',
+ () => getParentElement('pic'), HTMLSourceElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'sizes', 'sizes', '(max-width: 32px) 28px',
+ '(max-width: 48px) 44px', 'sizes on HTMLSourceElement', 'source',
+ () => getParentElement('pic'),
+ instance => instance.setAttribute('srcset', '/images/green.png 3x'),
+ HTMLSourceElement
+);
+testReflectAttributeWithDependentAttributes(
+ 'media', 'media', '(max-width: 7px)',
+ '(max-width: 9px)', 'media on HTMLSourceElement', 'source',
+ () => getParentElement('pic'),
+ instance => instance.setAttribute('srcset', '/images/green.png 3x'),
+ HTMLSourceElement
+);
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLStyleElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLStyleElement.html
new file mode 100644
index 0000000000..d68d5cb76d
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLStyleElement.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLStyleElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="media of HTMLStyleElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-style-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<script>
+
+function getParentElement() {
+ return document.head;
+}
+
+testReflectAttributeWithParentNode(
+ 'media', 'media', 'print',
+ 'screen', 'media on HTMLStyleElement', 'style',
+ getParentElement, HTMLStyleElement
+);
+
+</script>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLTableCellElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLTableCellElement.html
new file mode 100644
index 0000000000..95a8459df8
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLTableCellElement.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLTableCellElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="colSpan, rowSpan, headers, scope, abbr of
+ HTMLTableCellElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-td-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<table><tr id="colSpan"></table>
+<table><tr id="rowSpan"><tr><tr></table>
+<table><tr><th id="id1"><th id="id2"><tr id="td_headers"><tr id="th_headers"></table>
+<script>
+
+function getParentElement(id) {
+ let parentElement = document.getElementById(id);
+ return parentElement;
+}
+
+testReflectAttributeWithParentNode(
+ 'colSpan', 'colspan', '2',
+ '3', 'colSpan on HTMLTableCellElement in td', 'td',
+ () => getParentElement('colSpan'), HTMLTableCellElement
+);
+testReflectAttributeWithParentNode(
+ 'colSpan', 'colspan', '2',
+ '3', 'colSpan on HTMLTableCellElement in th', 'th',
+ () => getParentElement('colSpan'), HTMLTableCellElement
+);
+testReflectAttributeWithParentNode(
+ 'rowSpan', 'rowspan', '2',
+ '3', 'rowSpan on HTMLTableCellElement in td', 'td',
+ () => getParentElement('rowSpan'), HTMLTableCellElement
+);
+testReflectAttributeWithParentNode(
+ 'rowSpan', 'rowspan', '2',
+ '3', 'rowSpan on HTMLTableCellElement in th', 'th',
+ () => getParentElement('rowSpan'), HTMLTableCellElement
+);
+testReflectAttributeWithParentNode(
+ 'headers', 'headers', 'id1',
+ 'id2', 'headers on HTMLTableCellElement in td', 'td',
+ () => getParentElement('td_headers'), HTMLTableCellElement
+);
+testReflectAttributeWithParentNode(
+ 'headers', 'headers', 'id1',
+ 'id2', 'headers on HTMLTableCellElement in th', 'th',
+ () => getParentElement('th_headers'), HTMLTableCellElement
+);
+testReflectAttributeWithParentNode(
+ 'scope', 'scope', 'row',
+ 'col', 'scope on HTMLTableCellElement in th', 'th',
+ () => getParentElement('colSpan'), HTMLTableCellElement
+);
+testReflectAttributeWithParentNode(
+ 'abbr', 'abbr', 'Model1',
+ 'Model2', 'abbr on HTMLTableCellElement in th', 'th',
+ () => getParentElement('colSpan'), HTMLTableCellElement
+);
+
+</script>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLTableColElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLTableColElement.html
new file mode 100644
index 0000000000..8e4d1359d8
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLTableColElement.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLTableColElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="span of HTMLTableColElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-colgroup-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<table id="tbl"></table>
+<script>
+
+function getParentElement() {
+ let parentElement = document.getElementById('tbl');
+ return parentElement;
+}
+
+testReflectAttributeWithParentNode(
+ 'span', 'span', '2',
+ '1', 'span on HTMLTableColElement', 'colgroup',
+ getParentElement, HTMLTableColElement
+);
+
+</script>
diff --git a/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLTimeElement.html b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLTimeElement.html
new file mode 100644
index 0000000000..b2f4cc8af7
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/customized-builtins/HTMLTimeElement.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Custom Elements: CEReactions on HTMLTimeElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="name of HTMLTimeElement interface must have CEReactions">
+<meta name="help" content="https://html.spec.whatwg.org/#the-time-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/custom-elements-helpers.js"></script>
+<script src="../resources/reactions.js"></script>
+
+<script>
+
+testReflectAttribute('dateTime', 'datetime', '2018-12-10', '2018-12-12', 'dateTime on HTMLTimeElement', 'time', HTMLTimeElement);
+
+</script>
diff --git a/testing/web-platform/tests/custom-elements/reactions/resources/reactions.js b/testing/web-platform/tests/custom-elements/reactions/resources/reactions.js
new file mode 100644
index 0000000000..5ed32a4fa4
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/resources/reactions.js
@@ -0,0 +1,452 @@
+
+let testNumber = 1;
+
+function testNodeConnector(testFunction, name) {
+ let container = document.createElement('div');
+ container.appendChild(document.createElement('div'));
+ document.body.appendChild(container);
+
+ test(function () {
+ var element = define_new_custom_element();
+ var instance = document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ testFunction(container, instance);
+ assert_array_equals(element.takeLog().types(), ['connected']);
+ }, name + ' must enqueue a connected reaction');
+
+ test(function () {
+ var element = define_new_custom_element();
+ var instance = document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ var newDoc = document.implementation.createHTMLDocument();
+ testFunction(container, instance);
+ assert_array_equals(element.takeLog().types(), ['connected']);
+ testFunction(newDoc.documentElement, instance);
+ assert_array_equals(element.takeLog().types(), ['disconnected', 'adopted', 'connected']);
+ }, name + ' must enqueue a disconnected reaction, an adopted reaction, and a connected reaction when the custom element was in another document');
+
+ container.parentNode.removeChild(container);
+}
+
+function testNodeDisconnector(testFunction, name) {
+ let container = document.createElement('div');
+ container.appendChild(document.createElement('div'));
+ document.body.appendChild(container);
+
+ test(function () {
+ var element = define_new_custom_element();
+ var instance = document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ container.appendChild(instance);
+ assert_array_equals(element.takeLog().types(), ['connected']);
+ testFunction(instance, window);
+ assert_array_equals(element.takeLog().types(), ['disconnected']);
+ }, name + ' must enqueue a disconnected reaction');
+
+ container.parentNode.removeChild(container);
+}
+
+function testInsertingMarkup(testFunction, name) {
+ let container = document.createElement('div');
+ container.appendChild(document.createElement('div'));
+ document.body.appendChild(container);
+
+ test(function () {
+ var element = define_new_custom_element();
+ testFunction(container, `<${element.name}></${element.name}>`);
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ }, name + ' must enqueue a connected reaction for a newly constructed custom element');
+
+ test(function () {
+ var element = define_new_custom_element(['title']);
+ testFunction(container, `<${element.name} id="hello" title="hi"></${element.name}>`);
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['constructed', 'attributeChanged', 'connected']);
+ assert_attribute_log_entry(logEntries[1], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
+ }, name + ' must enqueue a attributeChanged reaction for a newly constructed custom element');
+
+ container.parentNode.removeChild(container);
+}
+
+function testParsingMarkup(testFunction, name) {
+ test(function () {
+ var element = define_new_custom_element(['id']);
+ assert_array_equals(element.takeLog().types(), []);
+ var instance = testFunction(document, `<${element.name} id="hello" class="foo"></${element.name}>`);
+ assert_equals(Object.getPrototypeOf(instance.querySelector(element.name)), element.class.prototype);
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['constructed', 'attributeChanged']);
+ assert_attribute_log_entry(logEntries[1], {name: 'id', oldValue: null, newValue: 'hello', namespace: null});
+ }, name + ' must construct a custom element');
+}
+
+function testCloner(testFunction, name) {
+ let container = document.createElement('div');
+ container.appendChild(document.createElement('div'));
+ document.body.appendChild(container);
+
+ test(function () {
+ var element = define_new_custom_element(['id']);
+ var instance = document.createElement(element.name);
+ container.appendChild(instance);
+
+ instance.setAttribute('id', 'foo');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected', 'attributeChanged']);
+ var newInstance = testFunction(instance);
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['constructed', 'attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'id', oldValue: null, newValue: 'foo', namespace: null});
+ }, name + ' must enqueue an attributeChanged reaction when cloning an element with an observed attribute');
+
+ test(function () {
+ var element = define_new_custom_element(['id']);
+ var instance = document.createElement(element.name);
+ container.appendChild(instance);
+
+ instance.setAttribute('lang', 'en');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ var newInstance = testFunction(instance);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ }, name + ' must not enqueue an attributeChanged reaction when cloning an element with an unobserved attribute');
+
+ test(function () {
+ var element = define_new_custom_element(['title', 'class']);
+ var instance = document.createElement(element.name);
+ container.appendChild(instance);
+
+ instance.setAttribute('lang', 'en');
+ instance.className = 'foo';
+ instance.setAttribute('title', 'hello world');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected', 'attributeChanged', 'attributeChanged']);
+ var newInstance = testFunction(instance);
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['constructed', 'attributeChanged', 'attributeChanged']);
+ assert_attribute_log_entry(logEntries[1], {name: 'class', oldValue: null, newValue: 'foo', namespace: null});
+ assert_attribute_log_entry(logEntries[2], {name: 'title', oldValue: null, newValue: 'hello world', namespace: null});
+ }, name + ' must enqueue an attributeChanged reaction when cloning an element only for observed attributes');
+}
+
+function testReflectAttributeWithContentValues(jsAttributeName, contentAttributeName, validValue1, contentValue1, validValue2, contentValue2, name, elementName, interfaceName) {
+ test(function () {
+ if (elementName === undefined) {
+ var element = define_new_custom_element([contentAttributeName]);
+ var instance = document.createElement(element.name);
+ } else {
+ var element = define_build_in_custom_element([contentAttributeName], interfaceName, elementName);
+ var instance = document.createElement(elementName, { is: element.name });
+ }
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ instance[jsAttributeName] = validValue1;
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+
+ assert_attribute_log_entry(logEntries.last(), {name: contentAttributeName, oldValue: null, newValue: contentValue1, namespace: null});
+ }, name + ' must enqueue an attributeChanged reaction when adding ' + contentAttributeName + ' content attribute');
+
+ test(function () {
+ if (elementName === undefined) {
+ var element = define_new_custom_element([contentAttributeName]);
+ var instance = document.createElement(element.name);
+ } else {
+ var element = define_build_in_custom_element([contentAttributeName], interfaceName, elementName);
+ var instance = document.createElement(elementName, { is: element.name });
+ }
+ instance[jsAttributeName] = validValue1;
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ instance[jsAttributeName] = validValue2;
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: contentAttributeName, oldValue: contentValue1, newValue: contentValue2, namespace: null});
+ }, name + ' must enqueue an attributeChanged reaction when replacing an existing attribute');
+}
+
+function testReflectAttribute(jsAttributeName, contentAttributeName, validValue1, validValue2, name, elementName, interfaceName) {
+ testReflectAttributeWithContentValues(jsAttributeName, contentAttributeName, validValue1, validValue1, validValue2, validValue2, name, elementName, interfaceName);
+}
+
+function testReflectBooleanAttribute(jsAttributeName, contentAttributeName, name, elementName, interfaceName) {
+ testReflectAttributeWithContentValues(jsAttributeName, contentAttributeName, true, '', false, null, name, elementName, interfaceName);
+}
+
+function testReflectAttributeWithContentValuesAndDependentAttributes(jsAttributeName, contentAttributeName, validValue1, contentValue1, validValue2, contentValue2, name, elementName, getParentElement, setAttributes, interfaceName) {
+ let parentElement = getParentElement();
+
+ test(() => {
+ let element = define_build_in_custom_element([contentAttributeName], interfaceName, elementName);
+ let instance = document.createElement(elementName, { is: element.name });
+
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ parentElement.appendChild(instance);
+ assert_array_equals(element.takeLog().types(), ['connected']);
+ setAttributes(instance);
+ instance[jsAttributeName] = validValue1;
+ let logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), { name: contentAttributeName, oldValue: null, newValue: contentValue1, namespace: null });
+
+ }, name + ' must enqueue an attributeChanged reaction when adding a new attribute');
+
+ test(() => {
+ let element = define_build_in_custom_element([contentAttributeName], interfaceName, elementName);
+ let instance = document.createElement(elementName, { is: element.name });
+ parentElement.appendChild(instance);
+ setAttributes(instance);
+ instance[jsAttributeName] = validValue1;
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected', 'attributeChanged']);
+ instance[jsAttributeName] = validValue2;
+ let logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), { name: contentAttributeName, oldValue: contentValue1, newValue: contentValue2, namespace: null });
+
+ }, name + ' must enqueue an attributeChanged reaction when replacing an existing attribute');
+}
+
+function testReflectAttributeWithDependentAttributes(jsAttributeName, contentAttributeName, validValue1, validValue2, name, elementName, getParentElement, setAttributes, interfaceName) {
+ testReflectAttributeWithContentValuesAndDependentAttributes(jsAttributeName, contentAttributeName, validValue1, validValue1, validValue2, validValue2, name, elementName, getParentElement, setAttributes, interfaceName);
+}
+
+function testReflectBooleanAttributeWithDependentAttributes(jsAttributeName, contentAttributeName, name, elementName, getParentElement, setAttributes, interfaceName) {
+ testReflectAttributeWithContentValuesAndDependentAttributes(jsAttributeName, contentAttributeName, true, '', false, null, name, elementName, getParentElement, setAttributes, interfaceName);
+}
+
+function testReflectAttributeWithContentValuesAndParentNode(jsAttributeName, contentAttributeName, validValue1, contentValue1, validValue2, contentValue2, name, elementName, getParentElement, interfaceName) {
+ let parentElement = getParentElement();
+
+ test(() => {
+ let element = define_build_in_custom_element([contentAttributeName], interfaceName, elementName);
+ let instance = document.createElement(elementName, { is: element.name });
+
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ parentElement.appendChild(instance);
+ assert_array_equals(element.takeLog().types(), ['connected']);
+ instance[jsAttributeName] = validValue1;
+ let logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), { name: contentAttributeName, oldValue: null, newValue: contentValue1, namespace: null });
+}, name + ' must enqueue an attributeChanged reaction when adding a new attribute');
+
+ test(() => {
+ let element = define_build_in_custom_element([contentAttributeName], interfaceName, elementName);
+ let instance = document.createElement(elementName, { is: element.name });
+ parentElement.appendChild(instance);
+
+ assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
+ instance[jsAttributeName] = validValue1;
+ assert_array_equals(element.takeLog().types(), ['attributeChanged']);
+ instance[jsAttributeName] = validValue2;
+ let logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), { name: contentAttributeName, oldValue: contentValue1, newValue: contentValue2, namespace: null });
+ }, name + ' must enqueue an attributeChanged reaction when replacing an existing attribute');
+}
+
+function testReflectAttributeWithParentNode(jsAttributeName, contentAttributeName, validValue1, validValue2, name, elementName, getParentElement, interfaceName) {
+ testReflectAttributeWithContentValuesAndParentNode(jsAttributeName, contentAttributeName, validValue1, validValue1, validValue2, validValue2, name, elementName, getParentElement, interfaceName);
+}
+
+function testReflectBooleanAttributeWithParentNode(jsAttributeName, contentAttributeName, name, elementName, getParentElement, interfaceName) {
+ testReflectAttributeWithContentValuesAndParentNode(jsAttributeName, contentAttributeName, true, '', false, null, name, elementName, getParentElement, interfaceName);
+}
+
+function testAttributeAdder(testFunction, name) {
+ test(function () {
+ var element = define_new_custom_element(['id']);
+ var instance = document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ testFunction(instance, 'id', 'foo');
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'id', oldValue: null, newValue: 'foo', namespace: null});
+ }, name + ' must enqueue an attributeChanged reaction when adding an attribute');
+
+ test(function () {
+ var element = define_new_custom_element(['class']);
+ var instance = document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ testFunction(instance, 'data-lang', 'en');
+ assert_array_equals(element.takeLog().types(), []);
+ }, name + ' must not enqueue an attributeChanged reaction when adding an unobserved attribute');
+
+ test(function () {
+ var element = define_new_custom_element(['title']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('title', 'hello');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ testFunction(instance, 'title', 'world');
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: 'hello', newValue: 'world', namespace: null});
+ }, name + ' must enqueue an attributeChanged reaction when replacing an existing attribute');
+
+ test(function () {
+ var element = define_new_custom_element([]);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('data-lang', 'zh');
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ testFunction(instance, 'data-lang', 'en');
+ assert_array_equals(element.takeLog().types(), []);
+ }, name + ' must enqueue an attributeChanged reaction when replacing an existing unobserved attribute');
+}
+
+function testAttributeMutator(testFunction, name) {
+ test(function () {
+ var element = define_new_custom_element(['title']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('title', 'hello');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ testFunction(instance, 'title', 'world');
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: 'hello', newValue: 'world', namespace: null});
+ }, name + ' must enqueue an attributeChanged reaction when replacing an existing attribute');
+
+ test(function () {
+ var element = define_new_custom_element(['class']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('data-lang', 'zh');
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ testFunction(instance, 'data-lang', 'en');
+ assert_array_equals(element.takeLog().types(), []);
+ }, name + ' must not enqueue an attributeChanged reaction when replacing an existing unobserved attribute');
+}
+
+function testAttributeRemover(testFunction, name, options) {
+ if (options && !options.onlyExistingAttribute) {
+ test(function () {
+ var element = define_new_custom_element(['title']);
+ var instance = document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ testFunction(instance, 'title');
+ assert_array_equals(element.takeLog().types(), []);
+ }, name + ' must not enqueue an attributeChanged reaction when removing an attribute that does not exist');
+ }
+
+ test(function () {
+ var element = define_new_custom_element([]);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('data-lang', 'hello');
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ testFunction(instance, 'data-lang');
+ assert_array_equals(element.takeLog().types(), []);
+ }, name + ' must not enqueue an attributeChanged reaction when removing an unobserved attribute');
+
+ test(function () {
+ var element = define_new_custom_element(['title']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('title', 'hello');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ testFunction(instance, 'title');
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: 'hello', newValue: null, namespace: null});
+ }, name + ' must enqueue an attributeChanged reaction when removing an existing attribute');
+
+ test(function () {
+ var element = define_new_custom_element([]);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('data-lang', 'ja');
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ testFunction(instance, 'data-lang');
+ assert_array_equals(element.takeLog().types(), []);
+ }, name + ' must not enqueue an attributeChanged reaction when removing an existing unobserved attribute');
+}
+
+function test_mutating_style_property_value(testFunction, name, options) {
+ const propertyName = (options || {}).propertyName || 'color';
+ const idlName = (options || {}).idlName || 'color';
+ const value1 = (options || {}).value1 || 'blue';
+ const rule1 = `${propertyName}: ${value1};`;
+ const value2 = (options || {}).value2 || 'red';
+ const rule2 = `${propertyName}: ${value2};`;
+
+ test(function () {
+ var element = define_new_custom_element(['style']);
+ var instance = document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ testFunction(instance, propertyName, idlName, value1);
+ assert_equals(instance.getAttribute('style'), rule1);
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'style', oldValue: null, newValue: rule1, namespace: null});
+ }, name + ' must enqueue an attributeChanged reaction when it adds the observed style attribute');
+
+ test(function () {
+ var element = define_new_custom_element(['title']);
+ var instance = document.createElement(element.name);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ testFunction(instance, propertyName, idlName, value1);
+ assert_equals(instance.getAttribute('style'), rule1);
+ assert_array_equals(element.takeLog().types(), []);
+ }, name + ' must not enqueue an attributeChanged reaction when it adds the style attribute but the style attribute is not observed');
+
+ test(function () {
+ var element = define_new_custom_element(['style']);
+ var instance = document.createElement(element.name);
+ testFunction(instance, propertyName, idlName, value1);
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ testFunction(instance, propertyName, idlName, value2);
+ assert_equals(instance.getAttribute('style'), rule2);
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'style', oldValue: rule1, newValue: rule2, namespace: null});
+ }, name + ' must enqueue an attributeChanged reaction when it mutates the observed style attribute');
+
+ test(function () {
+ var element = define_new_custom_element([]);
+ var instance = document.createElement(element.name);
+ testFunction(instance, propertyName, idlName, value1);
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ testFunction(instance, propertyName, idlName, value2);
+ assert_equals(instance.getAttribute('style'), rule2);
+ assert_array_equals(element.takeLog().types(), []);
+ }, name + ' must not enqueue an attributeChanged reaction when it mutates the style attribute but the style attribute is not observed');
+}
+
+function test_removing_style_property_value(testFunction, name) {
+ test(function () {
+ var element = define_new_custom_element(['style']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('style', 'color: red; display: none;');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ testFunction(instance, 'color', 'color');
+ assert_equals(instance.getAttribute('style'), 'display: none;'); // Don't make this empty since browser behaviors are inconsistent now.
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'style', oldValue: 'color: red; display: none;', newValue: 'display: none;', namespace: null});
+ }, name + ' must enqueue an attributeChanged reaction when it removes a property from the observed style attribute');
+
+ test(function () {
+ var element = define_new_custom_element(['class']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('style', 'color: red; display: none;');
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ testFunction(instance, 'color', 'color');
+ assert_equals(instance.getAttribute('style'), 'display: none;'); // Don't make this empty since browser behaviors are inconsistent now.
+ assert_array_equals(element.takeLog().types(), []);
+ }, name + ' must not enqueue an attributeChanged reaction when it removes a property from the style attribute but the style attribute is not observed');
+}
+
+function test_mutating_style_property_priority(testFunction, name) {
+ test(function () {
+ var element = define_new_custom_element(['style']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('style', 'color: red');
+ assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+ testFunction(instance, 'color', 'color', true);
+ assert_equals(instance.getAttribute('style'), 'color: red !important;');
+ var logEntries = element.takeLog();
+ assert_array_equals(logEntries.types(), ['attributeChanged']);
+ assert_attribute_log_entry(logEntries.last(), {name: 'style', oldValue: 'color: red', newValue: 'color: red !important;', namespace: null});
+ }, name + ' must enqueue an attributeChanged reaction when it makes a property important and the style attribute is observed');
+
+ test(function () {
+ var element = define_new_custom_element(['id']);
+ var instance = document.createElement(element.name);
+ instance.setAttribute('style', 'color: red');
+ assert_array_equals(element.takeLog().types(), ['constructed']);
+ testFunction(instance, 'color', 'color', true);
+ assert_equals(instance.getAttribute('style'), 'color: red !important;');
+ assert_array_equals(element.takeLog().types(), []);
+ }, name + ' must enqueue an attributeChanged reaction when it makes a property important but the style attribute is not observed');
+}
diff --git a/testing/web-platform/tests/custom-elements/reactions/with-exceptions.html b/testing/web-platform/tests/custom-elements/reactions/with-exceptions.html
new file mode 100644
index 0000000000..131348b1c4
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/with-exceptions.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Custom Elements: CEReactions interaction with exceptions</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<meta name="help" content="https://html.spec.whatwg.org/multipage/#cereactions">
+<meta name="help" content="https://github.com/whatwg/html/pull/3235">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+
+<div id="log"></div>
+
+<script>
+"use strict";
+// Basically from https://github.com/whatwg/html/issues/3217#issuecomment-343633273
+test_with_window((contentWindow, contentDocument) => {
+ let reactionRan = false;
+ contentWindow.customElements.define("custom-element", class extends contentWindow.HTMLElement {
+ disconnectedCallback() {
+ reactionRan = true;
+ }
+ });
+ const text = contentDocument.createTextNode("");
+ contentDocument.documentElement.appendChild(text);
+ const element = contentDocument.createElement("custom-element");
+ contentDocument.documentElement.appendChild(element);
+ assert_throws_dom(
+ "HierarchyRequestError",
+ contentWindow.DOMException,
+ () => text.before("", contentDocument.documentElement)
+ );
+ assert_true(reactionRan);
+}, "Reaction must run even after the exception is thrown");
+</script>