Update web-platform-tests to revision e8bfc205e36ad699601212cd50083870bad9a75d

This commit is contained in:
Ms2ger 2016-11-14 11:07:09 +01:00
parent 65dd6d4340
commit ccdb0a3458
1428 changed files with 118036 additions and 9786 deletions

View file

@ -0,0 +1,580 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: CustomElementRegistry interface</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="CustomElementRegistry interface must exist">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="log"></div>
<script>
test(function () {
assert_true('define' in CustomElementRegistry.prototype, '"define" exists on CustomElementRegistry.prototype');
assert_true('define' in customElements, '"define" exists on window.customElements');
}, 'CustomElementRegistry interface must have define as a method');
test(function () {
assert_throws({'name': 'TypeError'}, function () { customElements.define('badname', 1); },
'customElements.define must throw a TypeError when the element interface is a number');
assert_throws({'name': 'TypeError'}, function () { customElements.define('badname', '123'); },
'customElements.define must throw a TypeError when the element interface is a string');
assert_throws({'name': 'TypeError'}, function () { customElements.define('badname', {}); },
'customElements.define must throw a TypeError when the element interface is an object');
assert_throws({'name': 'TypeError'}, function () { customElements.define('badname', []); },
'customElements.define must throw a TypeError when the element interface is an array');
}, 'customElements.define must throw when the element interface is not a constructor');
test(function () {
customElements.define('custom-html-element', HTMLElement);
}, 'customElements.define must not throw the constructor is HTMLElement');
test(function () {
class MyCustomElement extends HTMLElement {};
assert_throws({'name': 'SyntaxError'}, function () { customElements.define(null, MyCustomElement); },
'customElements.define must throw a SyntaxError if the tag name is null');
assert_throws({'name': 'SyntaxError'}, function () { customElements.define('', MyCustomElement); },
'customElements.define must throw a SyntaxError if the tag name is empty');
assert_throws({'name': 'SyntaxError'}, function () { customElements.define('abc', MyCustomElement); },
'customElements.define must throw a SyntaxError if the tag name does not contain "-"');
assert_throws({'name': 'SyntaxError'}, function () { customElements.define('a-Bc', MyCustomElement); },
'customElements.define must throw a SyntaxError if the tag name contains an upper case letter');
var builtinTagNames = [
'annotation-xml',
'color-profile',
'font-face',
'font-face-src',
'font-face-uri',
'font-face-format',
'font-face-name',
'missing-glyph'
];
for (var tagName of builtinTagNames) {
assert_throws({'name': 'SyntaxError'}, function () { customElements.define(tagName, MyCustomElement); },
'customElements.define must throw a SyntaxError if the tag name is "' + tagName + '"');
}
}, 'customElements.define must throw with an invalid name');
test(function () {
class SomeCustomElement extends HTMLElement {};
var calls = [];
var OtherCustomElement = new Proxy(class extends HTMLElement {}, {
get: function (target, name) {
calls.push(name);
return target[name];
}
})
customElements.define('some-custom-element', SomeCustomElement);
assert_throws({'name': 'NotSupportedError'}, function () { customElements.define('some-custom-element', OtherCustomElement); },
'customElements.define must throw a NotSupportedError if the specified tag name is already used');
assert_array_equals(calls, [], 'customElements.define must validate the custom element name before getting the prototype of the constructor');
}, 'customElements.define must throw when there is already a custom element of the same name');
test(function () {
class AnotherCustomElement extends HTMLElement {};
customElements.define('another-custom-element', AnotherCustomElement);
assert_throws({'name': 'NotSupportedError'}, function () { customElements.define('some-other-element', AnotherCustomElement); },
'customElements.define must throw a NotSupportedError if the specified class already defines an element');
}, 'customElements.define must throw a NotSupportedError when there is already a custom element with the same class');
test(function () {
var outerCalls = [];
var OuterCustomElement = new Proxy(class extends HTMLElement { }, {
get: function (target, name) {
outerCalls.push(name);
customElements.define('inner-custom-element', InnerCustomElement);
return target[name];
}
});
var innerCalls = [];
var InnerCustomElement = new Proxy(class extends HTMLElement { }, {
get: function (target, name) {
outerCalls.push(name);
return target[name];
}
});
assert_throws({'name': 'NotSupportedError'}, function () { customElements.define('outer-custom-element', OuterCustomElement); },
'customElements.define must throw a NotSupportedError if the specified class already defines an element');
assert_array_equals(outerCalls, ['prototype'], 'customElements.define must get "prototype"');
assert_array_equals(innerCalls, [],
'customElements.define must throw a NotSupportedError when element definition is running flag is set'
+ ' before getting the prototype of the constructor');
}, 'customElements.define must throw a NotSupportedError when element definition is running flag is set');
test(function () {
var calls = [];
var ElementWithBadInnerConstructor = new Proxy(class extends HTMLElement { }, {
get: function (target, name) {
calls.push(name);
customElements.define('inner-custom-element', 1);
return target[name];
}
});
assert_throws({'name': 'TypeError'}, function () {
customElements.define('element-with-bad-inner-constructor', ElementWithBadInnerConstructor);
}, 'customElements.define must throw a NotSupportedError if IsConstructor(constructor) is false');
assert_array_equals(calls, ['prototype'], 'customElements.define must get "prototype"');
}, 'customElements.define must check IsConstructor on the constructor before checking the element definition is running flag');
test(function () {
var calls = [];
var ElementWithBadInnerName = new Proxy(class extends HTMLElement { }, {
get: function (target, name) {
calls.push(name);
customElements.define('badname', class extends HTMLElement {});
return target[name];
}
});
assert_throws({'name': 'SyntaxError'}, function () {
customElements.define('element-with-bad-inner-name', ElementWithBadInnerName);
}, 'customElements.define must throw a SyntaxError if the specified name is not a valid custom element name');
assert_array_equals(calls, ['prototype'], 'customElements.define must get "prototype"');
}, 'customElements.define must validate the custom element name before checking the element definition is running flag');
test(function () {
var unresolvedElement = document.createElement('constructor-calls-define');
document.body.appendChild(unresolvedElement);
var elementUpgradedDuringUpgrade = document.createElement('defined-during-upgrade');
document.body.appendChild(elementUpgradedDuringUpgrade);
var DefinedDuringUpgrade = class extends HTMLElement { };
class ConstructorCallsDefine extends HTMLElement {
constructor() {
customElements.define('defined-during-upgrade', DefinedDuringUpgrade);
assert_false(unresolvedElement instanceof ConstructorCallsDefine);
assert_true(elementUpgradedDuringUpgrade instanceof DefinedDuringUpgrade);
super();
assert_true(unresolvedElement instanceof ConstructorCallsDefine);
assert_true(elementUpgradedDuringUpgrade instanceof DefinedDuringUpgrade);
}
}
assert_false(unresolvedElement instanceof ConstructorCallsDefine);
assert_false(elementUpgradedDuringUpgrade instanceof DefinedDuringUpgrade);
customElements.define('constructor-calls-define', ConstructorCallsDefine);
}, 'customElements.define unset the element definition is running flag before upgrading custom elements');
(function () {
var testCase = async_test('customElements.define must not throw'
+' when defining another custom element in a different global object during Get(constructor, "prototype")', {timeout: 100});
var iframe = document.createElement('iframe');
iframe.onload = function () {
testCase.step(function () {
var InnerCustomElement = class extends iframe.contentWindow.HTMLElement {};
var calls = [];
var proxy = new Proxy(class extends HTMLElement { }, {
get: function (target, name) {
calls.push(name);
iframe.contentWindow.customElements.define('another-custom-element', InnerCustomElement);
return target[name];
}
})
customElements.define('element-with-inner-element-define', proxy);
assert_array_equals(calls, ['prototype'], 'customElements.define must get "prototype"');
assert_true(iframe.contentDocument.createElement('another-custom-element') instanceof InnerCustomElement);
});
document.body.removeChild(iframe);
testCase.done();
}
document.body.appendChild(iframe);
})();
test(function () {
var calls = [];
var ElementWithBadInnerName = new Proxy(class extends HTMLElement { }, {
get: function (target, name) {
calls.push(name);
customElements.define('badname', class extends HTMLElement {});
return target[name];
}
});
assert_throws({'name': 'SyntaxError'}, function () {
customElements.define('element-with-bad-inner-name', ElementWithBadInnerName);
}, 'customElements.define must throw a SyntaxError if the specified name is not a valid custom element name');
assert_array_equals(calls, ['prototype'], 'customElements.define must get "prototype"');
}, '');
test(function () {
var calls = [];
var proxy = new Proxy(class extends HTMLElement { }, {
get: function (target, name) {
calls.push(name);
return target[name];
}
});
customElements.define('proxy-element', proxy);
assert_array_equals(calls, ['prototype']);
}, 'customElements.define must get "prototype" property of the constructor');
test(function () {
var proxy = new Proxy(class extends HTMLElement { }, {
get: function (target, name) {
throw {name: 'expectedError'};
}
});
assert_throws({'name': 'expectedError'}, function () { customElements.define('element-with-string-prototype', proxy); });
}, 'customElements.define must rethrow an exception thrown while getting "prototype" property of the constructor');
test(function () {
var returnedValue;
var proxy = new Proxy(class extends HTMLElement { }, {
get: function (target, name) { return returnedValue; }
});
returnedValue = null;
assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-string-prototype', proxy); },
'customElements.define must throw when "prototype" property of the constructor is null');
returnedValue = undefined;
assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-string-prototype', proxy); },
'customElements.define must throw when "prototype" property of the constructor is undefined');
returnedValue = 'hello';
assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-string-prototype', proxy); },
'customElements.define must throw when "prototype" property of the constructor is a string');
returnedValue = 1;
assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-string-prototype', proxy); },
'customElements.define must throw when "prototype" property of the constructor is a number');
}, 'customElements.define must throw when "prototype" property of the constructor is not an object');
test(function () {
var constructor = function () {}
var calls = [];
constructor.prototype = new Proxy(constructor.prototype, {
get: function (target, name) {
calls.push(name);
return target[name];
}
});
customElements.define('element-with-proxy-prototype', constructor);
assert_array_equals(calls, ['connectedCallback', 'disconnectedCallback', 'adoptedCallback', 'attributeChangedCallback']);
}, 'customElements.define must get callbacks of the constructor prototype');
test(function () {
var constructor = function () {}
var calls = [];
constructor.prototype = new Proxy(constructor.prototype, {
get: function (target, name) {
calls.push(name);
if (name == 'disconnectedCallback')
throw {name: 'expectedError'};
return target[name];
}
});
assert_throws({'name': 'expectedError'}, function () { customElements.define('element-with-throwing-callback', constructor); });
assert_array_equals(calls, ['connectedCallback', 'disconnectedCallback'],
'customElements.define must not get callbacks after one of the get throws');
}, 'customElements.define must rethrow an exception thrown while getting callbacks on the constructor prototype');
test(function () {
var constructor = function () {}
var calls = [];
constructor.prototype = new Proxy(constructor.prototype, {
get: function (target, name) {
calls.push(name);
if (name == 'adoptedCallback')
return 1;
return target[name];
}
});
assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-throwing-callback', constructor); });
assert_array_equals(calls, ['connectedCallback', 'disconnectedCallback', 'adoptedCallback'],
'customElements.define must not get callbacks after one of the conversion throws');
}, 'customElements.define must rethrow an exception thrown while converting a callback value to Function callback type');
test(function () {
var constructor = function () {}
constructor.prototype.attributeChangedCallback = function () { };
var prototypeCalls = [];
var callOrder = 0;
constructor.prototype = new Proxy(constructor.prototype, {
get: function (target, name) {
if (name == 'prototype' || name == 'observedAttributes')
throw 'Unexpected access to observedAttributes';
prototypeCalls.push(callOrder++);
prototypeCalls.push(name);
return target[name];
}
});
var constructorCalls = [];
var proxy = new Proxy(constructor, {
get: function (target, name) {
constructorCalls.push(callOrder++);
constructorCalls.push(name);
return target[name];
}
});
customElements.define('element-with-attribute-changed-callback', proxy);
assert_array_equals(prototypeCalls, [1, 'connectedCallback', 2, 'disconnectedCallback', 3, 'adoptedCallback', 4, 'attributeChangedCallback']);
assert_array_equals(constructorCalls, [0, 'prototype', 5, 'observedAttributes']);
}, 'customElements.define must get "observedAttributes" property on the constructor prototype when "attributeChangedCallback" is present');
test(function () {
var constructor = function () {}
constructor.prototype.attributeChangedCallback = function () { };
var calls = [];
var proxy = new Proxy(constructor, {
get: function (target, name) {
calls.push(name);
if (name == 'observedAttributes')
throw {name: 'expectedError'};
return target[name];
}
});
assert_throws({'name': 'expectedError'}, function () { customElements.define('element-with-throwing-observed-attributes', proxy); });
assert_array_equals(calls, ['prototype', 'observedAttributes'],
'customElements.define must get "prototype" and "observedAttributes" on the constructor');
}, 'customElements.define must rethrow an exception thrown while getting observedAttributes on the constructor prototype');
test(function () {
var constructor = function () {}
constructor.prototype.attributeChangedCallback = function () { };
var calls = [];
var proxy = new Proxy(constructor, {
get: function (target, name) {
calls.push(name);
if (name == 'observedAttributes')
return 1;
return target[name];
}
});
assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-invalid-observed-attributes', proxy); });
assert_array_equals(calls, ['prototype', 'observedAttributes'],
'customElements.define must get "prototype" and "observedAttributes" on the constructor');
}, 'customElements.define must rethrow an exception thrown while converting the value of observedAttributes to sequence<DOMString>');
test(function () {
var constructor = function () {}
constructor.prototype.attributeChangedCallback = function () { };
constructor.observedAttributes = {[Symbol.iterator]: function *() {
yield 'foo';
throw {name: 'SomeError'};
}};
assert_throws({'name': 'SomeError'}, function () { customElements.define('element-with-generator-observed-attributes', constructor); });
}, 'customElements.define must rethrow an exception thrown while iterating over observedAttributes to sequence<DOMString>');
test(function () {
var constructor = function () {}
constructor.prototype.attributeChangedCallback = function () { };
constructor.observedAttributes = {[Symbol.iterator]: 1};
assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-observed-attributes-with-uncallable-iterator', constructor); });
}, 'customElements.define must rethrow an exception thrown while retrieving Symbol.iterator on observedAttributes');
test(function () {
var constructor = function () {}
constructor.observedAttributes = 1;
customElements.define('element-without-callback-with-invalid-observed-attributes', constructor);
}, 'customElements.define must not throw even if "observedAttributes" fails to convert if "attributeChangedCallback" is not defined');
test(function () {
class MyCustomElement extends HTMLElement {};
customElements.define('my-custom-element', MyCustomElement);
var instance = new MyCustomElement;
assert_true(instance instanceof MyCustomElement,
'An instance of a custom HTML element be an instance of the associated interface');
assert_true(instance instanceof HTMLElement,
'An instance of a custom HTML element must inherit from HTMLElement');
assert_equals(instance.localName, 'my-custom-element',
'An instance of a custom element must use the associated tag name');
assert_equals(instance.namespaceURI, 'http://www.w3.org/1999/xhtml',
'A custom element HTML must use HTML namespace');
}, 'customElements.define must define an instantiatable custom element');
test(function () {
var disconnectedElement = document.createElement('some-custom');
var connectedElementBeforeShadowHost = document.createElement('some-custom');
var connectedElementAfterShadowHost = document.createElement('some-custom');
var elementInShadowTree = document.createElement('some-custom');
var childElementOfShadowHost = document.createElement('some-custom');
var customShadowHost = document.createElement('some-custom');
var elementInNestedShadowTree = document.createElement('some-custom');
var container = document.createElement('div');
var shadowHost = document.createElement('div');
var shadowRoot = shadowHost.attachShadow({mode: 'closed'});
container.appendChild(connectedElementBeforeShadowHost);
container.appendChild(shadowHost);
container.appendChild(connectedElementAfterShadowHost);
shadowHost.appendChild(childElementOfShadowHost);
shadowRoot.appendChild(elementInShadowTree);
shadowRoot.appendChild(customShadowHost);
var innerShadowRoot = customShadowHost.attachShadow({mode: 'closed'});
innerShadowRoot.appendChild(elementInNestedShadowTree);
var calls = [];
class SomeCustomElement extends HTMLElement {
constructor() {
super();
calls.push(this);
}
};
document.body.appendChild(container);
customElements.define('some-custom', SomeCustomElement);
assert_array_equals(calls, [connectedElementBeforeShadowHost, elementInShadowTree, customShadowHost, elementInNestedShadowTree, childElementOfShadowHost, connectedElementAfterShadowHost]);
}, 'customElements.define must upgrade elements in the shadow-including tree order');
test(function () {
assert_true('get' in CustomElementRegistry.prototype, '"get" exists on CustomElementRegistry.prototype');
assert_true('get' in customElements, '"get" exists on window.customElements');
}, 'CustomElementRegistry interface must have get as a method');
test(function () {
assert_equals(customElements.get('a-b'), undefined);
}, 'customElements.get must return undefined when the registry does not contain an entry with the given name');
test(function () {
assert_equals(customElements.get('html'), undefined);
assert_equals(customElements.get('span'), undefined);
assert_equals(customElements.get('div'), undefined);
assert_equals(customElements.get('g'), undefined);
assert_equals(customElements.get('ab'), undefined);
}, 'customElements.get must return undefined when the registry does not contain an entry with the given name even if the name was not a valid custom element name');
test(function () {
assert_equals(customElements.get('existing-custom-element'), undefined);
class ExistingCustomElement extends HTMLElement {};
customElements.define('existing-custom-element', ExistingCustomElement);
assert_equals(customElements.get('existing-custom-element'), ExistingCustomElement);
}, 'customElements.get return the constructor of the entry with the given name when there is a matching entry.');
test(function () {
assert_true(customElements.whenDefined('some-name') instanceof Promise);
}, 'customElements.whenDefined must return a promise for a valid custom element name');
test(function () {
assert_equals(customElements.whenDefined('some-name'), customElements.whenDefined('some-name'));
}, 'customElements.whenDefined must return the same promise each time invoked for a valid custom element name which has not been defined');
promise_test(function () {
var resolved = false;
var rejected = false;
customElements.whenDefined('a-b').then(function () { resolved = true; }, function () { rejected = true; });
return Promise.resolve().then(function () {
assert_false(resolved, 'The promise returned by "whenDefined" must not be resolved until a custom element is defined');
assert_false(rejected, 'The promise returned by "whenDefined" must not be rejected until a custom element is defined');
});
}, 'customElements.whenDefined must return an unresolved promise when the registry does not contain the entry with the given name')
promise_test(function () {
var promise = customElements.whenDefined('badname');
promise.then(function (value) { promise.resolved = value; }, function (value) { promise.rejected = value; });
assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
return Promise.resolve().then(function () {
assert_false('resolved' in promise, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
assert_true('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
});
}, 'customElements.whenDefined must return a rejected promise when the given name is not a valid custom element name');
promise_test(function () {
customElements.define('preexisting-custom-element', class extends HTMLElement { });
var promise = customElements.whenDefined('preexisting-custom-element');
promise.then(function (value) { promise.resolved = value; }, function (value) { promise.rejected = value; });
assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
return Promise.resolve().then(function () {
assert_true('resolved' in promise, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
assert_equals(promise.resolved, undefined,
'The promise returned by "whenDefined" must be resolved with "undefined" when a custom element is defined');
assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
});
}, 'customElements.whenDefined must return a resolved promise when the registry contains the entry with the given name');
promise_test(function () {
class AnotherExistingCustomElement extends HTMLElement {};
customElements.define('another-existing-custom-element', AnotherExistingCustomElement);
var promise1 = customElements.whenDefined('another-existing-custom-element');
var promise2 = customElements.whenDefined('another-existing-custom-element');
promise1.then(function (value) { promise1.resolved = value; }, function (value) { promise1.rejected = value; });
promise2.then(function (value) { promise2.resolved = value; }, function (value) { promise2.rejected = value; });
assert_not_equals(promise1, promise2);
assert_false('resolved' in promise1, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
assert_false('resolved' in promise2, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
assert_false('rejected' in promise1, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
assert_false('rejected' in promise2, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
return Promise.resolve().then(function () {
assert_true('resolved' in promise1, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
assert_equals(promise1.resolved, undefined, 'The promise returned by "whenDefined" must be resolved with "undefined" when a custom element is defined');
assert_false('rejected' in promise1, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
assert_true('resolved' in promise2, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
assert_equals(promise2.resolved, undefined, 'The promise returned by "whenDefined" must be resolved with "undefined" when a custom element is defined');
assert_false('rejected' in promise2, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
});
}, 'customElements.whenDefined must return a new resolved promise each time invoked when the registry contains the entry with the given name');
promise_test(function () {
var promise = customElements.whenDefined('element-defined-after-whendefined');
promise.then(function (value) { promise.resolved = value; }, function (value) { promise.rejected = value; });
assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
var promiseAfterDefine;
return Promise.resolve().then(function () {
assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the element is defined');
assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the element is defined');
assert_equals(customElements.whenDefined('element-defined-after-whendefined'), promise,
'"whenDefined" must return the same unresolved promise before the custom element is defined');
customElements.define('element-defined-after-whendefined', class extends HTMLElement { });
assert_false('resolved' in promise, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
promiseAfterDefine = customElements.whenDefined('element-defined-after-whendefined');
promiseAfterDefine.then(function (value) { promiseAfterDefine.resolved = value; }, function (value) { promiseAfterDefine.rejected = value; });
assert_not_equals(promiseAfterDefine, promise, '"whenDefined" must return a resolved promise once the custom element is defined');
assert_false('resolved' in promiseAfterDefine, 'The promise returned by "whenDefined" must not be resolved until the end of the next microtask');
assert_false('rejected' in promiseAfterDefine, 'The promise returned by "whenDefined" must not be rejected until the end of the next microtask');
}).then(function () {
assert_true('resolved' in promise, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
assert_equals(promise.resolved, undefined,
'The promise returned by "whenDefined" must be resolved with "undefined" when a custom element is defined');
assert_false('rejected' in promise, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
assert_true('resolved' in promiseAfterDefine, 'The promise returned by "whenDefined" must be resolved when a custom element is defined');
assert_equals(promiseAfterDefine.resolved, undefined,
'The promise returned by "whenDefined" must be resolved with "undefined" when a custom element is defined');
assert_false('rejected' in promiseAfterDefine, 'The promise returned by "whenDefined" must not be rejected when a custom element is defined');
});
}, 'A promise returned by customElements.whenDefined must be resolved by "define"');
</script>
</body>
</html>

View file

@ -0,0 +1,332 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: document.createElement should create an element with synchronous custom elements flag set</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="document.createElement should create an element with synchronous custom elements flag set">
<link rel="help" content="https://dom.spec.whatwg.org/#dom-document-createelement">
<link rel="help" content="https://dom.spec.whatwg.org/#concept-create-element">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helper.js"></script>
</head>
<body>
<div id="log"></div>
<script>
setup({allow_uncaught_exception:true});
test(function () {
class MyCustomElement extends HTMLElement {};
assert_true(document.createElement('my-custom-element') instanceof HTMLElement);
assert_false(document.createElement('my-custom-element') instanceof MyCustomElement);
customElements.define('my-custom-element', MyCustomElement);
var instance = document.createElement('my-custom-element');
assert_true(instance instanceof MyCustomElement);
assert_equals(instance.localName, 'my-custom-element');
assert_equals(instance.namespaceURI, 'http://www.w3.org/1999/xhtml', 'A custom element HTML must use HTML namespace');
}, 'document.createElement must create an instance of custom elements');
function assert_reports(expected, testFunction, message) {
var uncaughtError = null;
window.onerror = function (message, url, lineNumber, columnNumber, error) { uncaughtError = error; return true; }
testFunction();
if (typeof(expected) == 'string')
assert_equals(uncaughtError, expected, message);
else if (expected && 'name' in expected)
assert_equals(uncaughtError.name, expected.name, message);
else
assert_equals(uncaughtError, expected, message);
window.onerror = null;
}
function assert_not_reports(testFunction, message) {
assert_reports(null, testFunction, message);
}
test(function () {
class ObjectCustomElement extends HTMLElement {
constructor()
{
return {foo: 'bar'};
}
};
customElements.define('object-custom-element', ObjectCustomElement);
var instance = new ObjectCustomElement;
assert_true(instance instanceof Object);
assert_equals(instance.foo, 'bar');
var instance;
assert_reports({name: 'TypeError'}, function () { instance = document.createElement('object-custom-element'); });
assert_equals(instance.localName, 'object-custom-element');
assert_true(instance instanceof HTMLUnknownElement);
}, 'document.createElement must report a TypeError when the result of Construct is not a DOM node');
test(function () {
class TextCustomElement extends HTMLElement {
constructor()
{
return document.createTextNode('hello');
}
};
customElements.define('text-custom-element', TextCustomElement);
assert_true(new TextCustomElement instanceof Text);
var instance;
assert_reports({name: 'TypeError'}, function () { instance = document.createElement('text-custom-element'); });
assert_equals(instance.localName, 'text-custom-element');
assert_true(instance instanceof HTMLUnknownElement);
}, 'document.createElement must report a TypeError when the result of Construct is a TextNode');
test(function () {
class ElementWithAttribute extends HTMLElement {
constructor()
{
super();
this.setAttribute('id', 'foo');
}
};
customElements.define('element-with-attribute', ElementWithAttribute);
assert_true(new ElementWithAttribute instanceof ElementWithAttribute);
var instance;
assert_reports({name: 'NotSupportedError'}, function () { instance = document.createElement('element-with-attribute'); });
assert_equals(instance.localName, 'element-with-attribute');
assert_true(instance instanceof HTMLUnknownElement);
}, 'document.createElement must report a NotSupportedError when attribute is added by setAttribute during construction');
test(function () {
class ElementWithAttrNode extends HTMLElement {
constructor()
{
super();
this.attributes.setNamedItem(document.createAttribute('title'));
}
};
customElements.define('element-with-attr-node', ElementWithAttrNode);
assert_true(new ElementWithAttrNode instanceof ElementWithAttrNode);
var instance;
assert_reports({name: 'NotSupportedError'}, function () { instance = document.createElement('element-with-attr-node'); });
assert_equals(instance.localName, 'element-with-attr-node');
assert_true(instance instanceof HTMLUnknownElement);
}, 'document.createElement must report a NotSupportedError when attribute is added by attributes.setNamedItem during construction');
test(function () {
class ElementWithNoAttributes extends HTMLElement {
constructor()
{
super();
this.attributes.setNamedItem(document.createAttribute('title'));
this.removeAttribute('title');
}
};
customElements.define('element-with-no-attiributes', ElementWithNoAttributes);
assert_true(new ElementWithNoAttributes instanceof ElementWithNoAttributes);
var instance;
assert_not_reports(function () { instance = document.createElement('element-with-no-attiributes'); });
assert_true(instance instanceof ElementWithNoAttributes);
}, 'document.createElement must not report a NotSupportedError when attribute is added and removed during construction');
test(function () {
class ElementWithChildText extends HTMLElement {
constructor()
{
super();
this.appendChild(document.createTextNode('hello'));
}
};
customElements.define('element-with-child-text', ElementWithChildText);
assert_true(new ElementWithChildText instanceof ElementWithChildText);
var instance;
assert_reports({name: 'NotSupportedError'}, function () { instance = document.createElement('element-with-child-text'); });
assert_equals(instance.localName, 'element-with-child-text');
assert_true(instance instanceof HTMLUnknownElement);
}, 'document.createElement must report a NotSupportedError when a Text child is added during construction');
test(function () {
class ElementWithChildComment extends HTMLElement {
constructor()
{
super();
this.appendChild(document.createComment('hello'));
}
};
customElements.define('element-with-child-comment', ElementWithChildComment);
assert_true(new ElementWithChildComment instanceof ElementWithChildComment);
var instance;
assert_reports({name: 'NotSupportedError'}, function () { instance = document.createElement('element-with-child-comment'); });
assert_equals(instance.localName, 'element-with-child-comment');
assert_true(instance instanceof HTMLUnknownElement);
}, 'document.createElement must report a NotSupportedError when a Comment child is added during construction');
test(function () {
class ElementWithChildElement extends HTMLElement {
constructor()
{
super();
this.appendChild(document.createElement('div'));
}
};
customElements.define('element-with-child-element', ElementWithChildElement);
assert_true(new ElementWithChildElement instanceof ElementWithChildElement);
var instance;
assert_reports({name: 'NotSupportedError'}, function () { instance = document.createElement('element-with-child-element'); });
assert_equals(instance.localName, 'element-with-child-element');
assert_true(instance instanceof HTMLUnknownElement);
}, 'document.createElement must report a NotSupportedError when an element child is added during construction');
test(function () {
class ElementWithNoChildElements extends HTMLElement {
constructor()
{
super();
this.appendChild(document.createElement('div'));
this.removeChild(this.firstChild);
}
};
customElements.define('element-with-no-child-elements', ElementWithNoChildElements);
var instance;
assert_not_reports(function () { instance = document.createElement('element-with-no-child-elements'); });
assert_true(instance instanceof ElementWithNoChildElements);
}, 'document.createElement must not report a NotSupportedError when an element child is added and removed during construction');
test(function () {
class ElementWithParent extends HTMLElement {
constructor()
{
super();
document.createElement('div').appendChild(this);
}
};
customElements.define('element-with-parent', ElementWithParent);
assert_true(new ElementWithParent instanceof ElementWithParent);
var instance;
assert_reports({name: 'NotSupportedError'}, function () { instance = document.createElement('element-with-parent'); });
assert_equals(instance.localName, 'element-with-parent');
assert_true(instance instanceof HTMLUnknownElement);
}, 'document.createElement must report a NotSupportedError when the element gets inserted into another element during construction');
test(function () {
class ElementWithNoParent extends HTMLElement {
constructor()
{
super();
document.createElement('div').appendChild(this);
this.parentNode.removeChild(this);
}
};
customElements.define('element-with-no-parent', ElementWithNoParent);
var instance;
assert_not_reports(function () { instance = document.createElement('element-with-no-parent'); });
assert_true(instance instanceof ElementWithNoParent);
}, 'document.createElement must not report a NotSupportedError when the element is inserted and removed from another element during construction');
document_types().forEach(function (entry, testNumber) {
if (entry.isOwner)
return;
var getDocument = entry.create;
var docuemntName = entry.name;
promise_test(function () {
return getDocument().then(function (doc) {
class ElementWithAdoptCall extends HTMLElement {
constructor()
{
super();
doc.adoptNode(this);
}
};
var name = 'element-with-adopt-call-' + testNumber;
customElements.define(name, ElementWithAdoptCall);
assert_true(new ElementWithAdoptCall instanceof ElementWithAdoptCall);
var instance;
assert_reports({name: 'NotSupportedError'}, function () { instance = document.createElement(name); });
assert_equals(instance.localName, name);
assert_true(instance instanceof HTMLUnknownElement);
});
}, 'document.createElement must report a NotSupportedError when the element is adopted into a ' + docuemntName + ' during construction');
promise_test(function () {
return getDocument().then(function (doc) {
class ElementInsertedIntoAnotherDocument extends HTMLElement {
constructor()
{
super();
doc.documentElement.appendChild(this);
}
};
var name = 'element-inserted-into-another-document-' + testNumber;
customElements.define(name, ElementInsertedIntoAnotherDocument);
assert_true(new ElementInsertedIntoAnotherDocument instanceof ElementInsertedIntoAnotherDocument);
var instance;
assert_reports({name: 'NotSupportedError'}, function () { instance = document.createElement(name); });
assert_equals(instance.localName, name);
assert_true(instance instanceof HTMLUnknownElement);
});
}, 'document.createElement must report a NotSupportedError when the element is inserted into a ' + docuemntName + ' during construction');
promise_test(function () {
return getDocument().then(function (doc) {
class ElementThatGetAdoptedBack extends HTMLElement {
constructor()
{
super();
doc.adoptNode(this);
document.adoptNode(this);
}
};
var name = 'element-that-get-adopted-back' + testNumber;
customElements.define(name, ElementThatGetAdoptedBack);
var instance;
assert_not_reports(function () { instance = document.createElement(name); });
assert_true(instance instanceof ElementThatGetAdoptedBack);
});
}, 'document.createElement must not report a NotSupportedError when the element is adopted back from a ' + docuemntName + ' during construction');
});
test(function () {
class DivCustomElement extends HTMLElement {
constructor()
{
super();
return document.createElement('div');
}
};
customElements.define('div-custom-element', DivCustomElement);
assert_true(new DivCustomElement instanceof HTMLDivElement);
var instance;
assert_reports({name: 'NotSupportedError'}, function () { instance = document.createElement('div-custom-element'); });
assert_equals(instance.localName, 'div-custom-element');
assert_true(instance instanceof HTMLUnknownElement);
}, 'document.createElement must report a NotSupportedError when the local name of the element does not match that of the custom element');
test(function () {
var exceptionToThrow = {name: 'exception thrown by a custom constructor'};
class ThrowCustomElement extends HTMLElement {
constructor()
{
super();
if (exceptionToThrow)
throw exceptionToThrow;
}
};
customElements.define('throw-custom-element', ThrowCustomElement);
assert_throws(exceptionToThrow, function () { new ThrowCustomElement; });
var instance;
assert_reports(exceptionToThrow, function () { instance = document.createElement('throw-custom-element'); });
assert_equals(instance.localName, 'throw-custom-element');
assert_true(instance instanceof HTMLUnknownElement);
exceptionToThrow = false;
var instance = document.createElement('throw-custom-element');
assert_true(instance instanceof ThrowCustomElement);
assert_equals(instance.localName, 'throw-custom-element');
}, 'document.createElement must report an exception thrown by a custom element constructor');
</script>
</body>
</html>

View file

@ -0,0 +1,135 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: adoptedCallback</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="adoptedCallback must be enqueued whenever custom element is adopted into a new document">
<link rel="help" href="https://w3c.github.io/webcomponents/spec/custom/#dfn-connected-callback">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/custom-elements-helpers.js"></script>
</head>
<body>
<div id="log"></div>
<script>
var calls = [];
class MyCustomElement extends HTMLElement {
connectedCallback() { calls.push('connected'); }
adoptedCallback(oldDocument, newDocument) { calls.push('adopted'); calls.push(oldDocument); calls.push(newDocument); }
disconnectedCallback() { calls.push('disconnected'); }
}
customElements.define('my-custom-element', MyCustomElement);
test(function () {
var instance = document.createElement('my-custom-element');
calls = [];
document.body.appendChild(instance);
assert_array_equals(calls, ['connected']);
}, 'Inserting a custom element into the owner document must not enqueue and invoke adoptedCallback');
document_types().forEach(function (entry) {
if (entry.isOwner)
return;
var documentName = entry.name;
var getDocument = entry.create;
promise_test(function () {
return getDocument().then(function (doc) {
var instance = document.createElement('my-custom-element');
calls = [];
doc.documentElement.appendChild(instance);
assert_array_equals(calls, ['adopted', document, doc, 'connected']);
});
}, 'Inserting a custom element into ' + documentName + ' must enqueue and invoke adoptedCallback');
promise_test(function () {
return getDocument().then(function (doc) {
var instance = document.createElement('my-custom-element');
document.body.appendChild(instance);
calls = [];
doc.documentElement.appendChild(instance);
assert_array_equals(calls, ['disconnected', 'adopted', document, doc, 'connected']);
});
}, 'Moving a custom element from the owner document into ' + documentName + ' must enqueue and invoke adoptedCallback');
promise_test(function () {
return getDocument().then(function (doc) {
var instance = document.createElement('my-custom-element');
var parent = document.createElement('div');
parent.appendChild(instance);
calls = [];
doc.documentElement.appendChild(parent);
assert_array_equals(calls, ['adopted', document, doc, 'connected']);
});
}, 'Inserting an ancestor of custom element into ' + documentName + ' must enqueue and invoke adoptedCallback');
promise_test(function () {
return getDocument().then(function (doc) {
var instance = document.createElement('my-custom-element');
var parent = document.createElement('div');
parent.appendChild(instance);
document.body.appendChild(parent);
calls = [];
doc.documentElement.appendChild(parent);
assert_array_equals(calls, ['disconnected', 'adopted', document, doc, 'connected']);
});
}, 'Moving an ancestor of custom element from the owner document into ' + documentName + ' must enqueue and invoke adoptedCallback');
promise_test(function () {
return getDocument().then(function (doc) {
var instance = document.createElement('my-custom-element');
var host = doc.createElementNS('http://www.w3.org/1999/xhtml', 'div');
var shadowRoot = host.attachShadow({mode: 'closed'});
doc.documentElement.appendChild(host);
calls = [];
shadowRoot.appendChild(instance);
assert_array_equals(calls, ['adopted', document, doc, 'connected']);
});
}, 'Inserting a custom element into a shadow tree in ' + documentName + ' must enqueue and invoke adoptedCallback');
promise_test(function () {
return getDocument().then(function (doc) {
var instance = document.createElement('my-custom-element');
var host = document.createElement('div');
var shadowRoot = host.attachShadow({mode: 'closed'});
shadowRoot.appendChild(instance);
calls = [];
doc.documentElement.appendChild(host);
assert_array_equals(calls, ['adopted', document, doc, 'connected']);
});
}, 'Inserting the shadow host of a custom element into ' + documentName + ' must enqueue and invoke adoptedCallback');
promise_test(function () {
return getDocument().then(function (doc) {
var instance = document.createElement('my-custom-element');
var host = document.createElement('div');
var shadowRoot = host.attachShadow({mode: 'closed'});
shadowRoot.appendChild(instance);
document.body.appendChild(host);
calls = [];
doc.documentElement.appendChild(host);
assert_array_equals(calls, ['disconnected', 'adopted', document, doc, 'connected']);
});
}, 'Moving the shadow host of a custom element from the owner document into ' + documentName + ' must enqueue and invoke adoptedCallback');
promise_test(function () {
return getDocument().then(function (doc) {
var instance = document.createElement('my-custom-element');
var host = doc.createElementNS('http://www.w3.org/1999/xhtml', 'div');
var shadowRoot = host.attachShadow({mode: 'closed'});
calls = [];
shadowRoot.appendChild(instance);
assert_array_equals(calls, ['adopted', document, doc]);
});
}, 'Inserting a custom element into a detached shadow tree that belongs to ' + documentName + ' must enqueue and invoke adoptedCallback');
});
</script>
</body>
</html>

View file

@ -0,0 +1,223 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: attributeChangedCallback</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="attributeChangedCallback must be enqueued whenever custom element's attribute is added, changed or removed">
<link rel="help" href="https://w3c.github.io/webcomponents/spec/custom/#dfn-attribute-changed-callback">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
</head>
<body>
<div id="log"></div>
<script>
var customElement = define_new_custom_element(['title', 'id', 'r']);
test(function () {
const instance = document.createElement(customElement.name);
assert_array_equals(customElement.takeLog().types(), ['constructed']);
instance.setAttribute('title', 'foo');
assert_equals(instance.getAttribute('title'), 'foo');
var logEntries = customElement.takeLog();
assert_array_equals(logEntries.types(), ['attributeChanged']);
assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: null, newValue: 'foo', namespace: null});
instance.removeAttribute('title');
assert_equals(instance.getAttribute('title'), null);
var logEntries = customElement.takeLog();
assert_array_equals(logEntries.types(), ['attributeChanged']);
assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: 'foo', newValue: null, namespace: null});
}, 'setAttribute and removeAttribute must enqueue and invoke attributeChangedCallback');
test(function () {
var instance = document.createElement(customElement.name);
assert_array_equals(customElement.takeLog().types(), ['constructed']);
instance.setAttributeNS('http://www.w3.org/2000/svg', 'title', 'hello');
assert_equals(instance.getAttribute('title'), 'hello');
var logEntries = customElement.takeLog();
assert_array_equals(logEntries.types(), ['attributeChanged']);
assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: null, newValue: 'hello', namespace: 'http://www.w3.org/2000/svg'});
instance.removeAttributeNS('http://www.w3.org/2000/svg', 'title');
assert_equals(instance.getAttribute('title'), null);
var logEntries = customElement.takeLog();
assert_array_equals(logEntries.types(), ['attributeChanged']);
assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: 'hello', newValue: null, namespace: 'http://www.w3.org/2000/svg'});
}, 'setAttributeNS and removeAttributeNS must enqueue and invoke attributeChangedCallback');
test(function () {
var instance = document.createElement(customElement.name);
assert_array_equals(customElement.takeLog().types(), ['constructed']);
var attr = document.createAttribute('id');
attr.value = 'bar';
instance.setAttributeNode(attr);
assert_equals(instance.getAttribute('id'), 'bar');
var logEntries = customElement.takeLog();
assert_array_equals(logEntries.types(), ['attributeChanged']);
assert_attribute_log_entry(logEntries.last(), {name: 'id', oldValue: null, newValue: 'bar', namespace: null});
instance.removeAttributeNode(attr);
assert_equals(instance.getAttribute('id'), null);
var logEntries = customElement.takeLog();
assert_array_equals(logEntries.types(), ['attributeChanged']);
assert_attribute_log_entry(logEntries.last(), {name: 'id', oldValue: 'bar', newValue: null, namespace: null});
}, 'setAttributeNode and removeAttributeNode must enqueue and invoke attributeChangedCallback for an HTML attribute');
test(function () {
const instance = document.createElement(customElement.name);
assert_array_equals(customElement.takeLog().types(), ['constructed']);
const attr = document.createAttributeNS('http://www.w3.org/2000/svg', 'r');
attr.value = '100';
instance.setAttributeNode(attr);
assert_equals(instance.getAttribute('r'), '100');
var logEntries = customElement.takeLog();
assert_array_equals(logEntries.types(), ['attributeChanged']);
assert_attribute_log_entry(logEntries.last(), {name: 'r', oldValue: null, newValue: '100', namespace: 'http://www.w3.org/2000/svg'});
instance.removeAttributeNode(attr);
assert_equals(instance.getAttribute('r'), null);
var logEntries = customElement.takeLog();
assert_array_equals(logEntries.types(), ['attributeChanged']);
assert_attribute_log_entry(logEntries.last(), {name: 'r', oldValue: '100', newValue: null, namespace: 'http://www.w3.org/2000/svg'});
}, 'setAttributeNode and removeAttributeNS must enqueue and invoke attributeChangedCallback for an SVG attribute');
test(function () {
const callsToOld = [];
const callsToNew = [];
class CustomElement extends HTMLElement { }
CustomElement.prototype.attributeChangedCallback = function (...args) {
callsToOld.push(create_attribute_changed_callback_log(this, ...args));
}
CustomElement.observedAttributes = ['title'];
customElements.define('element-with-mutated-attribute-changed-callback', CustomElement);
CustomElement.prototype.attributeChangedCallback = function (...args) {
callsToNew.push(create_attribute_changed_callback_log(this, ...args));
}
const instance = document.createElement('element-with-mutated-attribute-changed-callback');
instance.setAttribute('title', 'hi');
assert_equals(instance.getAttribute('title'), 'hi');
assert_array_equals(callsToNew, []);
assert_equals(callsToOld.length, 1);
assert_attribute_log_entry(callsToOld[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
}, 'Mutating attributeChangedCallback after calling customElements.define must not affect the callback being invoked');
test(function () {
const calls = [];
class CustomElement extends HTMLElement {
attributeChangedCallback(...args) {
calls.push(create_attribute_changed_callback_log(this, ...args));
}
}
CustomElement.observedAttributes = ['title'];
customElements.define('element-not-observing-id-attribute', CustomElement);
const instance = document.createElement('element-not-observing-id-attribute');
assert_equals(calls.length, 0);
instance.setAttribute('title', 'hi');
assert_equals(calls.length, 1);
assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
instance.setAttribute('id', 'some');
assert_equals(calls.length, 1);
assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
}, 'attributedChangedCallback must not be invoked when the observed attributes does not contain the attribute');
test(function () {
const calls = [];
class CustomElement extends HTMLElement { }
CustomElement.prototype.attributeChangedCallback = function (...args) {
calls.push(create_attribute_changed_callback_log(this, ...args));
}
CustomElement.observedAttributes = ['title', 'lang'];
customElements.define('element-with-mutated-observed-attributes', CustomElement);
CustomElement.observedAttributes = ['title', 'id'];
const instance = document.createElement('element-with-mutated-observed-attributes');
instance.setAttribute('title', 'hi');
assert_equals(calls.length, 1);
assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
instance.setAttribute('id', 'some');
assert_equals(calls.length, 1);
instance.setAttribute('lang', 'en');
assert_equals(calls.length, 2);
assert_attribute_log_entry(calls[1], {name: 'lang', oldValue: null, newValue: 'en', namespace: null});
}, 'Mutating observedAttributes after calling customElements.define must not affect the set of attributes for which attributedChangedCallback is invoked');
test(function () {
var calls = [];
class CustomElement extends HTMLElement { }
CustomElement.prototype.attributeChangedCallback = function (...args) {
calls.push(create_attribute_changed_callback_log(this, ...args));
}
CustomElement.observedAttributes = { [Symbol.iterator]: function *() { yield 'lang'; yield 'style'; } };
customElements.define('element-with-generator-observed-attributes', CustomElement);
var instance = document.createElement('element-with-generator-observed-attributes');
instance.setAttribute('lang', 'en');
assert_equals(calls.length, 1);
assert_attribute_log_entry(calls[0], {name: 'lang', oldValue: null, newValue: 'en', namespace: null});
instance.setAttribute('lang', 'ja');
assert_equals(calls.length, 2);
assert_attribute_log_entry(calls[1], {name: 'lang', oldValue: 'en', newValue: 'ja', namespace: null});
instance.setAttribute('title', 'hello');
assert_equals(calls.length, 2);
instance.setAttribute('style', 'font-size: 2rem');
assert_equals(calls.length, 3);
assert_attribute_log_entry(calls[2], {name: 'style', oldValue: null, newValue: 'font-size: 2rem', namespace: null});
}, 'attributedChangedCallback must be enqueued for attributes specified in a non-Array iterable observedAttributes');
test(function () {
var calls = [];
class CustomElement extends HTMLElement { }
CustomElement.prototype.attributeChangedCallback = function (...args) {
calls.push(create_attribute_changed_callback_log(this, ...args));
}
CustomElement.observedAttributes = ['style'];
customElements.define('element-with-style-attribute-observation', CustomElement);
var instance = document.createElement('element-with-style-attribute-observation');
assert_equals(calls.length, 0);
instance.style.fontSize = '10px';
assert_equals(calls.length, 1);
assert_attribute_log_entry(calls[0], {name: 'style', oldValue: null, newValue: 'font-size: 10px;', namespace: null});
instance.style.fontSize = '20px';
assert_equals(calls.length, 2);
assert_attribute_log_entry(calls[1], {name: 'style', oldValue: 'font-size: 10px;', newValue: 'font-size: 20px;', namespace: null});
}, 'attributedChangedCallback must be enqueued for style attribute change by mutating inline style declaration');
test(function () {
var calls = [];
class CustomElement extends HTMLElement { }
CustomElement.prototype.attributeChangedCallback = function (...args) {
calls.push(create_attribute_changed_callback_log(this, ...args));
}
CustomElement.observedAttributes = ['title'];
customElements.define('element-with-no-style-attribute-observation', CustomElement);
var instance = document.createElement('element-with-no-style-attribute-observation');
assert_equals(calls.length, 0);
instance.style.fontSize = '10px';
assert_equals(calls.length, 0);
instance.title = 'hello';
assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hello', namespace: null});
}, 'attributedChangedCallback must not be enqueued when mutating inline style declaration if the style attribute is not observed');
</script>
</body>
</html>

View file

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: connectedCallback</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="connectedCallback must be enqueued whenever custom element is inserted into a document">
<link rel="help" href="https://w3c.github.io/webcomponents/spec/custom/#dfn-connected-callback">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
</head>
<body>
<div id="log"></div>
<script>
var calls = [];
class MyCustomElement extends HTMLElement {
connectedCallback() { calls.push('connected', this); }
disconnectedCallback() { calls.push('disconnected', this); }
}
customElements.define('my-custom-element', MyCustomElement);
document_types().forEach(function (entry) {
var documentName = entry.name;
var getDocument = entry.create;
promise_test(function () {
return getDocument().then(function (doc) {
var instance = document.createElement('my-custom-element');
calls = [];
doc.documentElement.appendChild(instance);
assert_array_equals(calls, ['connected', instance]);
});
}, 'Inserting a custom element into ' + documentName + ' must enqueue and invoke connectedCallback');
promise_test(function () {
return getDocument().then(function (doc) {
var instance = document.createElement('my-custom-element');
var parent = document.createElement('div');
parent.appendChild(instance);
calls = [];
doc.documentElement.appendChild(parent);
assert_array_equals(calls, ['connected', instance]);
});
}, 'Inserting an ancestor of custom element into ' + documentName + ' must enqueue and invoke connectedCallback');
promise_test(function () {
return getDocument().then(function (doc) {
var instance = document.createElement('my-custom-element');
var host = doc.createElementNS('http://www.w3.org/1999/xhtml', 'div');
var shadowRoot = host.attachShadow({mode: 'closed'});
doc.documentElement.appendChild(host);
calls = [];
shadowRoot.appendChild(instance);
assert_array_equals(calls, ['connected', instance]);
});
}, 'Inserting a custom element into a shadow tree in ' + documentName + ' must enqueue and invoke connectedCallback');
promise_test(function () {
return getDocument().then(function (doc) {
var instance = document.createElement('my-custom-element');
var host = doc.createElementNS('http://www.w3.org/1999/xhtml', 'div');
var shadowRoot = host.attachShadow({mode: 'closed'});
shadowRoot.appendChild(instance);
calls = [];
doc.documentElement.appendChild(host);
assert_array_equals(calls, ['connected', instance]);
});
}, 'Inserting the shadow host of a custom element into ' + documentName + ' must enqueue and invoke connectedCallback');
promise_test(function () {
return getDocument().then(function (doc) {
var instance = document.createElement('my-custom-element');
var host = doc.createElementNS('http://www.w3.org/1999/xhtml', 'div');
var shadowRoot = host.attachShadow({mode: 'closed'});
calls = [];
shadowRoot.appendChild(instance);
assert_array_equals(calls, []);
});
}, 'Inserting a custom element into a detached shadow tree that belongs to ' + documentName + ' must not enqueue and invoke connectedCallback');
});
</script>
</body>
</html>

View file

@ -0,0 +1,157 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: Each element must have its own custom element reaction queue</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="Each element must have its own custom element reaction queue">
<meta name="help" content="https://html.spec.whatwg.org/multipage/scripting.html#custom-element-reaction-queue">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/custom-elements-helpers.js"></script>
</head>
<body>
<div id="log"></div>
<script>
test_with_window(function (contentWindow) {
const contentDocument = contentWindow.document;
contentDocument.write('<test-element id="first-element">');
contentDocument.write('<test-element id="second-element">');
const element1 = contentDocument.getElementById('first-element');
const element2 = contentDocument.getElementById('second-element');
assert_equals(Object.getPrototypeOf(element1), contentWindow.HTMLElement.prototype);
assert_equals(Object.getPrototypeOf(element2), contentWindow.HTMLElement.prototype);
let log = [];
class TestElement extends contentWindow.HTMLElement {
constructor() {
super();
log.push(create_constructor_log(this));
}
connectedCallback(...args) {
log.push(create_connected_callback_log(this, ...args));
}
attributeChangedCallback(...args) {
log.push(create_attribute_changed_callback_log(this, ...args));
}
static get observedAttributes() { return ['id']; }
}
contentWindow.customElements.define('test-element', TestElement);
assert_equals(Object.getPrototypeOf(element1), TestElement.prototype);
assert_equals(Object.getPrototypeOf(element2), TestElement.prototype);
assert_equals(log.length, 6);
assert_constructor_log_entry(log[0], element1);
assert_attribute_log_entry(log[1], {name: 'id', oldValue: null, newValue: 'first-element', namespace: null});
assert_connected_log_entry(log[2], element1);
assert_constructor_log_entry(log[3], element2);
assert_attribute_log_entry(log[4], {name: 'id', oldValue: null, newValue: 'second-element', namespace: null});
assert_connected_log_entry(log[5], element2);
}, 'Upgrading a custom element must invoke attributeChangedCallback and connectedCallback before start upgrading another element');
test_with_window(function (contentWindow) {
const contentDocument = contentWindow.document;
contentDocument.write('<test-element id="first-element">');
contentDocument.write('<test-element id="second-element">');
const element1 = contentDocument.getElementById('first-element');
const element2 = contentDocument.getElementById('second-element');
assert_equals(Object.getPrototypeOf(element1), contentWindow.HTMLElement.prototype);
assert_equals(Object.getPrototypeOf(element2), contentWindow.HTMLElement.prototype);
let log = [];
class TestElement extends contentWindow.HTMLElement {
constructor() {
super();
log.push(create_constructor_log(this));
if (this == element1) {
element2.setAttribute('title', 'hi');
element2.removeAttribute('title');
element2.setAttribute('class', 'foo');
}
}
connectedCallback(...args) {
log.push(create_connected_callback_log(this, ...args));
}
attributeChangedCallback(...args) {
log.push(create_attribute_changed_callback_log(this, ...args));
}
static get observedAttributes() { return ['id', 'class', 'title']; }
}
contentWindow.customElements.define('test-element', TestElement);
assert_equals(Object.getPrototypeOf(element1), TestElement.prototype);
assert_equals(Object.getPrototypeOf(element2), TestElement.prototype);
assert_equals(log.length, 7);
assert_constructor_log_entry(log[0], element1);
assert_attribute_log_entry(log[1], {name: 'id', oldValue: null, newValue: 'first-element', namespace: null});
assert_connected_log_entry(log[2], element1);
assert_constructor_log_entry(log[3], element2);
assert_attribute_log_entry(log[4], {name: 'id', oldValue: null, newValue: 'second-element', namespace: null});
assert_attribute_log_entry(log[5], {name: 'class', oldValue: null, newValue: 'foo', namespace: null});
assert_connected_log_entry(log[6], element2);
}, 'Mutating a undefined custom element while upgrading a custom element must not enqueue or invoke reactions on the mutated element');
test_with_window(function (contentWindow) {
let log = [];
let element1;
let element2;
class TestElement extends contentWindow.HTMLElement {
constructor() {
super();
log.push(create_constructor_log(this));
}
adoptedCallback(...args) {
log.push(create_adopted_callback_log(this, ...args));
if (this == element1)
element3.setAttribute('id', 'foo');
}
connectedCallback(...args) {
log.push(create_connected_callback_log(this, ...args));
}
attributeChangedCallback(...args) {
log.push(create_attribute_changed_callback_log(this, ...args));
}
static get observedAttributes() { return ['id', 'class']; }
}
contentWindow.customElements.define('test-element', TestElement);
let contentDocument = contentWindow.document;
element1 = contentDocument.createElement('test-element');
element2 = contentDocument.createElement('test-element');
element3 = contentDocument.createElement('test-element');
assert_equals(Object.getPrototypeOf(element1), TestElement.prototype);
assert_equals(Object.getPrototypeOf(element2), TestElement.prototype);
assert_equals(Object.getPrototypeOf(element3), TestElement.prototype);
assert_equals(log.length, 3);
assert_constructor_log_entry(log[0], element1);
assert_constructor_log_entry(log[1], element2);
assert_constructor_log_entry(log[2], element3);
log = [];
const container = contentDocument.createElement('div');
container.appendChild(element1);
container.appendChild(element2);
container.appendChild(element3);
const anotherDocument = document.implementation.createHTMLDocument();
anotherDocument.documentElement.appendChild(container);
assert_equals(log.length, 7);
assert_adopted_log_entry(log[0], element1);
assert_adopted_log_entry(log[1], element3);
assert_connected_log_entry(log[2], element3);
assert_attribute_log_entry(log[3], {name: 'id', oldValue: null, newValue: 'foo', namespace: null});
assert_connected_log_entry(log[4], element1);
assert_adopted_log_entry(log[5], element2);
assert_connected_log_entry(log[6], element2);
}, 'Mutating another custom element inside adopted callback must invoke all pending callbacks on the mutated element');
</script>
</body>
</html>

View file

@ -0,0 +1,93 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: disconnectedCallback</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="disconnectedCallback must be enqueued whenever custom element is removed from a document">
<link rel="help" href="https://w3c.github.io/webcomponents/spec/custom/#dfn-connected-callback">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/custom-elements-helpers.js"></script>
</head>
<body>
<div id="log"></div>
<script>
var calls = [];
class MyCustomElement extends HTMLElement {
connectedCallback() { calls.push('connected', this); }
disconnectedCallback() { calls.push('disconnected', this); }
}
customElements.define('my-custom-element', MyCustomElement);
document_types().forEach(function (entry) {
var documentName = entry.name;
var getDocument = entry.create;
promise_test(function () {
return getDocument().then(function (doc) {
var instance = document.createElement('my-custom-element');
doc.documentElement.appendChild(instance);
calls = [];
doc.documentElement.removeChild(instance);
assert_array_equals(calls, ['disconnected', instance]);
});
}, 'Removing a custom element from ' + documentName + ' must enqueue and invoke disconnectedCallback');
promise_test(function () {
return getDocument().then(function (doc) {
var instance = document.createElement('my-custom-element');
var parent = document.createElement('div');
parent.appendChild(instance);
doc.documentElement.appendChild(parent);
calls = [];
doc.documentElement.removeChild(parent);
assert_array_equals(calls, ['disconnected', instance]);
});
}, 'Removing an ancestor of custom element from ' + documentName + ' must enqueue and invoke disconnectedCallback');
promise_test(function () {
return getDocument().then(function (doc) {
var instance = document.createElement('my-custom-element');
var host = doc.createElementNS('http://www.w3.org/1999/xhtml', 'div');
var shadowRoot = host.attachShadow({mode: 'closed'});
doc.documentElement.appendChild(host);
shadowRoot.appendChild(instance);
calls = [];
shadowRoot.removeChild(instance);
assert_array_equals(calls, ['disconnected', instance]);
});
}, 'Removing a custom element from a shadow tree in ' + documentName + ' must enqueue and invoke disconnectedCallback');
promise_test(function () {
return getDocument().then(function (doc) {
var instance = document.createElement('my-custom-element');
var host = doc.createElementNS('http://www.w3.org/1999/xhtml', 'div');
var shadowRoot = host.attachShadow({mode: 'closed'});
shadowRoot.appendChild(instance);
doc.documentElement.appendChild(host);
calls = [];
doc.documentElement.removeChild(host);
assert_array_equals(calls, ['disconnected', instance]);
});
}, 'Removing the shadow host of a custom element from a' + documentName + ' must enqueue and invoke disconnectedCallback');
promise_test(function () {
return getDocument().then(function (doc) {
var instance = document.createElement('my-custom-element');
var host = doc.createElementNS('http://www.w3.org/1999/xhtml', 'div');
var shadowRoot = host.attachShadow({mode: 'closed'});
shadowRoot.appendChild(instance);
calls = [];
shadowRoot.removeChild(instance);
assert_array_equals(calls, []);
});
}, 'Removing a custom element from a detached shadow tree that belongs to ' + documentName + ' must not enqueue and invoke disconnectedCallback');
});
</script>
</body>
</html>

View file

@ -5,120 +5,124 @@
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/custom-elements-helpers.js"></script>
<body>
<script>
"use strict";
test_with_window(w => {
let afterDefinition = false;
let beforeDefinition = true;
const proto1 = { "proto": "number one" };
const proto2 = { "proto": "number two" };
const TestElement = (function () {
assert_throws({ name: "prototype throws" }, () => {
const o = Reflect.construct(w.HTMLElement, [], new.target);
function TestElement() {
const o = Reflect.construct(w.HTMLElement, [], new.target);
assert_equals(Object.getPrototypeOf(o), proto2,
"Must use the value returned from new.target.prototype");
assert_not_equals(Object.getPrototypeOf(o), proto1,
"Must not use the prototype stored at definition time");
}
assert_equals(Object.getPrototypeOf(o), proto2,
"Must use the value returned from new.target.prototype");
assert_not_equals(Object.getPrototypeOf(o), proto1,
"Must not use the prototype stored at definition time");
});
}).bind({});
Object.defineProperty(TestElement, "prototype", {
get() {
return beforeDefinition ? proto1 : proto2;
}
const ElementWithDynamicPrototype = new Proxy(TestElement, {
get: function (target, name) {
if (name == "prototype")
return beforeDefinition ? proto1 : proto2;
return target[name];
}
});
w.customElements.define("test-element", TestElement);
beforeDefinition = true;
new TestElement();
w.customElements.define("test-element", ElementWithDynamicPrototype);
beforeDefinition = false;
new ElementWithDynamicPrototype();
}, "Use NewTarget's prototype, not the one stored at definition time");
test_with_window(w => {
// We have to not throw during define(), but throw during super()
let throws = false;
const TestElement = (function () {
function TestElement() {
throws = true;
assert_throws({ name: "prototype throws" }, () => {
return Reflect.construct(w.HTMLElement, [], new.target);
Reflect.construct(w.HTMLElement, [], new.target);
});
}).bind({});
}
Object.defineProperty(TestElement, "prototype", {
get() {
if (throws) {
const ElementWithDynamicPrototype = new Proxy(TestElement, {
get: function (target, name) {
if (throws && name == "prototype")
throw { name: "prototype throws" };
}
return {};
return target[name];
}
});
w.customElements.define("test-element", TestElement);
w.customElements.define("test-element", ElementWithDynamicPrototype);
throws = true;
new TestElement();
new ElementWithDynamicPrototype();
}, "Rethrow any exceptions thrown while getting the prototype");
test_with_window(w => {
for (const notAnObject of [null, undefined, 5, "string"]) {
[null, undefined, 5, "string"].forEach(function (notAnObject) {
test_with_window(w => {
// We have to return an object during define(), but not during super()
let returnNotAnObject = false;
const TestElement = (function () {
function TestElement() {
const o = Reflect.construct(w.HTMLElement, [], new.target);
assert_equals(Object.getPrototypeOf(o), window.HTMLElement,
assert_equals(Object.getPrototypeOf(new.target), window.Function.prototype);
assert_equals(Object.getPrototypeOf(o), window.HTMLElement.prototype,
"Must use the HTMLElement from the realm of NewTarget");
assert_not_equals(Object.getPrototypeOf(o), w.HTMLElement,
assert_not_equals(Object.getPrototypeOf(o), w.HTMLElement.prototype,
"Must not use the HTMLElement from the realm of the active function object (w.HTMLElement)");
return o;
}).bind({});
}
Object.defineProperty(TestElement, "prototype", {
get() {
return returnNotAnObject ? notAnObject : {};
const ElementWithDynamicPrototype = new Proxy(TestElement, {
get: function (target, name) {
if (name == "prototype")
return returnNotAnObject ? notAnObject : {};
return target[name];
}
});
w.customElements.define("test-element", TestElement);
w.customElements.define("test-element", ElementWithDynamicPrototype);
returnNotAnObject = true;
new TestElement();
}
}, "If prototype is not object, derives the fallback from NewTarget's realm (autonomous custom elements)");
new ElementWithDynamicPrototype();
}, "If prototype is not object (" + notAnObject + "), derives the fallback from NewTarget's realm (autonomous custom elements)");
});
test_with_window(w => {
for (const notAnObject of [null, undefined, 5, "string"]) {
[null, undefined, 5, "string"].forEach(function (notAnObject) {
test_with_window(w => {
// We have to return an object during define(), but not during super()
let returnNotAnObject = false;
const TestElement = (function () {
function TestElement() {
const o = Reflect.construct(w.HTMLParagraphElement, [], new.target);
assert_equals(Object.getPrototypeOf(o), window.HTMLParagraphElement,
assert_equals(Object.getPrototypeOf(o), window.HTMLParagraphElement.prototype,
"Must use the HTMLParagraphElement from the realm of NewTarget");
assert_not_equals(Object.getPrototypeOf(o), w.HTMLParagraphElement,
assert_not_equals(Object.getPrototypeOf(o), w.HTMLParagraphElement.prototype,
"Must not use the HTMLParagraphElement from the realm of the active function object (w.HTMLParagraphElement)");
return o;
}).bind({});
}
Object.defineProperty(TestElement, "prototype", {
get() {
return returnNotAnObject ? notAnObject : {};
const ElementWithDynamicPrototype = new Proxy(TestElement, {
get: function (target, name) {
if (name == "prototype")
return returnNotAnObject ? notAnObject : {};
return target[name];
}
});
w.customElements.define("test-element", TestElement, { extends: "p" });
w.customElements.define("test-element", ElementWithDynamicPrototype, { extends: "p" });
returnNotAnObject = true;
new TestElement();
}
}, "If prototype is not object, derives the fallback from NewTarget's realm (customized built-in elements)");
new ElementWithDynamicPrototype();
}, "If prototype is not object (" + notAnObject + "), derives the fallback from NewTarget's realm (customized built-in elements)");
});
</script>
</body>

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: Changes to the HTML parser</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="HTML parser must construct custom elements inside document.write">
<link rel="help" href="https://html.spec.whatwg.org/#create-an-element-for-the-token">
<link rel="help" href="https://dom.spec.whatwg.org/#concept-create-element">
<link rel="help" href="https://html.spec.whatwg.org/#document.write()">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="log"></div>
<script>
class MyCustomElement extends HTMLElement { }
customElements.define('my-custom-element', MyCustomElement);
document.write('<my-custom-element></my-custom-element>');
test(function () {
var instance = document.querySelector('my-custom-element');
assert_true(instance instanceof HTMLElement);
assert_true(instance instanceof MyCustomElement);
}, 'HTML parser must instantiate custom elements inside document.write');
</script>
</body>
</html>

View file

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: Changes to the HTML parser</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="HTML parser must construct a custom element synchronously">
<link rel="help" href="https://html.spec.whatwg.org/#create-an-element-for-the-token">
<link rel="help" href="https://dom.spec.whatwg.org/#concept-create-element">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="log"></div>
<script>
var childElementCountInConstructor;
var containerChildNodesInConstructor = [];
var containerNextSiblingInConstructor;
class MyCustomElement extends HTMLElement {
constructor() {
super();
var container = document.getElementById('custom-element-container');
for (var i = 0; i < container.childNodes.length; i++)
containerChildNodesInConstructor.push(container.childNodes[i]);
containerNextSiblingInConstructor = container.nextSibling;
}
};
customElements.define('my-custom-element', MyCustomElement);
</script>
<div id="custom-element-container">
<span id="custom-element-previous-element"></span>
<my-custom-element></my-custom-element>
<div id="custom-element-next-element"></div>
</div>
<script>
test(function () {
var instance = document.querySelector('my-custom-element');
assert_equals(containerChildNodesInConstructor.length, 3);
assert_equals(containerChildNodesInConstructor[0], instance.parentNode.firstChild);
assert_equals(containerChildNodesInConstructor[1], document.getElementById('custom-element-previous-element'));
assert_equals(containerChildNodesInConstructor[2], instance.previousSibling);
assert_equals(containerNextSiblingInConstructor, null);
}, 'HTML parser must only append nodes that appear before a custom element before instantiating the custom element');
</script>
</body>
</html>

View file

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: Changes to the HTML parser</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="HTML parser creates a custom element">
<link rel="help" href="https://html.spec.whatwg.org/#create-an-element-for-the-token">
<link rel="help" href="https://dom.spec.whatwg.org/#concept-create-element">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="log"></div>
<my-custom-element id="instance1"></my-custom-element>
<script>
class MyCustomElement extends HTMLElement { };
test(function () {
var customElement = document.getElementById('instance1');
assert_true(customElement instanceof HTMLElement, 'An unresolved custom element must be an instance of HTMLElement');
assert_false(customElement instanceof MyCustomElement, 'An unresolved custom element must NOT be an instance of that custom element');
assert_equals(customElement.localName, 'my-custom-element');
assert_equals(customElement.namespaceURI, 'http://www.w3.org/1999/xhtml', 'A custom element HTML must use HTML namespace');
}, 'HTML parser must NOT create a custom element before customElements.define is called');
customElements.define('my-custom-element', MyCustomElement);
</script>
<my-custom-element id="instance2"></my-custom-element>
<script>
test(function () {
var customElement = document.getElementById('instance2');
assert_true(customElement instanceof HTMLElement, 'A resolved custom element must be an instance of HTMLElement');
assert_false(customElement instanceof HTMLUnknownElement, 'A resolved custom element must NOT be an instance of HTMLUnknownElement');
assert_true(customElement instanceof MyCustomElement, 'A resolved custom element must be an instance of that custom element');
assert_equals(customElement.localName, 'my-custom-element');
assert_equals(customElement.namespaceURI, 'http://www.w3.org/1999/xhtml', 'A custom element HTML must use HTML namespace');
}, 'HTML parser must create a defined custom element before executing inline scripts');
</script>
</body>
</html>

View file

@ -0,0 +1,91 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: Changes to the HTML parser</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="HTML parser must fallback to creating a HTMLUnknownElement when a custom element construction fails">
<link rel="help" href="https://html.spec.whatwg.org/#create-an-element-for-the-token">
<link rel="help" href="https://dom.spec.whatwg.org/#concept-create-element">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="log"></div>
<script>
setup({allow_uncaught_exception:true});
class ReturnsTextNode extends HTMLElement {
constructor() {
super();
return document.createTextNode('some text');
}
};
customElements.define('returns-text', ReturnsTextNode);
class ReturnsNonElementObject extends HTMLElement {
constructor() {
super();
return {};
}
};
customElements.define('returns-non-element-object', ReturnsNonElementObject);
class LacksSuperCall extends HTMLElement {
constructor() { }
};
customElements.define('lacks-super-call', LacksSuperCall);
class ThrowsException extends HTMLElement {
constructor() {
throw 'Bad';
}
};
customElements.define('throws-exception', ThrowsException);
</script>
<returns-text></returns-text>
<returns-non-element-object></returns-non-element-object>
<lacks-super-call></lacks-super-call>
<throws-exception></throws-exception>
<script>
test(function () {
var instance = document.querySelector('returns-text');
assert_false(instance instanceof ReturnsTextNode, 'HTML parser must NOT instantiate a custom element when the constructor returns a Text node');
assert_true(instance instanceof HTMLElement, 'The fallback element created by HTML parser must be an instance of HTMLElement');
assert_true(instance instanceof HTMLUnknownElement, 'The fallback element created by HTML parser must be an instance of HTMLUnknownElement');
}, 'HTML parser must create a fallback HTMLUnknownElement when a custom element constructor returns a Text node');
test(function () {
var instance = document.querySelector('returns-non-element-object');
assert_false(instance instanceof ReturnsNonElementObject, 'HTML parser must NOT instantiate a custom element when the constructor returns a non-Element object');
assert_true(instance instanceof HTMLElement, 'The fallback element created by HTML parser must be an instance of HTMLElement');
assert_true(instance instanceof HTMLUnknownElement, 'The fallback element created by HTML parser must be an instance of HTMLUnknownElement');
}, 'HTML parser must create a fallback HTMLUnknownElement when a custom element constructor returns non-Element object');
test(function () {
var instance = document.querySelector('lacks-super-call');
assert_false(instance instanceof LacksSuperCall, 'HTML parser must NOT instantiate a custom element when the constructor does not call super()');
assert_true(instance instanceof HTMLElement, 'The fallback element created by HTML parser must be an instance of HTMLElement');
assert_true(instance instanceof HTMLUnknownElement, 'The fallback element created by HTML parser must be an instance of HTMLUnknownElement');
}, 'HTML parser must create a fallback HTMLUnknownElement when a custom element constructor does not call super()');
test(function () {
var instance = document.querySelector('throws-exception');
assert_false(instance instanceof ThrowsException, 'HTML parser must NOT instantiate a custom element when the constructor throws an exception');
assert_true(instance instanceof HTMLElement, 'The fallback element created by HTML parser must be an instance of HTMLElement');
assert_true(instance instanceof HTMLUnknownElement, 'The fallback element created by HTML parser must be an instance of HTMLUnknownElement');
}, 'HTML parser must create a fallback HTMLUnknownElement when a custom element constructor throws an exception');
</script>
</body>
</html>

View file

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: Changes to the HTML parser</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="HTML parser must set the attributes and append the children on a custom element">
<link rel="help" href="https://html.spec.whatwg.org/#create-an-element-for-the-token">
<link rel="help" href="https://dom.spec.whatwg.org/#concept-create-element">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="log"></div>
<script>
var numberOfAttributesInConstructor;
var numberOfChildNodesInConstructor;
class MyCustomElement extends HTMLElement {
constructor(...args) {
super(...args);
numberOfAttributesInConstructor = this.attributes.length;
numberOfChildNodesInConstructor = this.childNodes.length;
}
};
customElements.define('my-custom-element', MyCustomElement);
</script>
<my-custom-element id="custom-element-id" class="class1 class2">hello <b>world</b></my-custom-element>
<script>
var customElement = document.querySelector('my-custom-element');
test(function () {
assert_equals(customElement.getAttribute('id'), 'custom-element-id', 'HTML parser must preserve the id attribute');
assert_equals(customElement.id, 'custom-element-id', 'HTML parser must preserve the semantics of reflect for the id attribute');
assert_equals(customElement.getAttribute('class'), 'class1 class2', 'HTML parser must preserve the class attribute');
assert_equals(customElement.classList.length, 2, 'HTML parser must initialize classList on custom elements');
assert_equals(customElement.classList[0], 'class1', 'HTML parser must initialize classList on custom elements');
assert_equals(customElement.classList[1], 'class2', 'HTML parser must initialize classList on custom elements');
}, 'HTML parser must set the attributes');
test(function () {
assert_equals(customElement.childNodes.length, 2, 'HTML parser must append child nodes');
assert_true(customElement.firstChild instanceof Text, 'HTML parser must append Text node child to a custom element');
assert_equals(customElement.firstChild.data, 'hello ', 'HTML parser must append Text node child to a custom element');
assert_true(customElement.lastChild instanceof HTMLElement, 'HTML parser must append a builtin element child to a custom element');
assert_true(customElement.lastChild.firstChild instanceof Text, 'HTML parser must preserve grandchild nodes of a custom element');
assert_equals(customElement.lastChild.firstChild.data, 'world', 'HTML parser must preserve grandchild nodes of a custom element');
}, 'HTML parser must append child nodes');
test(function () {
assert_equals(numberOfAttributesInConstructor, 0, 'HTML parser must not set attributes on a custom element before invoking the constructor');
assert_equals(numberOfChildNodesInConstructor, 0, 'HTML parser must not append child nodes to a custom element before invoking the constructor');
}, 'HTML parser must set the attributes or append children before calling constructor');
</script>
</body>
</html>

View file

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: HTML parser must construct a custom element instead of upgrading</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="HTML parser must construct a custom element instead of upgrading">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<link rel="help" href="https://html.spec.whatwg.org/#create-an-element-for-the-token">
<link rel="help" href="https://dom.spec.whatwg.org/#concept-create-element">
</head>
<body>
<div id="log"></div>
<script>
let anotherElementCreatedBeforeSuperCall = undefined;
let elementCreatedBySuperCall = undefined;
let shouldCreateElementBeforeSuperCall = true;
class InstantiatesItselfBeforeSuper extends HTMLElement {
constructor() {
if (shouldCreateElementBeforeSuperCall) {
shouldCreateElementBeforeSuperCall = false;
anotherElementCreatedBeforeSuperCall = new InstantiatesItselfBeforeSuper();
}
super();
elementCreatedBySuperCall = this;
}
};
customElements.define('instantiates-itself-before-super', InstantiatesItselfBeforeSuper);
let shouldCreateAnotherInstance = true;
let anotherInstance = undefined;
let firstInstance = undefined;
class ReturnsAnotherInstance extends HTMLElement {
constructor() {
super();
if (shouldCreateAnotherInstance) {
shouldCreateAnotherInstance = false;
firstInstance = this;
anotherInstance = new ReturnsAnotherInstance;
return anotherInstance;
} else
return this;
}
};
customElements.define('returns-another-instance', ReturnsAnotherInstance);
</script>
<instantiates-itself-before-super></instantiates-itself-before-super>
<returns-another-instance></returns-another-instance>
<script>
test(function () {
var instance = document.querySelector('instantiates-itself-before-super');
assert_equals(instance instanceof InstantiatesItselfBeforeSuper, 'HTML parser must insert the synchronously constructed custom element');
assert_equals(instance, elementCreatedBySuperCall, 'HTML parser must insert the element returned by the custom element constructor');
assert_not_equals(instance, anotherElementCreatedBeforeSuperCall, 'HTML parser must not insert another instance of the custom element created before super() call');
assert_equals(anotherElementCreatedBeforeSuperCall.parentNode, null, 'HTML parser must not insert another instance of the custom element created before super() call');
}, 'HTML parser must use the returned value of the custom element constructor instead of the one created before super() call');
test(function () {
var instance = document.querySelector('returns-another-instance');
assert_equals(instance instanceof ReturnsAnotherInstance, 'HTML parser must insert the synchronously constructed custom element');
assert_equals(instance, anotherInstance, 'HTML parser must insert the element returned by the custom element constructor');
assert_not_equals(instance, firstInstance, 'HTML parser must not insert the element created by super() call if the constructor returned another element');
assert_equals(firstInstance.parentNode, null, 'HTML parser must not insert the element created by super() call if the constructor returned another element');
}, 'HTML parser must use the returned value of the custom element constructor instead using the one created in super() call');
</script>
</body>
</html>

View file

@ -0,0 +1,126 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: HTML parser must use the owner document's custom element registry</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="HTML parser must use the owner document's custom element registry">
<link rel="help" href="https://html.spec.whatwg.org/#create-an-element-for-the-token">
<link rel="help" href="https://dom.spec.whatwg.org/#concept-create-element">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="log"></div>
<script>
class MyCustomElement extends HTMLElement { };
customElements.define('my-custom-element', MyCustomElement);
document.write('<template><my-custom-element></my-custom-element></template>');
test(function () {
var template = document.querySelector('template');
var instance = template.content.firstChild;
assert_true(instance instanceof HTMLElement,
'A custom element inside a template element must be an instance of HTMLElement');
assert_false(instance instanceof MyCustomElement,
'A custom element must not be instantiated inside a template element using the registry of the template element\'s owner document');
assert_equals(instance.ownerDocument, template.content.ownerDocument,
'Custom elements inside a template must use the appropriate template contents owner document as the owner document');
}, 'HTML parser must not instantiate custom elements inside template elements');
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.contentDocument.body.innerHTML = '<my-custom-element></my-custom-element>';
test(function () {
var instance = iframe.contentDocument.querySelector('my-custom-element');
assert_true(instance instanceof iframe.contentWindow.HTMLElement);
assert_false(instance instanceof MyCustomElement);
}, 'HTML parser must not use the registry of the owner element\'s document inside an iframe');
class ElementInIFrame extends iframe.contentWindow.HTMLElement { };
iframe.contentWindow.customElements.define('element-in-iframe', ElementInIFrame);
iframe.contentDocument.body.innerHTML = '<element-in-iframe></element-in-iframe>';
test(function () {
var instance = iframe.contentDocument.querySelector('element-in-iframe');
assert_true(instance instanceof iframe.contentWindow.HTMLElement, 'A custom element inside an iframe must be an instance of HTMLElement');
assert_true(instance instanceof ElementInIFrame,
'A custom element must be instantiated inside an iframe using the registry of the content document');
assert_equals(instance.ownerDocument, iframe.contentDocument,
'The owner document of custom elements inside an iframe must be the content document of the iframe');
}, 'HTML parser must use the registry of the content document inside an iframe');
document.write('<element-in-iframe></element-in-iframe>');
test(function () {
var instance = document.querySelector('element-in-iframe');
assert_true(instance instanceof HTMLElement);
assert_false(instance instanceof ElementInIFrame);
}, 'HTML parser must not instantiate a custom element defined inside an frame in frame element\'s owner document');
document.body.removeChild(iframe);
test(function () {
var windowlessDocument = document.implementation.createHTMLDocument();
windowlessDocument.open();
windowlessDocument.write('<my-custom-element></my-custom-element>');
windowlessDocument.close();
var instance = windowlessDocument.querySelector('my-custom-element');
assert_true(instance instanceof HTMLElement);
assert_false(instance instanceof MyCustomElement);
}, 'HTML parser must use the registry of window.document in a document created by document.implementation.createHTMLDocument()');
test(function () {
var windowlessDocument = document.implementation.createDocument ('http://www.w3.org/1999/xhtml', 'html', null);
windowlessDocument.documentElement.innerHTML = '<my-custom-element></my-custom-element>';
var instance = windowlessDocument.querySelector('my-custom-element');
assert_true(instance instanceof HTMLElement);
assert_false(instance instanceof MyCustomElement);
}, 'HTML parser must use the registry of window.document in a document created by document.implementation.createXHTMLDocument()');
test(function () {
var windowlessDocument = new Document;
windowlessDocument.appendChild(windowlessDocument.createElement('html'));
windowlessDocument.documentElement.innerHTML = '<my-custom-element></my-custom-element>';
var instance = windowlessDocument.querySelector('my-custom-element');
assert_true(instance instanceof Element);
assert_false(instance instanceof MyCustomElement);
}, 'HTML parser must use the registry of window.document in a document created by new Document');
promise_test(function () {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', '../resources/empty-html-document.html');
xhr.overrideMimeType('text/xml');
xhr.onload = function () { resolve(xhr.responseXML); }
xhr.onerror = function () { reject('Failed to fetch the document'); }
xhr.send();
}).then(function (doc) {
doc.documentElement.innerHTML = '<my-custom-element></my-custom-element>';
var instance = doc.querySelector('my-custom-element');
assert_true(instance instanceof Element);
assert_false(instance instanceof MyCustomElement);
});
}, 'HTML parser must use the registry of window.document in a document created by XMLHttpRequest');
</script>
</body>
</html>

View file

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: Custom element reactions must be invoked before returning to author scripts</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="Custom element reactions must be invoked before returning to author scripts">
<link rel="help" href="https://html.spec.whatwg.org/multipage/scripting.html#invoke-custom-element-reactions">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="log"></div>
<script>
class MyCustomElement extends HTMLElement {
attributeChangedCallback(...args) {
this.handler(...args);
}
handler() { }
}
MyCustomElement.observedAttributes = ['data-title', 'title'];
customElements.define('my-custom-element', MyCustomElement);
test(function () {
var instance = document.createElement('my-custom-element');
var anotherInstance = document.createElement('my-custom-element');
var callbackOrder = [];
instance.handler = function () {
callbackOrder.push([this, 'begin']);
anotherInstance.setAttribute('data-title', 'baz');
callbackOrder.push([this, 'end']);
}
anotherInstance.handler = function () {
callbackOrder.push([this, 'begin']);
callbackOrder.push([this, 'end']);
}
instance.setAttribute('title', 'foo');
assert_equals(callbackOrder.length, 4);
assert_array_equals(callbackOrder[0], [instance, 'begin']);
assert_array_equals(callbackOrder[1], [anotherInstance, 'begin']);
assert_array_equals(callbackOrder[2], [anotherInstance, 'end']);
assert_array_equals(callbackOrder[3], [instance, 'end']);
}, 'setAttribute and removeAttribute must enqueue and invoke attributeChangedCallback');
test(function () {
var shouldCloneAnotherInstance = false;
var anotherInstanceClone;
var log = [];
class SelfCloningElement extends HTMLElement {
constructor() {
super();
log.push([this, 'begin']);
if (shouldCloneAnotherInstance) {
shouldCloneAnotherInstance = false;
anotherInstanceClone = anotherInstance.cloneNode(false);
}
log.push([this, 'end']);
}
}
customElements.define('self-cloning-element', SelfCloningElement);
var instance = document.createElement('self-cloning-element');
var anotherInstance = document.createElement('self-cloning-element');
shouldCloneAnotherInstance = true;
assert_equals(log.length, 4);
var instanceClone = instance.cloneNode(false);
assert_equals(log.length, 8);
assert_array_equals(log[0], [instance, 'begin']);
assert_array_equals(log[1], [instance, 'end']);
assert_array_equals(log[2], [anotherInstance, 'begin']);
assert_array_equals(log[3], [anotherInstance, 'end']);
assert_array_equals(log[4], [instanceClone, 'begin']);
assert_array_equals(log[5], [anotherInstanceClone, 'begin']);
assert_array_equals(log[6], [anotherInstanceClone, 'end']);
assert_array_equals(log[7], [instanceClone, 'end']);
}, 'Calling Node.prototype.cloneNode(false) must push a new element queue to the processing stack');
</script>
</body>
</html>

View file

@ -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>

View file

@ -0,0 +1,85 @@
<!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');
test_mutating_style_property_value(function (instance, propertyName, idlName, value) {
instance.style.setPropertyValue(propertyName, value);
}, 'setPropertyValue on CSSStyleDeclaration');
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>

View file

@ -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, after, replaceWith, and remove of ChildNode 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.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();
}, 'replaceWith on ChildNode');
</script>
</body>
</html>

View file

@ -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>

View file

@ -0,0 +1,219 @@
<!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(['lang']);
var instance = document.createElement(element.name);
instance.setAttribute('class', 'hello world');
assert_array_equals(element.takeLog().types(), ['constructed']);
instance.classList.toggle('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.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>

View file

@ -0,0 +1,154 @@
<!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) {
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) {
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>

View file

@ -0,0 +1,82 @@
<!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');
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>

View file

@ -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>

View file

@ -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>

View file

@ -0,0 +1,42 @@
<!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, 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');
testReflectAttribute('dropzone', 'dropzone', 'copy', 'move', 'dropzone on HTMLElement');
testReflectAttribute('contextMenu', 'contextmenu', 'menu1', 'menu2', 'contextMenu on HTMLElement');
testReflectAttributeWithContentValues('spellcheck', 'spellcheck', true, 'true', false, 'false', 'spellcheck on HTMLElement');
testNodeDisconnector(function (customElement) {
customElement.parentNode.innerText = '';
}, 'innerText on HTMLElement');
if ('outerText' in HTMLElement.prototype) {
// Not yet to be in the standard but all but Gecko supports this property: https://github.com/whatwg/html/issues/668
testNodeDisconnector(function (customElement) {
customElement.outerText = '';
}, 'outerText on HTMLElement');
}
</script>
</body>
</html>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -0,0 +1,361 @@
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) {
test(function () {
var element = define_new_custom_element([contentAttributeName]);
var instance = document.createElement(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 () {
var element = define_new_custom_element([contentAttributeName]);
var instance = document.createElement(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) {
testReflectAttributeWithContentValues(jsAttributeName, contentAttributeName, validValue1, validValue1, validValue2, validValue2, name);
}
function testReflectBooleanAttribute(jsAttributeName, contentAttributeName, name) {
testReflectAttributeWithContentValues(jsAttributeName, contentAttributeName, true, '', false, null, name);
}
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');
}

View file

@ -16,7 +16,215 @@ function test_with_window(f, name, srcdoc) {
promise_test((t) => {
return create_window_in_test(t, srcdoc)
.then((w) => {
f(w);
f(w, w.document);
});
}, name);
}
function define_custom_element_in_window(window, name, observedAttributes) {
let log = [];
class CustomElement extends window.HTMLElement {
constructor() {
super();
log.push(create_constructor_log(this));
}
attributeChangedCallback(...args) {
log.push(create_attribute_changed_callback_log(this, ...args));
}
connectedCallback() { log.push(create_connected_callback_log(this)); }
disconnectedCallback() { log.push(create_disconnected_callback_log(this)); }
adoptedCallback(oldDocument, newDocument) { log.push({type: 'adopted', element: this, oldDocument: oldDocument, newDocument: newDocument}); }
}
CustomElement.observedAttributes = observedAttributes;
window.customElements.define(name, CustomElement);
return {
name: name,
class: CustomElement,
takeLog: function () {
let currentLog = log; log = [];
currentLog.types = () => currentLog.map((entry) => entry.type);
currentLog.last = () => currentLog[currentLog.length - 1];
return currentLog;
}
};
}
function create_constructor_log(element) {
return {type: 'constructed', element: element};
}
function assert_constructor_log_entry(log, element) {
assert_equals(log.type, 'constructed');
assert_equals(log.element, element);
}
function create_connected_callback_log(element) {
return {type: 'connected', element: element};
}
function assert_connected_log_entry(log, element) {
assert_equals(log.type, 'connected');
assert_equals(log.element, element);
}
function create_disconnected_callback_log(element) {
return {type: 'disconnected', element: element};
}
function assert_disconnected_log_entry(log, element) {
assert_equals(log.type, 'disconnected');
assert_equals(log.element, element);
}
function assert_adopted_log_entry(log, element) {
assert_equals(log.type, 'adopted');
assert_equals(log.element, element);
}
function create_adopted_callback_log(element) {
return {type: 'adopted', element: element};
}
function create_attribute_changed_callback_log(element, name, oldValue, newValue, namespace) {
return {
type: 'attributeChanged',
element: element,
name: name,
namespace: namespace,
oldValue: oldValue,
newValue: newValue,
actualValue: element.getAttributeNS(namespace, name)
};
}
function assert_attribute_log_entry(log, expected) {
assert_equals(log.type, 'attributeChanged');
assert_equals(log.name, expected.name);
assert_equals(log.oldValue, expected.oldValue);
assert_equals(log.newValue, expected.newValue);
assert_equals(log.actualValue, expected.newValue);
assert_equals(log.namespace, expected.namespace);
}
function define_new_custom_element(observedAttributes) {
let log = [];
let name = 'custom-element-' + define_new_custom_element._element_number++;
class CustomElement extends HTMLElement {
constructor() {
super();
log.push({type: 'constructed', element: this});
}
attributeChangedCallback(...args) {
log.push(create_attribute_changed_callback_log(this, ...args));
}
connectedCallback() { log.push({type: 'connected', element: this}); }
disconnectedCallback() { log.push({type: 'disconnected', element: this}); }
adoptedCallback(oldDocument, newDocument) { log.push({type: 'adopted', element: this, oldDocument: oldDocument, newDocument: newDocument}); }
}
CustomElement.observedAttributes = observedAttributes;
customElements.define(name, CustomElement);
return {
name: name,
class: CustomElement,
takeLog: function () {
let currentLog = log; log = [];
currentLog.types = () => currentLog.map((entry) => entry.type);
currentLog.last = () => currentLog[currentLog.length - 1];
return currentLog;
}
};
}
define_new_custom_element._element_number = 1;
function document_types() {
return [
{
name: 'the document',
create: function () { return Promise.resolve(document); },
isOwner: true,
hasBrowsingContext: true,
},
{
name: 'the document of the template elements',
create: function () {
return new Promise(function (resolve) {
var template = document.createElementNS('http://www.w3.org/1999/xhtml', 'template');
var doc = template.content.ownerDocument;
if (!doc.documentElement)
doc.appendChild(doc.createElement('html'));
resolve(doc);
});
},
hasBrowsingContext: false,
},
{
name: 'a new document',
create: function () {
return new Promise(function (resolve) {
var doc = new Document();
doc.appendChild(doc.createElement('html'));
resolve(doc);
});
},
hasBrowsingContext: false,
},
{
name: 'a cloned document',
create: function () {
return new Promise(function (resolve) {
var doc = document.cloneNode(false);
doc.appendChild(doc.createElement('html'));
resolve(doc);
});
},
hasBrowsingContext: false,
},
{
name: 'a document created by createHTMLDocument',
create: function () {
return Promise.resolve(document.implementation.createHTMLDocument());
},
hasBrowsingContext: false,
},
{
name: 'an HTML document created by createDocument',
create: function () {
return Promise.resolve(document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null));
},
hasBrowsingContext: false,
},
{
name: 'the document of an iframe',
create: function () {
return new Promise(function (resolve, reject) {
var iframe = document.createElement('iframe');
iframe.onload = function () { resolve(iframe.contentDocument); }
iframe.onerror = function () { reject('Failed to load an empty iframe'); }
document.body.appendChild(iframe);
});
},
hasBrowsingContext: true,
},
{
name: 'an HTML document fetched by XHR',
create: function () {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'resources/empty-html-document.html');
xhr.overrideMimeType('text/xml');
xhr.onload = function () { resolve(xhr.responseXML); }
xhr.onerror = function () { reject('Failed to fetch the document'); }
xhr.send();
});
},
hasBrowsingContext: false,
}
];
}

View file

@ -0,0 +1,5 @@
<!DOCTYPE html>
<html>
<body>
</body>
</html>

View file

@ -0,0 +1,190 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: Enqueue a custom element upgrade reaction</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="Enqueue a custom element upgrade reaction must upgrade a custom element">
<link rel="help" href="https://dom.spec.whatwg.org/#concept-create-element">
<link rel="help" href="https://html.spec.whatwg.org/multipage/scripting.html#concept-try-upgrade">
<link rel="help" href="https://html.spec.whatwg.org/multipage/scripting.html#enqueue-a-custom-element-upgrade-reaction">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
</head>
<body>
<div id="log"></div>
<script>
class PredefinedCustomElement extends HTMLElement {}
customElements.define('predefined-custom-element', PredefinedCustomElement);
var customElementNumber = 1;
function generateNextCustomElementName() { return 'custom-' + customElementNumber++; }
// Tests for documents without a browsing context.
document_types().filter(function (entry) { return !entry.isOwner && !entry.hasBrowsingContext; }).forEach(function (entry) {
var documentName = entry.name;
var getDocument = entry.create;
promise_test(function () {
return getDocument().then(function (doc) {
assert_false(doc.createElement('predefined-custom-element') instanceof PredefinedCustomElement);
});
}, 'Creating an element in ' + documentName + ' must not enqueue a custom element upgrade reaction'
+ ' because the document does not have a browsing context');
promise_test(function () {
var name = generateNextCustomElementName();
var unresolvedElement = document.createElement(name);
assert_equals(unresolvedElement.__proto__, HTMLElement.prototype,
'[[Prototype]] internal slot of the unresolved custom element must be the HTMLElement prototype');
return getDocument().then(function (doc) {
var unresolvedElementInDoc = doc.createElement(name);
var prototype = (unresolvedElementInDoc.namespaceURI == 'http://www.w3.org/1999/xhtml' ? HTMLElement : Element).prototype;
assert_equals(unresolvedElementInDoc.__proto__, prototype,
'[[Prototype]] internal slot of the unresolved custom element must be the ' + prototype.toString() + ' prototype');
var someCustomElement = class extends HTMLElement {};
customElements.define(name, someCustomElement);
assert_equals(unresolvedElementInDoc.__proto__, prototype, '"define" must not upgrade a disconnected unresolved custom elements');
doc.documentElement.appendChild(unresolvedElementInDoc);
assert_equals(unresolvedElementInDoc.__proto__, prototype,
'Inserting an element into a document without a browsing context must not enqueue a custom element upgrade reaction');
});
}, 'Creating an element in ' + documentName + ' and inserting into the document must not enqueue a custom element upgrade reaction');
promise_test(function () {
var name = generateNextCustomElementName();
var unresolvedElement = document.createElement(name);
assert_equals(unresolvedElement.__proto__, HTMLElement.prototype,
'[[Prototype]] internal slot of the unresolved custom element must be the HTMLElement prototype');
return getDocument().then(function (doc) {
var unresolvedElementInDoc = doc.createElement(name);
var prototype = (unresolvedElementInDoc.namespaceURI == 'http://www.w3.org/1999/xhtml' ? HTMLElement : Element).prototype;
assert_equals(unresolvedElementInDoc.__proto__, prototype,
'[[Prototype]] internal slot of the unresolved custom element must be the ' + prototype.toString() + ' prototype');
var someCustomElement = class extends HTMLElement {};
customElements.define(name, someCustomElement);
assert_equals(unresolvedElementInDoc.__proto__, prototype, '"define" must not upgrade a disconnected unresolved custom elements');
document.body.appendChild(unresolvedElementInDoc);
if (unresolvedElementInDoc.namespaceURI == 'http://www.w3.org/1999/xhtml') {
assert_equals(unresolvedElementInDoc.__proto__, someCustomElement.prototype,
'Inserting an element into a document with a browsing context must enqueue a custom element upgrade reaction');
} else {
assert_equals(unresolvedElementInDoc.__proto__, prototype,
'Looking up a custom element definition must return null if the element is not in the HTML namespace');
}
});
}, 'Creating an element in ' + documentName + ' and adopting back to a document with browsing context must enqueue a custom element upgrade reaction');
});
// Tests for documents with a browsing context.
document_types().filter(function (entry) { return !entry.isOwner && entry.hasBrowsingContext; }).forEach(function (entry) {
var documentName = entry.name;
var getDocument = entry.create;
promise_test(function () {
return getDocument().then(function (doc) {
assert_false(doc.createElement('predefined-custom-element') instanceof PredefinedCustomElement);
});
}, 'Creating an element in ' + documentName + ' must not enqueue a custom element upgrade reaction if there is no matching definition');
promise_test(function () {
return getDocument().then(function (doc) {
var docWindow = doc.defaultView;
class DistinctPredefinedCustomElement extends docWindow.HTMLElement { };
docWindow.customElements.define('predefined-custom-element', DistinctPredefinedCustomElement);
assert_true(doc.createElement('predefined-custom-element') instanceof DistinctPredefinedCustomElement);
});
}, 'Creating an element in ' + documentName + ' must enqueue a custom element upgrade reaction if there is a matching definition');
promise_test(function () {
var unresolvedElement = document.createElement('unresolved-element');
return getDocument().then(function (doc) {
var docWindow = doc.defaultView;
class UnresolvedElement extends docWindow.HTMLElement { };
var unresolvedElementInDoc = doc.createElement('unresolved-element');
assert_equals(unresolvedElement.__proto__, HTMLElement.prototype);
assert_equals(unresolvedElementInDoc.__proto__, docWindow.HTMLElement.prototype);
docWindow.customElements.define('unresolved-element', UnresolvedElement);
assert_equals(unresolvedElement.__proto__, HTMLElement.prototype);
assert_equals(unresolvedElementInDoc.__proto__, docWindow.HTMLElement.prototype);
});
}, '"define" in ' + documentName + ' must not enqueue a custom element upgrade reaction on a disconnected unresolved custom element');
promise_test(function () {
var unresolvedElement = document.createElement('unresolved-element');
return getDocument().then(function (doc) {
var docWindow = doc.defaultView;
class UnresolvedElement extends docWindow.HTMLElement { };
var unresolvedElementInDoc = doc.createElement('unresolved-element');
assert_equals(unresolvedElement.__proto__, HTMLElement.prototype);
assert_equals(unresolvedElementInDoc.__proto__, docWindow.HTMLElement.prototype);
docWindow.customElements.define('unresolved-element', UnresolvedElement);
doc.documentElement.appendChild(unresolvedElementInDoc);
assert_equals(unresolvedElement.__proto__, HTMLElement.prototype);
assert_equals(unresolvedElementInDoc.__proto__, UnresolvedElement.prototype);
});
}, 'Inserting an unresolved custom element into ' + documentName + ' must enqueue a custom element upgrade reaction');
promise_test(function () {
var unresolvedElement = document.createElement('unresolved-element');
return getDocument().then(function (doc) {
var docWindow = doc.defaultView;
class UnresolvedElement extends docWindow.HTMLElement { };
var unresolvedElementInDoc = doc.createElement('unresolved-element');
doc.documentElement.appendChild(unresolvedElementInDoc);
assert_equals(unresolvedElement.__proto__, HTMLElement.prototype);
assert_equals(unresolvedElementInDoc.__proto__, docWindow.HTMLElement.prototype);
docWindow.customElements.define('unresolved-element', UnresolvedElement);
assert_equals(unresolvedElement.__proto__, HTMLElement.prototype);
assert_equals(unresolvedElementInDoc.__proto__, UnresolvedElement.prototype);
});
}, '"define" in ' + documentName + ' must enqueue a custom element upgrade reaction on a connected unresolved custom element');
promise_test(function () {
var unresolvedElement = document.createElement('unresolved-element');
return getDocument().then(function (doc) {
var docWindow = doc.defaultView;
class UnresolvedElement extends docWindow.HTMLElement { };
assert_false(unresolvedElement instanceof UnresolvedElement);
docWindow.customElements.define('unresolved-element', UnresolvedElement);
doc.adoptNode(unresolvedElement);
assert_false(unresolvedElement instanceof UnresolvedElement);
});
}, 'Adopting (and leaving disconnceted) an unresolved custom element into ' + documentName + ' must not enqueue a custom element upgrade reaction');
promise_test(function () {
var unresolvedElement = document.createElement('unresolved-element');
return getDocument().then(function (doc) {
var docWindow = doc.defaultView;
class UnresolvedElement extends docWindow.HTMLElement { };
assert_false(unresolvedElement instanceof UnresolvedElement);
docWindow.customElements.define('unresolved-element', UnresolvedElement);
doc.documentElement.appendChild(unresolvedElement);
assert_true(unresolvedElement instanceof UnresolvedElement);
});
}, 'Adopting and inserting an unresolved custom element into ' + documentName + ' must enqueue a custom element upgrade reaction');
});
</script>
</body>
</html>

View file

@ -0,0 +1,185 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: Upgrading</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="Node.prototype.cloneNode should upgrade a custom element">
<link rel="help" href="https://html.spec.whatwg.org/#upgrades">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/custom-elements-helpers.js"></script>
</head>
<body>
<div id="log"></div>
<script>
test(function () {
class MyCustomElement extends HTMLElement {}
customElements.define('my-custom-element', MyCustomElement);
var instance = document.createElement('my-custom-element');
assert_true(instance instanceof HTMLElement);
assert_true(instance instanceof MyCustomElement);
var clone = instance.cloneNode(false);
assert_not_equals(instance, clone);
assert_true(clone instanceof HTMLElement,
'A cloned custom element must be an instance of HTMLElement');
assert_true(clone instanceof MyCustomElement,
'A cloned custom element must be an instance of the custom element');
}, 'Node.prototype.cloneNode(false) must be able to clone a custom element');
test_with_window(function (contentWindow) {
var contentDocument = contentWindow.document;
class MyCustomElement extends contentWindow.HTMLElement {}
contentWindow.customElements.define('my-custom-element', MyCustomElement);
var instance = contentDocument.createElement('my-custom-element');
assert_true(instance instanceof contentWindow.HTMLElement);
assert_true(instance instanceof MyCustomElement);
var clone = instance.cloneNode(false);
assert_not_equals(instance, clone);
assert_true(clone instanceof contentWindow.HTMLElement,
'A cloned custom element must be an instance of HTMLElement');
assert_true(clone instanceof MyCustomElement,
'A cloned custom element must be an instance of the custom element');
}, 'Node.prototype.cloneNode(false) must be able to clone a custom element inside an iframe');
test_with_window(function (contentWindow) {
var contentDocument = contentWindow.document;
class MyCustomElement extends contentWindow.HTMLElement { }
contentWindow.customElements.define('my-custom-element', MyCustomElement);
var instance = contentDocument.createElement('my-custom-element');
var container = contentDocument.createElement('div');
container.appendChild(instance);
var containerClone = container.cloneNode(true);
assert_true(containerClone instanceof contentWindow.HTMLDivElement);
var clone = containerClone.firstChild;
assert_not_equals(instance, clone);
assert_true(clone instanceof contentWindow.HTMLElement,
'A cloned custom element must be an instance of HTMLElement');
assert_true(clone instanceof MyCustomElement,
'A cloned custom element must be an instance of the custom element');
}, 'Node.prototype.cloneNode(true) must be able to clone a descendent custom element');
test_with_window(function (contentWindow) {
var parentNodeInConstructor;
var previousSiblingInConstructor;
var nextSiblingInConstructor;
class MyCustomElement extends contentWindow.HTMLElement {
constructor() {
super();
parentNodeInConstructor = this.parentNode;
previousSiblingInConstructor = this.previousSibling;
nextSiblingInConstructor = this.nextSibling;
}
}
contentWindow.customElements.define('my-custom-element', MyCustomElement);
var contentDocument = contentWindow.document;
var instance = contentDocument.createElement('my-custom-element');
var siblingBeforeInstance = contentDocument.createElement('b');
var siblingAfterInstance = contentDocument.createElement('a');
var container = contentDocument.createElement('div');
container.appendChild(siblingBeforeInstance);
container.appendChild(instance);
container.appendChild(siblingAfterInstance);
var containerClone = container.cloneNode(true);
assert_equals(parentNodeInConstructor, containerClone,
'An upgraded element must have its parentNode set before the custom element constructor is called');
assert_equals(previousSiblingInConstructor, containerClone.firstChild,
'An upgraded element must have its previousSibling set before the custom element constructor is called');
assert_equals(nextSiblingInConstructor, containerClone.lastChild,
'An upgraded element must have its nextSibling set before the custom element constructor is called');
}, 'Node.prototype.cloneNode(true) must set parentNode, previousSibling, and nextSibling before upgrading custom elements');
test_with_window(function (contentWindow) {
class MyCustomElement extends contentWindow.HTMLElement {
constructor(doNotCreateItself) {
super();
if (!doNotCreateItself)
new MyCustomElement(true);
}
}
contentWindow.customElements.define('my-custom-element', MyCustomElement);
var instance = new MyCustomElement(false);
var uncaughtError;
contentWindow.onerror = function (message, url, lineNumber, columnNumber, error) { uncaughtError = error; return true; }
instance.cloneNode(false);
assert_equals(uncaughtError.name, 'InvalidStateError');
}, 'HTMLElement constructor must throw an InvalidStateError when the top of the construction stack is marked AlreadyConstructed'
+ ' due to a custom element constructor constructing itself after super() call');
test_with_window(function (contentWindow) {
class MyCustomElement extends contentWindow.HTMLElement {
constructor(doNotCreateItself) {
if (!doNotCreateItself)
new MyCustomElement(true);
super();
}
}
contentWindow.customElements.define('my-custom-element', MyCustomElement);
var instance = new MyCustomElement(false);
var uncaughtError;
contentWindow.onerror = function (message, url, lineNumber, columnNumber, error) { uncaughtError = error; return true; }
instance.cloneNode(false);
assert_equals(uncaughtError.name, 'InvalidStateError');
}, 'HTMLElement constructor must throw an InvalidStateError when the top of the construction stack is marked AlreadyConstructed'
+ ' due to a custom element constructor constructing itself before super() call');
test_with_window(function (contentWindow) {
var contentDocument = contentWindow.document;
var returnSpan = false;
class MyCustomElement extends contentWindow.HTMLElement {
constructor() {
super();
if (returnSpan)
return contentDocument.createElement('span');
}
}
contentWindow.customElements.define('my-custom-element', MyCustomElement);
var instance = new MyCustomElement(false);
returnSpan = true;
var uncaughtError;
contentWindow.onerror = function (message, url, lineNumber, columnNumber, error) { uncaughtError = error; return true; }
instance.cloneNode(false);
assert_equals(uncaughtError.name, 'InvalidStateError');
}, 'Upgrading a custom element must throw InvalidStateError when the custom element\'s constructor returns another element');
test_with_window(function (contentWindow) {
var contentDocument = contentWindow.document;
var instance = contentDocument.createElement('my-custom-element');
contentDocument.body.appendChild(instance);
var calls = [];
class MyCustomElement extends contentWindow.HTMLElement {
constructor() {
super();
calls.push(this);
throw 'bad';
}
}
var uncaughtError;
contentWindow.onerror = function (message, url, lineNumber, columnNumber, error) { uncaughtError = error; return true; }
contentWindow.customElements.define('my-custom-element', MyCustomElement);
assert_equals(uncaughtError, 'bad');
assert_array_equals(calls, [instance]);
contentDocument.body.removeChild(instance);
contentDocument.body.appendChild(instance);
assert_array_equals(calls, [instance]);
}, 'Inserting an element must not try to upgrade a custom element when it had already failed to upgrade once');
</script>
</body>
</html>

View file

@ -0,0 +1,158 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: Upgrading custom elements should enqueue attributeChanged and connected callbacks</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="Upgrading custom elements should enqueue attributeChanged and connected callbacksml">
<meta name="help" content="https://html.spec.whatwg.org/#upgrades">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/custom-elements-helpers.js"></script>
</head>
<body>
<div id="log"></div>
<script>
setup({allow_uncaught_exception:true});
test_with_window(function (contentWindow) {
const contentDocument = contentWindow.document;
contentDocument.write('<test-element id="some" title="This is a test">');
const undefinedElement = contentDocument.querySelector('test-element');
assert_equals(Object.getPrototypeOf(undefinedElement), contentWindow.HTMLElement.prototype);
let log = [];
class TestElement extends contentWindow.HTMLElement {
constructor() {
super();
log.push(create_constructor_log(this));
}
attributeChangedCallback(...args) {
log.push(create_attribute_changed_callback_log(this, ...args));
}
static get observedAttributes() { return ['id', 'title']; }
}
contentWindow.customElements.define('test-element', TestElement);
assert_equals(Object.getPrototypeOf(undefinedElement), TestElement.prototype);
assert_equals(log.length, 3);
assert_constructor_log_entry(log[0], undefinedElement);
assert_attribute_log_entry(log[1], {name: 'id', oldValue: null, newValue: 'some', namespace: null});
assert_attribute_log_entry(log[2], {name: 'title', oldValue: null, newValue: 'This is a test', namespace: null});
}, 'Upgrading a custom element must enqueue attributeChangedCallback on each attribute');
test_with_window(function (contentWindow) {
const contentDocument = contentWindow.document;
contentDocument.write('<test-element id="some" title="This is a test" class="foo">');
const undefinedElement = contentDocument.querySelector('test-element');
assert_equals(Object.getPrototypeOf(undefinedElement), contentWindow.HTMLElement.prototype);
let log = [];
class TestElement extends contentWindow.HTMLElement {
constructor() {
super();
log.push(create_constructor_log(this));
}
attributeChangedCallback(...args) {
log.push(create_attribute_changed_callback_log(this, ...args));
}
static get observedAttributes() { return ['class', 'id']; }
}
contentWindow.customElements.define('test-element', TestElement);
assert_equals(Object.getPrototypeOf(undefinedElement), TestElement.prototype);
assert_equals(log.length, 3);
assert_constructor_log_entry(log[0], undefinedElement);
assert_attribute_log_entry(log[1], {name: 'id', oldValue: null, newValue: 'some', namespace: null});
assert_attribute_log_entry(log[2], {name: 'class', oldValue: null, newValue: 'foo', namespace: null});
}, 'Upgrading a custom element not must enqueue attributeChangedCallback on unobserved attributes');
test_with_window(function (contentWindow) {
const contentDocument = contentWindow.document;
contentDocument.write('<test-element id="some" title="This is a test" class="foo">');
const undefinedElement = contentDocument.querySelector('test-element');
assert_equals(Object.getPrototypeOf(undefinedElement), contentWindow.HTMLElement.prototype);
let log = [];
class TestElement extends contentWindow.HTMLElement {
constructor() {
super();
log.push(create_constructor_log(this));
}
connectedCallback(...args) {
log.push(create_connected_callback_log(this, ...args));
}
}
contentWindow.customElements.define('test-element', TestElement);
assert_equals(Object.getPrototypeOf(undefinedElement), TestElement.prototype);
assert_equals(log.length, 2);
assert_constructor_log_entry(log[0], undefinedElement);
assert_connected_log_entry(log[1], undefinedElement);
}, 'Upgrading a custom element must enqueue connectedCallback if the element in the document');
test_with_window(function (contentWindow) {
const contentDocument = contentWindow.document;
contentDocument.write('<test-element id="some" title="This is a test" class="foo">');
const undefinedElement = contentDocument.querySelector('test-element');
assert_equals(Object.getPrototypeOf(undefinedElement), contentWindow.HTMLElement.prototype);
let log = [];
class TestElement extends contentWindow.HTMLElement {
constructor() {
super();
log.push(create_constructor_log(this));
}
connectedCallback(...args) {
log.push(create_connected_callback_log(this, ...args));
}
attributeChangedCallback(...args) {
log.push(create_attribute_changed_callback_log(this, ...args));
}
static get observedAttributes() { return ['class', 'id']; }
}
contentWindow.customElements.define('test-element', TestElement);
assert_equals(Object.getPrototypeOf(undefinedElement), TestElement.prototype);
assert_equals(log.length, 4);
assert_constructor_log_entry(log[0], undefinedElement);
assert_attribute_log_entry(log[1], {name: 'id', oldValue: null, newValue: 'some', namespace: null});
assert_attribute_log_entry(log[2], {name: 'class', oldValue: null, newValue: 'foo', namespace: null});
assert_connected_log_entry(log[3], undefinedElement);
}, 'Upgrading a custom element must enqueue attributeChangedCallback before connectedCallback');
test_with_window(function (contentWindow) {
const contentDocument = contentWindow.document;
contentDocument.write('<test-element id="some" title="This is a test" class="foo">');
const undefinedElement = contentDocument.querySelector('test-element');
assert_equals(Object.getPrototypeOf(undefinedElement), contentWindow.HTMLElement.prototype);
let log = [];
class TestElement extends contentWindow.HTMLElement {
constructor() {
super();
log.push(create_constructor_log(this));
throw 'Exception thrown as a part of test';
}
connectedCallback(...args) {
log.push(create_connected_callback_log(this, ...args));
}
attributeChangedCallback(...args) {
log.push(create_attribute_changed_callback_log(this, ...args));
}
static get observedAttributes() { return ['class', 'id']; }
}
contentWindow.customElements.define('test-element', TestElement);
assert_equals(Object.getPrototypeOf(undefinedElement), TestElement.prototype);
assert_equals(log.length, 1);
assert_constructor_log_entry(log[0], undefinedElement);
}, 'Upgrading a custom element must not invoke attributeChangedCallback and connectedCallback when the element failed to upgrade');
</script>
</body>
</html>

View file

@ -0,0 +1,97 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: Upgrading unresolved elements</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="HTML parser must add an unresolved custom element to the upgrade candidates map">
<link rel="help" href="https://html.spec.whatwg.org/#upgrades">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="log"></div>
<my-custom-element></my-custom-element>
<instantiates-itself-after-super></instantiates-itself-after-super>
<instantiates-itself-before-super></instantiates-itself-before-super>
<my-other-element id="instance"></my-other-element>
<my-other-element id="otherInstance"></my-other-element>
<script>
setup({allow_uncaught_exception:true});
test(function () {
class MyCustomElement extends HTMLElement { }
var instance = document.querySelector('my-custom-element');
assert_true(instance instanceof HTMLElement);
assert_false(instance instanceof HTMLUnknownElement,
'an unresolved custom element should not be an instance of HTMLUnknownElement');
assert_false(instance instanceof MyCustomElement);
customElements.define('my-custom-element', MyCustomElement);
assert_true(instance instanceof HTMLElement);
assert_true(instance instanceof MyCustomElement,
'Calling customElements.define must upgrade existing custom elements');
}, 'Element.prototype.createElement must add an unresolved custom element to the upgrade candidates map');
test(function () {
class InstantiatesItselfAfterSuper extends HTMLElement {
constructor(doNotCreateItself) {
super();
if (!doNotCreateItself)
new InstantiatesItselfAfterSuper(true);
}
}
var uncaughtError;
window.onerror = function (message, url, lineNumber, columnNumber, error) { uncaughtError = error; return true; }
customElements.define('instantiates-itself-after-super', InstantiatesItselfAfterSuper);
assert_equals(uncaughtError.name, 'InvalidStateError');
}, 'HTMLElement constructor must throw an InvalidStateError when the top of the construction stack is marked AlreadyConstructed'
+ ' due to a custom element constructor constructing itself after super() call');
test(function () {
class InstantiatesItselfBeforeSuper extends HTMLElement {
constructor(doNotCreateItself) {
if (!doNotCreateItself)
new InstantiatesItselfBeforeSuper(true);
super();
}
}
var uncaughtError;
window.onerror = function (message, url, lineNumber, columnNumber, error) { uncaughtError = error; return true; }
customElements.define('instantiates-itself-before-super', InstantiatesItselfBeforeSuper);
assert_equals(uncaughtError.name, 'InvalidStateError');
}, 'HTMLElement constructor must throw an InvalidStateError when the top of the construction stack is marked AlreadyConstructed'
+ ' due to a custom element constructor constructing itself before super() call');
test(function () {
class MyOtherElement extends HTMLElement {
constructor() {
super();
if (this == instance)
return otherInstance;
}
}
var instance = document.getElementById('instance');
var otherInstance = document.getElementById('otherInstance');
assert_false(instance instanceof MyOtherElement);
assert_false(otherInstance instanceof MyOtherElement);
var uncaughtError;
window.onerror = function (message, url, lineNumber, columnNumber, error) { uncaughtError = error; return true; }
customElements.define('my-other-element', MyOtherElement);
assert_equals(uncaughtError.name, 'InvalidStateError');
assert_true(document.createElement('my-other-element') instanceof MyOtherElement,
'Upgrading of custom elements must happen after the definition was added to the registry.');
}, 'Upgrading a custom element must throw an InvalidStateError when the returned element is not SameValue as the upgraded element');
</script>
</body>
</html>