diff options
Diffstat (limited to 'testing/web-platform/tests/custom-elements')
97 files changed, 7720 insertions, 0 deletions
diff --git a/testing/web-platform/tests/custom-elements/CustomElementRegistry.html b/testing/web-platform/tests/custom-elements/CustomElementRegistry.html new file mode 100644 index 000000000..ecc7810e8 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/CustomElementRegistry.html @@ -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> diff --git a/testing/web-platform/tests/custom-elements/HTMLElement-constructor.html b/testing/web-platform/tests/custom-elements/HTMLElement-constructor.html new file mode 100644 index 000000000..1ed625f63 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/HTMLElement-constructor.html @@ -0,0 +1,84 @@ +<!DOCTYPE html> +<html> +<head> +<title>Custom Elements: HTMLElement must allow subclassing</title> +<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> +<meta name="assert" content="HTMLElement must allow subclassing"> +<link rel="help" href="https://html.spec.whatwg.org/#html-element-constructors"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> +<div id="log"></div> +<script> + +test(function () { + customElements.define('html-custom-element', HTMLElement); + assert_throws({'name': 'TypeError'}, function () { new HTMLElement(); }); +}, 'HTMLElement constructor must throw a TypeError when NewTarget is equal to itself'); + +test(function () { + customElements.define('html-proxy-custom-element', new Proxy(HTMLElement, {})); + assert_throws({'name': 'TypeError'}, function () { new HTMLElement(); }); +}, 'HTMLElement constructor must throw a TypeError when NewTarget is equal to itself via a Proxy object'); + +test(function () { + class SomeCustomElement extends HTMLElement {}; + assert_throws({'name': 'TypeError'}, function () { new SomeCustomElement; }); +}, 'HTMLElement constructor must throw TypeError when it has not been defined by customElements.define'); + +test(function () { + class CustomElementWithInferredTagName extends HTMLElement {}; + customElements.define('inferred-name', CustomElementWithInferredTagName); + + var instance = new CustomElementWithInferredTagName; + assert_true(instance instanceof Element, 'A custom element must inherit from Element'); + assert_true(instance instanceof Node, 'A custom element must inherit from Node'); + assert_equals(instance.localName, 'inferred-name'); + assert_equals(instance.nodeName, 'INFERRED-NAME'); + assert_equals(instance.namespaceURI, 'http://www.w3.org/1999/xhtml', 'A custom HTML element must use HTML namespace'); + + document.body.appendChild(instance); + assert_equals(document.body.lastChild, instance, + 'document.body.appendChild must be able to insert a custom element'); + assert_equals(document.querySelector('inferred-name'), instance, + 'document.querySelector must be able to find a custom element by its tag name'); + +}, 'HTMLElement constructor must infer the tag name from the element interface'); + +test(function () { + class ConcreteCustomElement extends HTMLElement { }; + class SubCustomElement extends ConcreteCustomElement { }; + customElements.define('concrete-custom-element', ConcreteCustomElement); + customElements.define('sub-custom-element', SubCustomElement); + + var instance = new ConcreteCustomElement(); + assert_true(instance instanceof ConcreteCustomElement); + assert_false(instance instanceof SubCustomElement); + assert_equals(instance.localName, 'concrete-custom-element'); + assert_equals(instance.nodeName, 'CONCRETE-CUSTOM-ELEMENT'); + + var instance = new SubCustomElement(); + assert_true(instance instanceof ConcreteCustomElement); + assert_true(instance instanceof SubCustomElement); + assert_equals(instance.localName, 'sub-custom-element'); + assert_equals(instance.nodeName, 'SUB-CUSTOM-ELEMENT'); + +}, 'HTMLElement constructor must allow subclassing a custom element'); + +test(function () { + class AbstractCustomElement extends HTMLElement { }; + class ConcreteSubCustomElement extends AbstractCustomElement { }; + customElements.define('concrete-sub-custom-element', ConcreteSubCustomElement); + + var instance = new ConcreteSubCustomElement(); + assert_true(instance instanceof AbstractCustomElement); + assert_true(instance instanceof ConcreteSubCustomElement); + assert_equals(instance.localName, 'concrete-sub-custom-element'); + assert_equals(instance.nodeName, 'CONCRETE-SUB-CUSTOM-ELEMENT'); + +}, 'HTMLElement constructor must allow subclassing an user-defined subclass of HTMLElement'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/OWNERS b/testing/web-platform/tests/custom-elements/OWNERS new file mode 100644 index 000000000..a8b25e79c --- /dev/null +++ b/testing/web-platform/tests/custom-elements/OWNERS @@ -0,0 +1,9 @@ +@alsemenov +@deepak-sa +@domenic +@dominiccooney +@hayatoito +@kojiishi +@rniwa +@sgrekhov +@takayoshikochi diff --git a/testing/web-platform/tests/custom-elements/adopted-callback.html b/testing/web-platform/tests/custom-elements/adopted-callback.html new file mode 100644 index 000000000..5c08a04a4 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/adopted-callback.html @@ -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> diff --git a/testing/web-platform/tests/custom-elements/attribute-changed-callback.html b/testing/web-platform/tests/custom-elements/attribute-changed-callback.html new file mode 100644 index 000000000..bd467912b --- /dev/null +++ b/testing/web-platform/tests/custom-elements/attribute-changed-callback.html @@ -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> diff --git a/testing/web-platform/tests/custom-elements/connected-callbacks.html b/testing/web-platform/tests/custom-elements/connected-callbacks.html new file mode 100644 index 000000000..d6e68262a --- /dev/null +++ b/testing/web-platform/tests/custom-elements/connected-callbacks.html @@ -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> diff --git a/testing/web-platform/tests/custom-elements/custom-element-registry/define.html b/testing/web-platform/tests/custom-elements/custom-element-registry/define.html new file mode 100644 index 000000000..fedb68741 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/custom-element-registry/define.html @@ -0,0 +1,243 @@ +<!DOCTYPE html> +<title>Custom Elements: Element definition</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div id="log"></div> +<iframe id="iframe"></iframe> +<script> +'use strict'; +(() => { + // Element definition + // https://html.spec.whatwg.org/multipage/scripting.html#element-definition + + // Use window from iframe to isolate the test. + const testWindow = iframe.contentDocument.defaultView; + const customElements = testWindow.customElements; + + let testable = false; + test(() => { + assert_true('customElements' in testWindow, '"window.customElements" exists'); + assert_true('define' in customElements, '"window.customElements.define" exists'); + testable = true; + }, '"window.customElements.define" should exists'); + if (!testable) + return; + + const expectTypeError = TypeError.prototype; + // Following errors are DOMException, not JavaScript errors. + const expectSyntaxError = 'SYNTAX_ERR'; + const expectNotSupportedError = 'NOT_SUPPORTED_ERR'; + + // 1. If IsConstructor(constructor) is false, + // then throw a TypeError and abort these steps. + test(() => { + assert_throws(expectTypeError, () => { + customElements.define(); + }); + }, 'If no arguments, should throw a TypeError'); + test(() => { + assert_throws(expectTypeError, () => { + customElements.define('test-define-one-arg'); + }); + }, 'If one argument, should throw a TypeError'); + [ + [ 'undefined', undefined ], + [ 'null', null ], + [ 'object', {} ], + [ 'string', 'string' ], + [ 'arrow function', () => {} ], // IsConstructor returns false for arrow functions + [ 'method', ({ m() { } }).m ], // IsConstructor returns false for methods + ].forEach(t => { + test(() => { + assert_throws(expectTypeError, () => { + customElements.define(`test-define-constructor-${t[0]}`, t[1]); + }); + }, `If constructor is ${t[0]}, should throw a TypeError`); + }); + + // 2. If name is not a valid custom element name, + // then throw a SyntaxError and abort these steps. + let validCustomElementNames = [ + // [a-z] (PCENChar)* '-' (PCENChar)* + // https://html.spec.whatwg.org/multipage/scripting.html#valid-custom-element-name + 'a-', + 'a-a', + 'aa-', + 'aa-a', + 'a-.-_', + 'a-0123456789', + 'a-\u6F22\u5B57', // Two CJK Unified Ideographs + 'a-\uD840\uDC0B', // Surrogate pair U+2000B + ]; + let invalidCustomElementNames = [ + undefined, + null, + '', + '-', + 'a', + 'input', + 'mycustomelement', + 'A', + 'A-', + '0-', + 'a-A', + 'a-Z', + 'A-a', + 'a-a\u00D7', + 'a-a\u3000', + 'a-a\uDB80\uDC00', // Surrogate pair U+F0000 + // name must not be any of the hyphen-containing element names. + 'annotation-xml', + 'color-profile', + 'font-face', + 'font-face-src', + 'font-face-uri', + 'font-face-format', + 'font-face-name', + 'missing-glyph', + ]; + validCustomElementNames.forEach(name => { + test(() => { + customElements.define(name, class {}); + }, `Element names: defining an element named ${name} should succeed`); + }); + invalidCustomElementNames.forEach(name => { + test(() => { + assert_throws(expectSyntaxError, () => { + customElements.define(name, class {}); + }); + }, `Element names: defining an element named ${name} should throw a SyntaxError`); + }); + + // 3. If this CustomElementRegistry contains an entry with name name, + // then throw a NotSupportedError and abort these steps. + test(() => { + customElements.define('test-define-dup-name', class {}); + assert_throws(expectNotSupportedError, () => { + customElements.define('test-define-dup-name', class {}); + }); + }, 'If the name is already defined, should throw a NotSupportedError'); + + // 5. If this CustomElementRegistry contains an entry with constructor constructor, + // then throw a NotSupportedError and abort these steps. + test(() => { + class TestDupConstructor {}; + customElements.define('test-define-dup-constructor', TestDupConstructor); + assert_throws(expectNotSupportedError, () => { + customElements.define('test-define-dup-ctor2', TestDupConstructor); + }); + }, 'If the constructor is already defined, should throw a NotSupportedError'); + + // 9.1. If extends is a valid custom element name, + // then throw a NotSupportedError. + validCustomElementNames.forEach(name => { + test(() => { + assert_throws(expectNotSupportedError, () => { + customElements.define('test-define-extend-valid-name', class {}, { extends: name }); + }); + }, `If extends is ${name}, should throw a NotSupportedError`); + }); + + // 9.2. If the element interface for extends and the HTML namespace is HTMLUnknownElement + // (e.g., if extends does not indicate an element definition in this specification), + // then throw a NotSupportedError. + [ + // https://html.spec.whatwg.org/multipage/dom.html#elements-in-the-dom:htmlunknownelement + 'bgsound', + 'blink', + 'isindex', + 'multicol', + 'nextid', + 'spacer', + 'elementnametobeunknownelement', + ].forEach(name => { + test(() => { + assert_throws(expectNotSupportedError, () => { + customElements.define('test-define-extend-' + name, class {}, { extends: name }); + }); + }, `If extends is ${name}, should throw a NotSupportedError`); + }); + + // 12.1. Let prototype be Get(constructor, "prototype"). Rethrow any exceptions. + function assert_rethrown(func, description) { + assert_throws({ name: 'rethrown' }, func, description); + } + function throw_rethrown_error() { + const e = new Error('check this is rethrown'); + e.name = 'rethrown'; + throw e; + } + test(() => { + // Hack for prototype to throw while IsConstructor is true. + const BadConstructor = (function () { }).bind({}); + Object.defineProperty(BadConstructor, 'prototype', { + get() { throw_rethrown_error(); } + }); + assert_rethrown(() => { + customElements.define('test-define-constructor-prototype-rethrow', BadConstructor); + }); + }, 'If constructor.prototype throws, should rethrow'); + + // 12.2. If Type(prototype) is not Object, + // then throw a TypeError exception. + test(() => { + const c = (function () { }).bind({}); // prototype is undefined. + assert_throws(expectTypeError, () => { + customElements.define('test-define-constructor-prototype-undefined', c); + }); + }, 'If Type(constructor.prototype) is undefined, should throw a TypeError'); + test(() => { + function c() {}; + c.prototype = 'string'; + assert_throws(expectTypeError, () => { + customElements.define('test-define-constructor-prototype-string', c); + }); + }, 'If Type(constructor.prototype) is string, should throw a TypeError'); + + // 12.3. Let lifecycleCallbacks be a map with the four keys "connectedCallback", + // "disconnectedCallback", "adoptedCallback", and "attributeChangedCallback", + // each of which belongs to an entry whose value is null. + // 12.4. For each of the four keys callbackName in lifecycleCallbacks: + // 12.4.1. Let callbackValue be Get(prototype, callbackName). Rethrow any exceptions. + // 12.4.2. If callbackValue is not undefined, then set the value of the entry in + // lifecycleCallbacks with key callbackName to the result of converting callbackValue + // to the Web IDL Function callback type. Rethrow any exceptions from the conversion. + [ + 'connectedCallback', + 'disconnectedCallback', + 'adoptedCallback', + 'attributeChangedCallback', + ].forEach(name => { + test(() => { + class C { + get [name]() { throw_rethrown_error(); } + } + assert_rethrown(() => { + customElements.define(`test-define-${name.toLowerCase()}-rethrow`, C); + }); + }, `If constructor.prototype.${name} throws, should rethrow`); + + [ + { name: 'undefined', value: undefined, success: true }, + { name: 'function', value: function () { }, success: true }, + { name: 'null', value: null, success: false }, + { name: 'object', value: {}, success: false }, + { name: 'integer', value: 1, success: false }, + ].forEach(data => { + test(() => { + class C { }; + C.prototype[name] = data.value; + if (data.success) { + customElements.define(`test-define-${name.toLowerCase()}-${data.name}`, C); + } else { + assert_throws(expectTypeError, () => { + customElements.define(`test-define-${name.toLowerCase()}-${data.name}`, C); + }); + } + }, `If constructor.prototype.${name} is ${data.name}, should ${data.success ? 'succeed' : 'throw a TypeError'}`); + }); + }); +})(); +</script> +</body> diff --git a/testing/web-platform/tests/custom-elements/disconnected-callbacks.html b/testing/web-platform/tests/custom-elements/disconnected-callbacks.html new file mode 100644 index 000000000..7f5a4d0f8 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/disconnected-callbacks.html @@ -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> diff --git a/testing/web-platform/tests/custom-elements/htmlconstructor/newtarget.html b/testing/web-platform/tests/custom-elements/htmlconstructor/newtarget.html new file mode 100644 index 000000000..04b4c4986 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/htmlconstructor/newtarget.html @@ -0,0 +1,124 @@ +<!DOCTYPE html> +<title>Custom Elements: [HTMLConstructor] derives prototype from NewTarget</title> +<meta name="author" title="Domenic Denicola" href="mailto:d@domenic.me"> +<meta name="help" content="https://html.spec.whatwg.org/multipage/dom.html#htmlconstructor"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/custom-elements-helpers.js"></script> + +<script> +"use strict"; + +test_with_window(w => { + let afterDefinition = false; + 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); + + 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; + } + }); + + w.customElements.define("test-element", TestElement); + + beforeDefinition = true; + new TestElement(); + +}, "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 () { + assert_throws({ name: "prototype throws" }, () => { + return Reflect.construct(w.HTMLElement, [], new.target); + }); + }).bind({}); + + Object.defineProperty(TestElement, "prototype", { + get() { + if (throws) { + throw { name: "prototype throws" }; + } + return {}; + } + }); + + w.customElements.define("test-element", TestElement); + + throws = true; + new TestElement(); + +}, "Rethrow any exceptions thrown while getting the prototype"); + +test_with_window(w => { + for (const notAnObject of [null, undefined, 5, "string"]) { + // We have to return an object during define(), but not during super() + let returnNotAnObject = false; + + const TestElement = (function () { + const o = Reflect.construct(w.HTMLElement, [], new.target); + + assert_equals(Object.getPrototypeOf(o), window.HTMLElement, + "Must use the HTMLElement from the realm of NewTarget"); + assert_not_equals(Object.getPrototypeOf(o), w.HTMLElement, + "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 : {}; + } + }); + + w.customElements.define("test-element", TestElement); + + returnNotAnObject = true; + new TestElement(); + } +}, "If prototype is not object, derives the fallback from NewTarget's realm (autonomous custom elements)"); + +test_with_window(w => { + for (const notAnObject of [null, undefined, 5, "string"]) { + // We have to return an object during define(), but not during super() + let returnNotAnObject = false; + + const TestElement = (function () { + const o = Reflect.construct(w.HTMLParagraphElement, [], new.target); + + assert_equals(Object.getPrototypeOf(o), window.HTMLParagraphElement, + "Must use the HTMLParagraphElement from the realm of NewTarget"); + assert_not_equals(Object.getPrototypeOf(o), w.HTMLParagraphElement, + "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 : {}; + } + }); + + w.customElements.define("test-element", TestElement, { extends: "p" }); + + returnNotAnObject = true; + new TestElement(); + } +}, "If prototype is not object, derives the fallback from NewTarget's realm (customized built-in elements)"); +</script> diff --git a/testing/web-platform/tests/custom-elements/reaction-timing.html b/testing/web-platform/tests/custom-elements/reaction-timing.html new file mode 100644 index 000000000..9e5bafbed --- /dev/null +++ b/testing/web-platform/tests/custom-elements/reaction-timing.html @@ -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> diff --git a/testing/web-platform/tests/custom-elements/reactions/Attr.html b/testing/web-platform/tests/custom-elements/reactions/Attr.html new file mode 100644 index 000000000..c9fa37f96 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/reactions/Attr.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> +<head> +<title>Custom Elements: CEReactions on Attr interface</title> +<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> +<meta name="assert" content="value of Attr interface must have CEReactions"> +<meta name="help" content="https://dom.spec.whatwg.org/#node"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/custom-elements-helpers.js"></script> +<script src="./resources/reactions.js"></script> +</head> +<body> +<div id="log"></div> +<script> + +testAttributeMutator(function (element, name, value) { + element.attributes[name].value = value; +}, 'value on Attr'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/reactions/ChildNode.html b/testing/web-platform/tests/custom-elements/reactions/ChildNode.html new file mode 100644 index 000000000..756f17229 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/reactions/ChildNode.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html> +<head> +<title>Custom Elements: CEReactions on ChildNode interface</title> +<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> +<meta name="assert" content="before, after, 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> diff --git a/testing/web-platform/tests/custom-elements/reactions/DOMTokenList.html b/testing/web-platform/tests/custom-elements/reactions/DOMTokenList.html new file mode 100644 index 000000000..38006d3e7 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/reactions/DOMTokenList.html @@ -0,0 +1,217 @@ +<!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'); + assert_array_equals(element.takeLog().types(), []); +}, 'remove on DOMTokenList must not enqueue an attributeChanged reaction 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> diff --git a/testing/web-platform/tests/custom-elements/reactions/Document.html b/testing/web-platform/tests/custom-elements/reactions/Document.html new file mode 100644 index 000000000..304ec9861 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/reactions/Document.html @@ -0,0 +1,47 @@ +<!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="importNode and adoptNode of Document interface must have CEReactions"> +<meta name="help" content="https://dom.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(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(); + newDoc.importNode(instance); + + var logEntries = element.takeLog(); + assert_array_equals(logEntries.types(), ['constructed']); + assert_equals(logEntries.last().oldDocument, document); + assert_equals(logEntries.last().newDocument, newDoc); +}, 'importNode on Document must construct a new custom element when importing a custom element'); + +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(); + newDoc.adoptNode(instance); + + var logEntries = element.takeLog(); + assert_array_equals(logEntries.types(), ['adopted']); + assert_equals(logEntries.last().oldDocument, document); + assert_equals(logEntries.last().newDocument, newDoc); +}, 'adoptNode on Document must enqueue an adopted reaction when importing a custom element'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/reactions/Element.html b/testing/web-platform/tests/custom-elements/reactions/Element.html new file mode 100644 index 000000000..ed627f44e --- /dev/null +++ b/testing/web-platform/tests/custom-elements/reactions/Element.html @@ -0,0 +1,61 @@ +<!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="id, className, slot, setAttribute, setAttributeNS, removeAttribute, removeAttributeNS, setAttributeNode, setAttributeNodeNS, removeAttributeNode, and insertAdjacentElement of Element interface must have CEReactions"> +<meta name="help" content="https://dom.spec.whatwg.org/#element"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/custom-elements-helpers.js"></script> +<script src="./resources/reactions.js"></script> +</head> +<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); +}); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/reactions/NamedNodeMap.html b/testing/web-platform/tests/custom-elements/reactions/NamedNodeMap.html new file mode 100644 index 000000000..0ae83e9ab --- /dev/null +++ b/testing/web-platform/tests/custom-elements/reactions/NamedNodeMap.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<html> +<head> +<title>Custom Elements: CEReactions on NamedNodeMap interface</title> +<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> +<meta name="assert" content="setNamedItem, setNamedItemNS, removeNameditem, and removeNamedItemNS of NamedNodeMap interface must have CEReactions"> +<meta name="help" content="https://dom.spec.whatwg.org/#node"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/custom-elements-helpers.js"></script> +<script src="./resources/reactions.js"></script> +</head> +<body> +<div id="log"></div> +<script> + +testAttributeAdder(function (element, name, value) { + var attr = element.ownerDocument.createAttribute(name); + attr.value = value; + element.attributes.setNamedItem(attr); +}, 'setNamedItem on NamedNodeMap'); + +testAttributeAdder(function (element, name, value) { + var attr = element.ownerDocument.createAttribute(name); + attr.value = value; + element.attributes.setNamedItemNS(attr); +}, 'setNamedItemNS on NamedNodeMap'); + +testAttributeRemover(function (element, name) { + element.attributes.removeNamedItem(name); +}, 'removeNamedItem on NamedNodeMap'); + +testAttributeRemover(function (element, name) { + element.attributes.removeNamedItemNS(null, name); +}, 'removeNamedItemNS on NamedNodeMap'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/reactions/Node.html b/testing/web-platform/tests/custom-elements/reactions/Node.html new file mode 100644 index 000000000..94da3d020 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/reactions/Node.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html> +<head> +<title>Custom Elements: CEReactions on Node interface</title> +<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> +<meta name="assert" content="nodeValue, textContent, normalize, cloneNode, insertBefore, appendChild, replaceChild, and removeChild of Node interface must have CEReactions"> +<meta name="help" content="https://dom.spec.whatwg.org/#node"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/custom-elements-helpers.js"></script> +<script src="./resources/reactions.js"></script> +</head> +<body> +<div id="log"></div> +<script> + +testAttributeMutator(function (element, name, value) { + element.getAttributeNode(name).nodeValue = value; +}, 'nodeValue on Node'); + +testAttributeMutator(function (element, name, value) { + element.getAttributeNode(name).textContent = value; +}, 'textContent on Node'); + +// FIXME: Add a test for normalize() + +testCloner(function (customElement) { + return customElement.cloneNode(false); +}, 'cloneNode on Node'); + +testNodeConnector(function (newContainer, customElement) { + newContainer.insertBefore(customElement, newContainer.firstChild); +}, 'insertBefore on ChildNode'); + +testNodeConnector(function (newContainer, customElement) { + newContainer.appendChild(customElement); +}, 'appendChild on ChildNode'); + +testNodeConnector(function (newContainer, customElement) { + newContainer.replaceChild(customElement, newContainer.firstChild); +}, 'replaceChild on ChildNode'); + +testNodeDisconnector(function (customElement) { + customElement.parentNode.removeChild(customElement); +}, 'removeChild on ChildNode'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/reactions/ParentNode.html b/testing/web-platform/tests/custom-elements/reactions/ParentNode.html new file mode 100644 index 000000000..b143b5a98 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/reactions/ParentNode.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html> +<head> +<title>Custom Elements: CEReactions on ParentNode interface</title> +<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> +<meta name="assert" content="prepend and append of ParentNode interface must have CEReactions"> +<meta name="help" content="https://dom.spec.whatwg.org/#parentnode"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/custom-elements-helpers.js"></script> +<script src="./resources/reactions.js"></script> +</head> +<body> +<div id="log"></div> +<script> + +testNodeConnector(function (newContainer, customElement) { + newContainer.prepend(customElement); +}, 'prepend on ParentNode'); + +testNodeConnector(function (newContainer, customElement) { + newContainer.append(customElement); +}, 'append on ParentNode'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/reactions/Range.html b/testing/web-platform/tests/custom-elements/reactions/Range.html new file mode 100644 index 000000000..82382d520 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/reactions/Range.html @@ -0,0 +1,49 @@ +<!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); +}, 'insertNode on Range'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/reactions/resources/reactions.js b/testing/web-platform/tests/custom-elements/reactions/resources/reactions.js new file mode 100644 index 000000000..c260dc4f3 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/reactions/resources/reactions.js @@ -0,0 +1,217 @@ + +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); + assert_array_equals(element.takeLog().types(), ['disconnected']); + }, name + ' must enqueue a disconnected reaction'); + + container.parentNode.removeChild(container); +} + +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 testReflectAttribute(jsAttributeName, contentAttributeName, validValue1, validValue2, 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: validValue1, 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: validValue1, newValue: validValue2, namespace: null}); + }, name + ' must enqueue an attributeChanged reaction when replacing an existing attribute'); +} + +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) { + 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'); +} diff --git a/testing/web-platform/tests/custom-elements/resources/custom-elements-helpers.js b/testing/web-platform/tests/custom-elements/resources/custom-elements-helpers.js new file mode 100644 index 000000000..63fb98b43 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/resources/custom-elements-helpers.js @@ -0,0 +1,162 @@ +function create_window_in_test(t, srcdoc) { + let p = new Promise((resolve) => { + let f = document.createElement('iframe'); + f.srcdoc = srcdoc ? srcdoc : ''; + f.onload = (event) => { + let w = f.contentWindow; + t.add_cleanup(() => f.parentNode && f.remove()); + resolve(w); + }; + document.body.appendChild(f); + }); + return p; +} + +function test_with_window(f, name, srcdoc) { + promise_test((t) => { + return create_window_in_test(t, srcdoc) + .then((w) => { + f(w); + }); + }, name); +} + +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, + 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, + } + ]; +} diff --git a/testing/web-platform/tests/custom-elements/resources/empty-html-document.html b/testing/web-platform/tests/custom-elements/resources/empty-html-document.html new file mode 100644 index 000000000..eaca3f49f --- /dev/null +++ b/testing/web-platform/tests/custom-elements/resources/empty-html-document.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<html> +<body> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/concepts/custom-elements-type-allowed-chars-first-char.html b/testing/web-platform/tests/custom-elements/v0/concepts/custom-elements-type-allowed-chars-first-char.html new file mode 100644 index 000000000..b83b5fa78 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/concepts/custom-elements-type-allowed-chars-first-char.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<html> +<head> +<title>First char allowed for custom element type </title> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="The custom element type identifies a custom element interface and is a sequence of characters that must match the NCName production"> +<meta name="timeout" content="long"> +<link rel="help" href="https://dvcs.w3.org/hg/webcomponents/raw-file/default/spec/custom/index.html#concepts"> +<link rel="help" href="http://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-NCName"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +function composeName(charCode) { + return String.fromCharCode(charCode) + '-x' + charCode.toString(16); +} + +// NCName definition from http://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-NCName +// NCName ::= (Letter | '_') (NCNameChar)* +// NCNameChar ::= Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender +// Letter ::= BaseChar | Ideographic + +test(function() { + testCharCode(getCharCode('_'), composeName, checkValidName); +}, 'Registering valid custom element name starting with \'_\' char'); + +test(function() { + baseCharsSingle.testEach(composeName, checkValidName); + baseCharsRanges.testEach(composeName, checkValidName); +}, 'Registering valid custom element name starting with base char'); + +test(function() { + ideographicCharsSingle.testEach(composeName, checkValidName); + ideographicCharsRanges.testEach(composeName, checkValidName); +}, 'Registering valid custom element name starting with ideographic char'); + +test(function() { + (new CharsArray(['.', '-', ':', '+', '='])).testEach(composeName, checkInvalidName); +}, 'Registering custom element starting with invalid character (general) should fail'); + +test(function() { + combiningChars.testEach(composeName, checkInvalidName); +}, 'Registering custom element starting with invalid character (combining char) should fail'); + +test(function() { + extenderChars.testEach(composeName, checkInvalidName); +}, 'Registering custom element starting with invalid character (extender char) should fail'); + +test(function() { + digitCharsRanges.testEach(composeName, checkInvalidName); +}, 'Registering custom element starting with invalid character (digit char) should fail'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/concepts/custom-elements-type-allowed-chars.html b/testing/web-platform/tests/custom-elements/v0/concepts/custom-elements-type-allowed-chars.html new file mode 100644 index 000000000..64252ab49 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/concepts/custom-elements-type-allowed-chars.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<html> +<head> +<title>Chars allowed for custom element type </title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="The custom element type identifies a custom element interface and is a sequence of characters that must match the NCName production"> +<meta name="timeout" content="long"> +<link rel="help" href="https://dvcs.w3.org/hg/webcomponents/raw-file/default/spec/custom/index.html#concepts"> +<link rel="help" href="http://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-NCName"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +function composeName(charCode) { + return 'x-' + String.fromCharCode(charCode) + '-x' + charCode.toString(16); +} + +// NCName definition from http://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-NCName +// NCName ::= (Letter | '_') (NCNameChar)* +// NCNameChar ::= Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender +// Letter ::= BaseChar | Ideographic + +test(function() { + baseCharsSingle.testEach(composeName, checkValidName); + baseCharsRanges.testEach(composeName, checkValidName); +}, 'Registering valid custom element types with base char'); + +test(function() { + ideographicCharsSingle.testEach(composeName, checkValidName); + ideographicCharsRanges.testEach(composeName, checkValidName); +}, 'Registering valid custom element types with ideographic char'); + +test(function() { + digitCharsRanges.testEach(composeName, checkValidName); +}, 'Registering valid custom element types with digit chars'); + +test(function() { + (new CharsArray(['.', '-', '_'])).testEach(composeName, checkValidName) +}, 'Registering valid custom element types with characters \'.\', \'-\', \'_\''); + +test(function() { + combiningChars.testEach(composeName, checkValidName); +}, 'Registering valid custom element types with combining char'); + +test(function() { + extenderChars.testEach(composeName, checkValidName); +}, 'Registering valid custom element types with extender char'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/concepts/custom-elements-type-naming.html b/testing/web-platform/tests/custom-elements/v0/concepts/custom-elements-type-naming.html new file mode 100644 index 000000000..c05f1ccde --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/concepts/custom-elements-type-naming.html @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<html> +<head> +<title>The custom element type is a sequence of characters that must match the NCName production and contain a minus character</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="The custom element type is a sequence of characters that must match the NCName production and contain a U+002D HYPHEN-MINUS character"> +<link rel="help" href="https://dvcs.w3.org/hg/webcomponents/raw-file/default/spec/custom/index.html#concepts"> +<link rel="help" href="http://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-NCName"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var validNames = ['x-frame', 'xx-frame', 'x--frame', '_-frame', 'x-', 'x--', + 'x-1', 'x-_', '_-_', '__-', '_-1', '_-..']; + HTML5_ELEMENTS.forEach(function(value) { + validNames.push('x-' + value); + validNames.push('Y-' + value.toUpperCase()); + }); + validNames.forEach(function(value) { + try { + doc.registerElement(value); + } catch (e) { + assert_unreached('Exception should not be thrown in case of attempt ' + + 'to register a custom element with a name \'' + value + '\''); + } + }); +}, 'Registering valid custom element types'); + +test(function() { + var doc = newHTMLDocument(); + var invalidNames = ['xframe', 'x_frame', 'x.frame', 'x1frame', '-xframe', '1-frame', + '1x-frame', '.-frame', '_frame', 'x-f!rame', 'x-:frame']; + invalidNames.forEach(function(value) { + assert_throws('SyntaxError', function() { + doc.registerElement(value); + }, 'Exception should be thrown in case of attempt to register element ' + + 'with the name \'' + value + '\''); + }); +}, 'Registering invalid custom element types should fail'); + +test(function() { + var doc = newHTMLDocument(); + var forbiddenNames = ['annotation-xml', + 'color-profile', + 'font-face', + 'font-face-src', + 'font-face-uri', + 'font-face-format', + 'font-face-name', + 'missing-glyph']; + forbiddenNames.forEach(function(value) { + assert_throws('SyntaxError', function() { + doc.registerElement(value); + }, 'Exception should be thrown in case of attempt to register element ' + + 'with the name \'' + value + '\''); + }); +}, 'Registering forbidden custom element types should fail'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/creating-and-passing-registries/new-registry-test.html b/testing/web-platform/tests/custom-elements/v0/creating-and-passing-registries/new-registry-test.html new file mode 100644 index 000000000..597f15c21 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/creating-and-passing-registries/new-registry-test.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<html> +<head> +<title>When an HTML Document is loaded in a browsing context, a new registry must be created and associated with this document</title> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="When an HTML Document is loaded in a browsing context, a new registry must be created and associated with this document."> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#creating-and-passing-registries"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +testInIFrame('../resources/blank.html', function(doc) { + try { + doc.registerElement('x-element'); + } catch (e) { + assert_unreached('Unexpected exception, while registering a valid custom element'); + } +}, 'Document, loaded into browsing context, must have a new empty registry'); + + +testInIFrame('../resources/blank.html', function(loadedDocument) { + var createdDocument = document.implementation.createHTMLDocument('Test Document'); + // Let's check that loadedDocument and createdDocument use different registeries. + createdDocument.registerElement('x-element'); + try { + loadedDocument.registerElement('x-element'); + } catch (e) { + assert_unreached('Unexpected exception while registering a custom element ' + + 'in a document, which has it\'s own registry'); + } +}, 'Document, loaded into browsing context, must have a new empty registry, ' + + 'which is different from other documents\' registries'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/creating-and-passing-registries/no-registry-test.html b/testing/web-platform/tests/custom-elements/v0/creating-and-passing-registries/no-registry-test.html new file mode 100644 index 000000000..d936fd34f --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/creating-and-passing-registries/no-registry-test.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html> +<head> +<title>New document without browsing context must not have a registry</title> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="In all other cases, new documents must not have a registry."> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#creating-and-passing-registries"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = document.implementation.createDocument(null, 'test', null); + assert_throws( + 'NotSupportedError', + function() { doc.registerElement('x-element'); }, + 'Registering valid custom element in a document ' + + 'without registry should fail'); +}, 'Document of type other than HTML, not loaded into browsing context, must not have a registry'); + +async_test(function(t) { + var request = new XMLHttpRequest(); + request.onreadystatechange = t.step_func(function() { + if (request.readyState == 4){ + assert_equals(200, request.status, 'Test document is not loaded correctly'); + var doc = request.response; + assert_true(doc instanceof HTMLDocument, + 'XMLHttpRequest\'s asynchronous response should be HTML document'); + assert_throws( + 'NotSupportedError', + function() { doc.registerElement('x-element'); }, + 'Registering valid custom element in ' + + 'an XMLHttpRequest\'s response document should fail'); + t.done(); + } + }); + + request.open('GET', '../resources/blank.html', true); + request.responseType = 'document'; + request.send(); +}, 'XMLHttpRequest\'s asynchronous response HTML document must not have a registry'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/creating-and-passing-registries/share-registry-create-document.html b/testing/web-platform/tests/custom-elements/v0/creating-and-passing-registries/share-registry-create-document.html new file mode 100644 index 000000000..64244f169 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/creating-and-passing-registries/share-registry-create-document.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> +<head> +<title>Document, created with createHTMLDocument or createDocument with HTML namespace, should share registry with the associated document</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="When DOMImplementation's createDocument method is invoked with namespace set to HTML Namespace or when the createHTMLDocument method is invoked, use the registry of the associated document to the new instance."> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#creating-and-passing-registries"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var name = 'x-frame'; + + var GeneratedConstructor = doc.registerElement(name); + var doc2 = doc.implementation.createHTMLDocument('Document 2'); + assert_throws( + 'NotSupportedError', + function() { doc2.registerElement(name); }, + 'Registering a custom element type name that is already registered in a shared ' + + 'registry should throw an exception'); + + var xframe = doc2.createElement(name); + assert_true(xframe instanceof GeneratedConstructor, + 'Created element should be x-frame instance'); +}, 'Document created by createHTMLDocument should share an existing registry'); + + +test(function() { + var doc = newHTMLDocument(); + var name = 'x-frame-1'; + + var GeneratedConstructor = doc.registerElement(name); + var doc2 = doc.implementation.createDocument(HTML_NAMESPACE, 'html', null); + assert_throws( + 'NotSupportedError', + function() { doc2.registerElement(name); }, + 'Exception should be thrown for custom element, ' + + 'which is already registered in shared registry'); + + var xframe = doc2.createElement(name); + assert_true(xframe instanceof GeneratedConstructor, + 'Created element should be x-frame instance'); +}, 'Document created by createDocument with HTML namespace should share an existing registry'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/creating-and-passing-registries/share-registry-import-document.html b/testing/web-platform/tests/custom-elements/v0/creating-and-passing-registries/share-registry-import-document.html new file mode 100644 index 000000000..251e4f123 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/creating-and-passing-registries/share-registry-import-document.html @@ -0,0 +1,95 @@ +<!DOCTYPE html> +<html> +<head> +<title>When creating an import, use the registry of the master document</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="When creating an import, use the registry of the master document."> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#creating-and-passing-registries"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +var test1 = async_test('Registry of the imported document should be shared with master document. ' + + 'Import is asynchronous'); +test1.step(function() { + var iframe = newIFrame('../resources/import-master-async.html'); + document.body.appendChild(iframe); + iframe.onload = test1.step_func(function() { + var doc = iframe.contentDocument; + var link = doc.querySelector('link[rel=import]'); + link.onload = test1.step_func(function() { + try { + var doc2 = link.import; + var name = 'x-frame'; + doc.registerElement(name); + assert_throws( + 'NotSupportedError', + function() { doc2.registerElement(name); }, + 'Registering a custom element type name that is already registered in a shared ' + + 'registry should throw an exception'); + test1.done(); + } finally { + iframe.remove(); + } + }); + }); +}); + + +var test2 = async_test('Registry of the master document should be shared with imported document. ' + + 'Import is asynchronous'); +test2.step(function() { + var iframe = newIFrame('../resources/import-master-async.html'); + document.body.appendChild(iframe); + iframe.onload = test2.step_func(function() { + var doc = iframe.contentDocument; + var link = doc.querySelector('link[rel=import]'); + link.onload = test2.step_func(function() { + try { + var doc2 = link.import; + var name = 'x-frame'; + doc2.registerElement(name); + assert_throws( + 'NotSupportedError', + function() { doc.registerElement(name); }, + 'Registering a custom element type name that is already registered in a shared ' + + 'registry should throw an exception'); + test2.done(); + } finally { + iframe.remove(); + } + }); + }); +}); + + +testInIFrame('../resources/import-master.html', function(doc) { + var link = doc.querySelector('link[rel=import]'); + var doc2 = link.import; + var name = 'x-frame'; + doc.registerElement(name); + assert_throws( + 'NotSupportedError', + function() { doc2.registerElement(name); }, + 'Registering a custom element type name that is already registered in a shared ' + + 'registry should throw an exception'); +}, 'Registry of the imported document should be shared with master document. Import is syncronous'); + + +testInIFrame('../resources/import-master.html', function(doc) { + var link = doc.querySelector('link[rel=import]'); + var doc2 = link.import; + var name = 'x-frame'; + doc2.registerElement(name); + assert_throws( + 'NotSupportedError', + function() { doc.registerElement(name); }, + 'Registering a custom element type name that is already registered in a shared ' + + 'registry should throw an exception'); +}, 'Registry of the master document should be shared with imported document. Import is syncronous'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/enqueuing-and-invoking-callbacks/invoke-callbacks.html b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/enqueuing-and-invoking-callbacks/invoke-callbacks.html new file mode 100644 index 000000000..97fb73560 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/enqueuing-and-invoking-callbacks/invoke-callbacks.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<html> +<head> +<title>Invoke CALLBACK with ELEMENT as callback this value</title> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="Invoke CALLBACK with ELEMENT as callback this value"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#enqueuing-and-invoking-callbacks"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-a', {prototype: proto}); + var customElement = new GeneratedConstructor(); + + doc.body.innerHTML = '<x-a id="x-a"></x-a>'; + assert_equals(doc.querySelector('#x-a'), proto.createdCallbackThis, + '\'this\' value of the created callback should be the custom element'); +}, 'Test \'this\' value inside created callback.'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + + assert_equals(doc.querySelector('#x-element'), proto.attachedCallbackThis, + '\'this\' value of the attached callback should be the custom element'); +}, 'Test \'this\' value inside attached callback.'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + + var customElement = doc.querySelector('#x-element'); + doc.body.removeChild(customElement); + assert_equals(customElement, proto.detachedCallbackThis, + '\'this\' value of the detached callback should be the custom element'); +}, 'Test \'this\' value inside detached callback.'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-b', {prototype: proto}); + var customElement = new GeneratedConstructor(); + + customElement.setAttribute('class', 'someClass'); + assert_equals(customElement, proto.attributeChangedCallbackThis, + '\'this\' value of the attributeChanged callback should be the custom element'); +}, 'Test \'this\' value inside attributeChanged callback.'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/attached-callback-move-element-test.html b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/attached-callback-move-element-test.html new file mode 100644 index 000000000..df4df5535 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/attached-callback-move-element-test.html @@ -0,0 +1,159 @@ +<!DOCTYPE html> +<html> +<head> +<title>Attached callback of a custom element should be called if element is moved</title> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="attached callback ... must be enqueued whenever custom element is inserted into a document and this document has a browsing context."> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#types-of-callbacks"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-a', {prototype: proto}); + var customElement = doc.createElement('x-a'); + + doc.body.appendChild(customElement); + assert_equals(proto.attachedCallbackCalledCounter, 0, 'Callback attached ' + + 'should not be called in documents that do not have a browsing context'); + + var divElement = doc.createElement('div'); + doc.body.appendChild(divElement); + divElement.appendChild(customElement); + assert_equals(proto.attachedCallbackCalledCounter, 0, 'Callback attached ' + + 'should not be called in documents that do not have a browsing context'); +}, 'Test attached callback if moving custom element inside document ' + + 'without browsing context'); + + +testInIFrame('../../resources/blank.html', function(docWithBrowsingContext) { + var docNoBrowsingContext = newHTMLDocument(); + var proto1 = newHTMLElementPrototype(); + docNoBrowsingContext.registerElement('x-b', {prototype: proto1}); + + var customElement = docNoBrowsingContext.createElement('x-b'); + docNoBrowsingContext.body.appendChild(customElement); + assert_equals(proto1.attachedCallbackCalledCounter, 0, + 'Callback attached should not be called ' + + 'in documents that do not have a browsing context'); + + var proto2 = newHTMLElementPrototype(); + docWithBrowsingContext.registerElement('x-b', {prototype: proto2}); + docWithBrowsingContext.body.appendChild(customElement); + assert_equals(proto1.attachedCallbackCalledCounter, 1, + 'Callback attached should be called in documents with browsing context'); + assert_equals(proto2.attachedCallbackCalledCounter, 0, + 'Callback attached, defined in receiving document, should not be called'); +}, 'Test attached callback if moving custom element from ' + + 'document without browsing context to document with browsing context'); + + +testInIFrame('../../resources/blank.html', function(docWithBrowsingContext) { + var proto1 = newHTMLElementPrototype(); + docWithBrowsingContext.registerElement('x-c', {prototype: proto1}); + + var customElement = docWithBrowsingContext.createElement('x-c'); + docWithBrowsingContext.body.appendChild(customElement); + assert_equals(proto1.attachedCallbackCalledCounter, 1, + 'Callback attached should be called in documents with browsing context'); + + var docNoBrowsingContext = newHTMLDocument(); + var proto2 = newHTMLElementPrototype(); + docNoBrowsingContext.registerElement('x-c', {prototype: proto2}); + docNoBrowsingContext.body.appendChild(customElement); + assert_equals(proto1.attachedCallbackCalledCounter, 1, 'Callback attached should not be called ' + + 'in documents that do not have a browsing context'); + assert_equals(proto2.attachedCallbackCalledCounter, 0, + 'Callback attached, defined in receiving document, should not be called'); +}, 'Test attached callback if moving custom element from ' + + 'document with browsing context to document without browsing context'); + + +testInIFrame('../../resources/blank.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-d', {prototype: proto}); + + var customElement = doc.createElement('x-d'); + doc.body.appendChild(customElement); + assert_equals(proto.attachedCallbackCalledCounter, 1, + 'Callback attached should be called in documents with browsing context'); + + var divElement = doc.createElement('div'); + doc.body.appendChild(divElement); + divElement.appendChild(customElement); + assert_equals(proto.attachedCallbackCalledCounter, 2, + 'Callback attached should be called in documents with browsing context'); +}, 'Test attached callback if moving custom element inside document ' + + 'with browsing context'); + + +testInIFrame('../../resources/blank.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-e', {prototype: proto}); + + var customElement = doc.createElement('x-e'); + doc.body.appendChild(customElement); + assert_equals(proto.attachedCallbackCalledCounter, 1, + 'Callback attached should be called in documents with browsing context'); + + var divElement = doc.createElement('div'); + divElement.appendChild(customElement); + assert_equals(proto.attachedCallbackCalledCounter, 1, + 'Callback attached should not be called if element is not appended to the document'); + + doc.body.appendChild(divElement); + assert_equals(proto.attachedCallbackCalledCounter, 2, + 'Callback attached should be called in documents with browsing context'); +}, 'Test attached callback if indirectly moving custom element inside document ' + + 'with browsing context'); + + +var moveTest = async_test('Test attached callback if moving custom element from ' + + 'document with browsing context to document with browsing context'); + +moveTest.step(function() { + var iframe1 = newIFrame('../../resources/blank.html'); + iframe1.onload = moveTest.step_func(function() { + var doc1 = iframe1.contentDocument; + + // register custom element type + var proto1 = newHTMLElementPrototype(); + doc1.registerElement('x-f', {prototype: proto1}); + + // create custom element + var customElement = doc1.createElement('x-f'); + doc1.body.appendChild(customElement); + assert_equals(proto1.attachedCallbackCalledCounter, 1, + 'Callback attached should be called in documents with browsing context'); + + // create second iframe + var iframe2 = newIFrame('../../resources/x-element.html'); + iframe2.onload = moveTest.step_func(function() { + var doc2 = iframe2.contentDocument; + + // register custom element type + var proto2 = newHTMLElementPrototype(); + doc2.registerElement('x-f', {prototype: proto2}); + + // move element + doc2.body.appendChild(customElement); + assert_equals(proto1.attachedCallbackCalledCounter, 2, + 'Callback attached should be called in documents with browsing context'); + assert_equals(proto2.attachedCallbackCalledCounter, 0, + 'Callback attached, defined in receiving document, should not be called'); + + // test clean up + iframe1.remove(); + iframe2.remove(); + moveTest.done(); + }); + }); +}); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/attached-callback-test.html b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/attached-callback-test.html new file mode 100644 index 000000000..a4cc0b746 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/attached-callback-test.html @@ -0,0 +1,117 @@ +<!DOCTYPE html> +<html> +<head> +<title>Attached callback of a custom element should be called </title> +<meta name="timeout" content="long"> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="attached callback ... must be enqueued whenever custom element is inserted into a document and this document has a browsing context."> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#types-of-callbacks"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-a', {prototype: proto}); + var customElement = new GeneratedConstructor(); + assert_equals(proto.attachedCallbackCalledCounter, 0, 'Callback attached ' + + 'should not be called in documents that do not have a browsing context'); +}, 'Test attached callback if custom element is instantiated via constructor. ' + + 'Document has no browsing context'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-b', {prototype: proto}); + doc.body.innerHTML = '<x-b></x-b>'; + assert_equals(proto.attachedCallbackCalledCounter, 0, 'Callback attached ' + + 'should not be called in documents that do not have a browsing context'); +}, 'Test attached callback if custom element is created via innerHTML property. ' + + 'Document has no browsing context'); + + +test(function() { + var doc = newHTMLDocument(); + doc.body.innerHTML = '<x-c></x-c>'; + var proto = newHTMLElementPrototype(); + doc.registerElement('x-c', {prototype: proto}); + assert_equals(proto.attachedCallbackCalledCounter, 0, 'Callback attached ' + + 'should not be called in documents that do not have a browsing context'); +}, 'Test attached callback if custom element is created via innerHTML property before ' + + 'registration. Document has no browsing context'); + + +test(function() { + var doc = newHTMLDocument(); + doc.body.innerHTML = '<x-d id="x-d"></x-d>'; + var customElement = doc.querySelector('#x-d'); + + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-d', {prototype: proto}); + + customElement.constructor.prototype = proto; + assert_equals(proto.attachedCallbackCalledCounter, 0, 'Callback attached should ' + + 'not be called for unregistered custom element in document without browsing context'); +}, 'Test attached callback if custom element is unregistered'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + assert_equals(proto.attachedCallbackCalledCounter, 1, 'Callback attached should be ' + + 'called in documents with browsing context'); +}, 'Test attached callback. Document has browsing context'); + + +testInIFrame('../../resources/blank.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + + var x = doc.createElement('x-element'); + assert_equals(proto.attachedCallbackCalledCounter, 0, 'Callback attached should not ' + + 'be called before element is added to document with browsing context'); + + doc.body.appendChild(x); + assert_equals(proto.attachedCallbackCalledCounter, 1, 'Callback attached should be called ' + + 'in documents with browsing context'); +}, 'Test attached callback. Registered element is created via document.createElement(). ' + + 'Document has browsing context'); + + +testInIFrame('../../resources/blank.html', function(doc) { + var x = doc.createElement('x-element'); + doc.body.appendChild(x); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + assert_equals(proto.attachedCallbackCalledCounter, 1, 'Callback attached should ' + + 'be called in documents with browsing context'); +}, 'Test attached callback. Unregistered element is created via document.createElement(). ' + + 'Document has browsing context'); + + +testInIFrame('../../resources/blank.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + doc.body.innerHTML = '<x-element></x-element>'; + assert_equals(proto.attachedCallbackCalledCounter, 1, 'Callback attached should ' + + 'be called in documents with browsing context'); +}, 'Test attached callback. Registered element is created via innerHTML property. ' + + 'Document has browsing context'); + + +testInIFrame('../../resources/blank.html', function(doc) { + doc.body.innerHTML = '<x-element></x-element>'; + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + assert_equals(proto.attachedCallbackCalledCounter, 1, 'Callback attached should ' + + 'be called in documents with browsing context'); +}, 'Test attached callback. Unresolved element is created via innerHTML property. ' + + 'Document has browsing context'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/attribute-changed-callback-change-attribute-test.html b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/attribute-changed-callback-change-attribute-test.html new file mode 100644 index 000000000..cd5f3a4b8 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/attribute-changed-callback-change-attribute-test.html @@ -0,0 +1,229 @@ +<!DOCTYPE html> +<html> +<head> +<title>Test attributeChanged callback is called if custom element attribute value is changed</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="attributeChanged callback must be enqueued whenever custom element's attribute is added, changed or removed"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#types-of-callbacks"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-a', {prototype: proto}); + var customElement = new GeneratedConstructor(); + + customElement.setAttribute('class', 'someClass'); + proto.attributeChangedCallbackCalledCounter = 0; + customElement.setAttribute('class', 'someClass2'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); +}, 'Test attributeChanged callback is called if attribute value is changed by method ' + + 'setAttribute(). The custom element is created via constructor'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-b', {prototype: proto}); + + doc.body.innerHTML = '<x-b id="x-b" class="oldValue"></x-b>'; + var customElement = doc.querySelector('#x-b'); + customElement.setAttribute('class', 'newValue'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); + assert_array_equals(proto.attributeChangedCallbackArgs, + ['class', 'oldValue', 'newValue'], + 'Unexpected callback attributeChanged arguments'); +}, 'Test attributeChanged callback arguments if attribute value is changed by method ' + + 'setAttribute(). The custom element is created via innerHTML property'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-c', {prototype: proto}); + var customElement = new GeneratedConstructor(); + + customElement.setAttribute('class', 'someClass'); + proto.attributeChangedCallbackCalledCounter = 0; + customElement.classList.add('someClass3'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); +}, 'Test attributeChanged callback is called if attribute value is changed by method ' + + 'classList.add(). The custom element is created via constructor'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-d', {prototype: proto}); + + doc.body.innerHTML = '<x-d id="x-d" class="oldValue"></x-d>'; + var customElement = doc.querySelector('#x-d'); + customElement.classList.add('newestValue'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); + assert_array_equals(proto.attributeChangedCallbackArgs, + ['class', 'oldValue', 'oldValue newestValue'], + 'Unexpected callback attributeChanged arguments'); +}, 'Test attributeChanged callback arguments if attribute value is changed by method ' + + 'classList.add(). The custom element is created via innerHTML property'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-e', {prototype: proto}); + var customElement = new GeneratedConstructor(); + + customElement.setAttribute('class', 'someClass'); + proto.attributeChangedCallbackCalledCounter = 0; + customElement.id = 'someId'; + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); +}, 'Test attributeChanged callback is called if attribute value is changed as property. ' + + 'The custom element is created via constructor'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-f', {prototype: proto}); + + doc.body.innerHTML = '<x-f id="x-f" class="oldValue"></x-f>'; + var customElement = doc.querySelector('#x-f'); + customElement.className = 'lastValue'; + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); + assert_array_equals(proto.attributeChangedCallbackArgs, + ['class', 'oldValue', 'lastValue'], + 'Unexpected callback attributeChanged arguments'); +}, 'Test attributeChanged callback arguments if attribute value is changed as property. ' + + 'The custom element is created via innerHTML property'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-g', {prototype: proto}); + var customElement = new GeneratedConstructor(); + + customElement.setAttribute('class', 'someClass someSuperClass'); + proto.attributeChangedCallbackCalledCounter = 0; + customElement.classList.toggle('someClass'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); +}, 'Test attributeChanged callback is called if attribute value is changed by classList.toggle(). ' + + 'The custom element is created via constructor'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-h', {prototype: proto}); + + doc.body.innerHTML = '<x-h id="x-h" class="oldValue lastValue"></x-h>'; + var customElement = doc.querySelector('#x-h'); + customElement.classList.toggle('lastValue'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); + assert_array_equals(proto.attributeChangedCallbackArgs, + ['class', 'oldValue lastValue', 'oldValue'], + 'Unexpected callback attributeChanged arguments'); +}, 'Test attributeChanged callback arguments if attribute value is changed by classList.toggle(). ' + + 'The custom element is created via innerHTML property'); + + +test(function() { + var doc = newHTMLDocument(); + doc.body.innerHTML = '<x-i id="x-i" class="oldValue"></x-i>'; + var customElement = doc.querySelector('#x-i'); + customElement.setAttribute('class', 'newValue'); + customElement.setAttribute('class', 'newestValue'); + + var proto = newHTMLElementPrototype(); + doc.registerElement('x-i', {prototype: proto}); + + assert_equals(proto.attributeChangedCallbackCalledCounter, 0, + 'Callback attributeChanged should not be called'); + + customElement.setAttribute('class', 'rightValue'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should not be called'); + assert_array_equals(proto.attributeChangedCallbackArgs, + ['class', 'newestValue', 'rightValue'], + 'Unexpected callback attributeChanged arguments'); +}, 'Test attributeChanged callback is not called if custom element is not registered'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + + var customElement = doc.querySelector('#x-element'); + customElement.setAttribute('class', 'firstValue'); + customElement.setAttribute('class', 'secondValue'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 2, 'Callback ' + + 'attributeChanged should be called after call to setAttribute()'); + + customElement.classList.add('someClass3'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 3, 'Callback ' + + 'attributeChanged should be called after call to classList.add()'); + + customElement.id = 'someId'; + assert_equals(proto.attributeChangedCallbackCalledCounter, 4, 'Callback ' + + 'attributeChanged should be called after changing attribute as property'); +}, 'Test attributeChanged callback is called if attribute value is changed. ' + + 'The document has browsing context'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + var customElement = doc.querySelector('#x-element'); + + customElement.setAttribute('class', 'firstValue'); + customElement.setAttribute('class', 'secondValue'); + assert_array_equals(proto.attributeChangedCallbackArgs, + ['class', 'firstValue', 'secondValue'], + 'Unexpected callback attributeChanged arguments after call to setAttribute()'); + + customElement.classList.add('newestValue'); + assert_array_equals(proto.attributeChangedCallbackArgs, + ['class', 'secondValue', 'secondValue newestValue'], + 'Unexpected callback attributeChanged arguments after call to classList.add()'); + + customElement.className = 'lastValue'; + assert_array_equals(proto.attributeChangedCallbackArgs, + ['class', 'secondValue newestValue', 'lastValue'], + 'Unexpected callback attributeChanged arguments after changing attribute as property'); +}, 'Test attributeChanged callback arguments if attribute value is changed. ' + + 'The document has browsing context'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var customElement = doc.querySelector('#x-element'); + customElement.setAttribute('class', 'firstValue'); + customElement.setAttribute('class', 'secondValue'); + + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + customElement.setAttribute('class', 'thirdValue'); + + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); + assert_array_equals(proto.attributeChangedCallbackArgs, + ['class', 'secondValue', 'thirdValue'], + 'Unexpected callback attributeChanged arguments after setAttribute() call'); +}, 'Test attributeChanged callback is not called if custom element is not registered. ' + + 'The document has browsing context'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/attribute-changed-callback-remove-attribute-test.html b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/attribute-changed-callback-remove-attribute-test.html new file mode 100644 index 000000000..fb451b074 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/attribute-changed-callback-remove-attribute-test.html @@ -0,0 +1,166 @@ +<!DOCTYPE html> +<html> +<head> +<title>Test attribute removing to check attributeChanged callback of a custom element</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="attributeChanged callback must be enqueued whenever custom element's attribute is removed"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#types-of-callbacks"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-a', {prototype: proto}); + + var customElement = new GeneratedConstructor(); + //attributeChangedCallback should be called the first time here + customElement.setAttribute('class', 'someClass'); + //attributeChangedCallback should be called the second time here + customElement.removeAttribute('class'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 2, 'Callback attributeChanged should be called ' + + 'after setAttribute() and removeAttribute() calls'); +}, 'Test attributeChanged callback if attribute is removed. ' + + 'The custom element created via constructor'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(element) { + var obj = doc.createElement(element); + var proto = newCustomElementPrototype(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement('x-' + element + '-' + element + '-1', { + prototype: proto, + extends: element + }); + + var customElement = new GeneratedConstructor(); + //attributeChangedCallback should be called the first time here + customElement.setAttribute('class', 'someClass'); + //attributeChangedCallback should be called the second time here + customElement.removeAttribute('class'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 2, + 'Callback attributeChanged should be called ' + + 'after setAttribute() and removeAttribute() calls for "' + element + '"'); + }); +}, 'Test attributeChanged callback if attribute is removed. ' + + 'The custom element created via constructor and extends HTML element'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-b', {prototype: proto}); + + doc.body.innerHTML = '<x-b id="x-b" class="theClass"></x-b>'; + var customElement = doc.querySelector('#x-b'); + customElement.removeAttribute('class'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called ' + + 'after removeAttribute() call'); +}, 'Test attributeChanged callback if attribute is removed. ' + + 'The custom element created via innerHTML property'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-c', {prototype: proto}); + + doc.body.innerHTML = '<x-c id="x-c" class="theClass"></x-c>'; + var customElement = doc.querySelector('#x-c'); + customElement.removeAttribute('class'); + assert_equals(proto.attributeChangedCallbackArgs[2], null, + 'Removing an attribute should invoke ' + + 'the attributeChanged callback with a null new value'); + assert_array_equals(proto.attributeChangedCallbackArgs, + ['class', 'theClass', null], + 'Unexpected attributeChanged callback arguments'); +}, 'Test attributeChanged callback arguments if attribute is removed. ' + + 'The custom element created via innerHTML property'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + + doc.body.innerHTML = '<x-d id="x-d" class="theClass"></x-d>'; + var customElement = doc.querySelector('#x-d'); + // this should not call or enqueue attributeChangedCallback + customElement.setAttribute('class', 'someClass'); + // this one should not too + customElement.removeAttribute('class'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 0, + 'Callback attributeChanged should not be called'); + + doc.registerElement('x-d', {prototype: proto}); + // this call invokes attributeChangedCallback + customElement.setAttribute('name', 'someName'); + // and this one + customElement.removeAttribute('name'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 2, + 'Callback attributeChanged should be called ' + + 'after setAttribute() and removeAttribute() calls'); +}, 'Test attributeChanged callback is not called if attribute is removed. ' + + 'The custom element created via innerHTML property and unresolved at first'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + + var customElement = doc.querySelector('#x-element'); + customElement.setAttribute('class', 'someClass'); + customElement.removeAttribute('class'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 2, + 'Callback attributeChanged should be called ' + + 'after setAttribute() and removeAttribute() calls'); +}, 'Test attributeChanged callback is called if attribute is removed. ' + + 'The custom element created via constructor and the document has browsing context'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + + doc.body.innerHTML = '<x-element id="x-element" class="theClass"></x-element>'; + var customElement = doc.querySelector('#x-element'); + customElement.removeAttribute('class'); + assert_equals(proto.attributeChangedCallbackArgs[2], null, + 'Removing an attribute should invoke ' + + 'the attributeChanged callback with a null new value'); + assert_array_equals(proto.attributeChangedCallbackArgs, + ['class', 'theClass', null], + 'Unexpected attributeChanged callback arguments'); +}, 'Test attributeChanged callback arguments if attribute is removed. ' + + 'The custom element created via innerHTML property and the document has browsing context'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var customElement = doc.querySelector('#x-element'); + // this should not call or enqueue attributeChangedCallback + customElement.setAttribute('name', 'someName'); + // this one too + customElement.removeAttribute('name'); + + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + assert_equals(proto.attributeChangedCallbackCalledCounter, 0, + 'Callback attributeChanged should not be called'); + // this call invokes attributeChangedCallback + customElement.setAttribute('class', 'someClass'); + // this call invokes attributeChangedCallback at second time + customElement.removeAttribute('class'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 2, + 'Callback attributeChanged should be called ' + + 'after setAttribute() and removeAttribute() calls'); +}, 'Test attributeChanged callback if attribute is removed. ' + + 'The custom element created via innerHTML property and unresolved at first. ' + + 'The document has browsing context'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/attribute-changed-callback-set-attribute-test.html b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/attribute-changed-callback-set-attribute-test.html new file mode 100644 index 000000000..1c4ab8618 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/attribute-changed-callback-set-attribute-test.html @@ -0,0 +1,338 @@ +<!DOCTYPE html> +<html> +<head> +<title>Test attributeChanged callback is called if custom element attribute value is set</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="attributeChanged callback ... must be enqueued whenever custom element's attribute is added, changed or removed."> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#types-of-callbacks"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-a', {prototype: proto}); + + var customElement = new GeneratedConstructor(); + customElement.setAttribute('class', 'someClass'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); +}, 'Test attributeChanged callback is called if attribute value is set by method ' + + 'setAttribute(). The custom element is created via constructor.'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-b', {prototype: proto}); + + var customElement = new GeneratedConstructor(); + customElement.setAttribute('class', 'someClass'); + assert_array_equals(proto.attributeChangedCallbackArgs, ['class', null, 'someClass'], + 'Unexpected callback attributeChanged arguments'); +}, 'Test attributeChanged callback arguments if attribute value is set by method ' + + 'setAttribute(). The custom element is created via constructor.'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-c', {prototype: proto}); + + var customElement = new GeneratedConstructor(); + customElement.classList.add('someClass'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); +}, 'Test attributeChanged callback is called if attribute value is set by method ' + + 'classList.add(). The custom element is created via constructor.'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-d', {prototype: proto}); + + var customElement = new GeneratedConstructor(); + customElement.classList.add('someClass'); + assert_array_equals(proto.attributeChangedCallbackArgs, ['class', null, 'someClass'], + 'Unexpected callback attributeChanged arguments'); +}, 'Test attributeChanged callback arguments if attribute value is set by method ' + + 'classList.add(). The custom element is created via constructor.'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-e', {prototype: proto}); + + var customElement = new GeneratedConstructor(); + customElement.classList.toggle('someClass'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); +}, 'Test attributeChanged callback is called if attribute value is set by method ' + + 'classList.toggle(). The custom element is created via constructor.'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-f', {prototype: proto}); + + var customElement = new GeneratedConstructor(); + customElement.classList.toggle('someClass'); + assert_array_equals(proto.attributeChangedCallbackArgs, ['class', null, 'someClass'], + 'Unexpected callback attributeChanged arguments'); +}, 'Test attributeChanged callback arguments if attribute value is set by method ' + + 'classList.toggle(). The custom element is created via constructor.'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-g', {prototype: proto}); + + var customElement = new GeneratedConstructor(); + customElement.className = 'someClass'; + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); +}, 'Test attributeChanged callback is called if attribute value is set as property. ' + + 'The custom element is created via constructor.'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-h', {prototype: proto}); + + var customElement = new GeneratedConstructor(); + customElement.className = 'someClass'; + assert_array_equals(proto.attributeChangedCallbackArgs, ['class', null, 'someClass'], + 'Unexpected callback attributeChanged arguments'); +}, 'Test attributeChanged callback arguments if attribute value is set as property. '+ + 'The custom element is created via constructor.'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-i', {prototype: proto}); + + doc.body.innerHTML = '<x-i id="x-i" class="theClass"></x-i>'; + assert_equals(proto.attributeChangedCallbackCalledCounter, 0, + 'Callback attributeChanged should not be called'); +}, 'Test attributeChanged callback is not called if attribute value is specified in HTML. '+ + 'The custom element is created via innerHTML property.'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-j', {prototype: proto}); + + doc.body.innerHTML = '<x-j id="x-j" class="theClass"></x-j>'; + var customElement = doc.querySelector('#x-j'); + customElement.setAttribute('class', 'someClass'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); +}, 'Test attributeChanged callback is called if attribute value is set by method setAttribute(). '+ + 'The custom element is created via innerHTML property.'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-k', {prototype: proto}); + + doc.body.innerHTML = '<x-k id="x-k"></x-k>'; + var customElement = doc.querySelector('#x-k'); + customElement.setAttribute('class', 'someClass'); + assert_array_equals(proto.attributeChangedCallbackArgs, ['class', null, 'someClass'], + 'Unexpected callback attributeChanged arguments'); +}, 'Test attributeChanged callback arguments if attribute value is set by method setAttribute(). '+ + 'The custom element is created via innerHTML property.'); + + +test(function() { + var doc = newHTMLDocument(); + + doc.body.innerHTML = '<x-l id="x-l"></x-l>'; + var customElement = doc.querySelector('#x-l'); + // this call shouldn't invoke or enqueue the callback + customElement.setAttribute('class', 'someClass'); + + var proto = newHTMLElementPrototype(); + doc.registerElement('x-l', {prototype: proto}); + assert_equals(proto.attributeChangedCallbackCalledCounter, 0, + 'Callback attributeChanged should not be called'); + // this one should + customElement.setAttribute('class', 'someClass2'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); +}, 'Test attributeChanged callback is not called if attribute value of unresolved element '+ + 'is set by method setAttribute().'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(element) { + var obj = doc.createElement(element); + var proto = newCustomElementPrototype(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement('x-' + element + '-' + element + '-1', { + prototype: proto, + extends: element + }); + + var customElement = new GeneratedConstructor(); + customElement.setAttribute('class', 'someClass'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); + }); +}, 'Test attributeChanged callback of the custom element that extends some HTML element. ' + + 'Test that the callback is called'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(element) { + var obj = doc.createElement(element); + var proto = newCustomElementPrototype(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement('x-' + element + '-' + element + '-1-1', { + prototype: proto, + extends: element + }); + + var customElement = new GeneratedConstructor(); + customElement.classList.add('someClass'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); + }); +}, 'Test attributeChanged callback is called if attribute is set by method classList.add(). '+ + 'The custom element extends some HTML element.'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(element) { + var obj = doc.createElement(element); + var proto = newCustomElementPrototype(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement('x-' + element + '-' + element + '-2', { + prototype: proto, + extends: element + }); + + var customElement = new GeneratedConstructor(); + customElement.setAttribute('class', 'someClass'); + assert_array_equals(proto.attributeChangedCallbackArgs, ['class', null, 'someClass'], + 'Unexpected callback attributeChanged arguments'); + }); +}, 'Test attributeChanged callback arguments if attribute value is set by method setAttribute(). '+ + 'The custom element extends some HTML element.'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + + var customElement = doc.querySelector('#x-element'); + customElement.setAttribute('class', 'someClass'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); +}, 'Test attributeChanged callback is called if attribute value is set by method setAttribute(). '+ + 'The document has browsing context.'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + + var customElement = doc.querySelector('#x-element'); + customElement.setAttribute('class', 'someClass'); + assert_array_equals(proto.attributeChangedCallbackArgs, ['class', null, 'someClass'], + 'Unexpected callback attributeChanged arguments'); +}, 'Test attributeChanged callback arguments if attribute value is set by method setAttribute(). '+ + 'The document has browsing context.'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + + var customElement = doc.querySelector('#x-element'); + customElement.classList.add('someClass'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); +}, 'Test attributeChanged callback is called if attribute value is set by method classList.add(). '+ + 'The document has browsing context.'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + + var customElement = doc.querySelector('#x-element'); + customElement.classList.add('someClass'); + assert_array_equals(proto.attributeChangedCallbackArgs, ['class', null, 'someClass'], + 'Unexpected callback attributeChanged arguments'); +}, 'Test attributeChanged callback arguments if attribute value is set by method classList.add(). '+ + 'The document has browsing context.'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var customElement = doc.querySelector('#x-element'); + // this call shouldn't invoke or enqueue the callback + customElement.setAttribute('name', 'someName'); + + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + assert_equals(proto.attributeChangedCallbackCalledCounter, 0, + 'Callback attributeChanged should not be called'); + // this one should + customElement.setAttribute('class', 'someClass'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); +}, 'Test attributeChanged callback is not called if attribute value of unresolved element '+ + 'is set by method setAttribute(). The document has browsing context.'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + HTML5_ELEMENTS.forEach(function(element) { + var obj = doc.createElement(element); + var proto = newCustomElementPrototype(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement('x-' + element + '-' + element + '-1', { + prototype: proto, + extends: element + }); + + var customElement = new GeneratedConstructor(); + customElement.setAttribute('class', 'someClass'); + assert_equals(proto.attributeChangedCallbackCalledCounter, 1, + 'Callback attributeChanged should be called'); + }); +}, 'Test attributeChanged callback is called if attribute value is set by method setAttribute(). '+ + 'The document has browsing context. The custom element extends some HTML element.'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + HTML5_ELEMENTS.forEach(function(element) { + var obj = doc.createElement(element); + var proto = newCustomElementPrototype(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement('x-' + element + '-' + element + '-2', { + prototype: proto, + extends: element + }); + + var customElement = new GeneratedConstructor(); + customElement.setAttribute('class', 'someClass'); + assert_array_equals(proto.attributeChangedCallbackArgs, ['class', null, 'someClass'], + 'Unexpected callback attributeChanged arguments'); + }); +}, 'Test attributeChanged callback arguments if attribute value is set by method setAttribute(). '+ + 'The document has browsing context. The custom element extends some HTML element.'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/created-callback-element-prototype-test.html b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/created-callback-element-prototype-test.html new file mode 100644 index 000000000..188957c1a --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/created-callback-element-prototype-test.html @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<html> +<head> +<title>The custom element prototype must be set just prior to invoking created callback</title> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="The custom element prototype must be set just prior to invoking callback."> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#types-of-callbacks"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + + doc.body.innerHTML = '<x-a></x-a>'; + doc.registerElement('x-a', {prototype: proto}); + assert_equals(proto.createdCallbackThis.constructor.prototype, proto, + 'The custom element prototype is incorrect inside created callback'); +}, 'Test custom element prototype inside created callback when custom element is created ' + + 'in HTML before registration of a custom element'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + + doc.registerElement('x-b', {prototype: proto}); + doc.body.innerHTML = '<x-b></x-b>'; + assert_equals(proto.createdCallbackThis.constructor.prototype, proto, + 'The custom element prototype is incorrect inside created callback'); +}, 'Test custom element prototype inside created callback when custom element is created ' + + 'in HTML after registration of a custom element'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var customElement = doc.createElement('x-c'); + + doc.body.appendChild(customElement); + doc.registerElement('x-c', {prototype: proto}); + assert_equals(proto.createdCallbackThis.constructor.prototype, proto, + 'The custom element prototype is incorrect inside created callback'); +}, 'Test custom element prototype inside created callback when custom element is created ' + + 'via document.createElement() before registration of a custom element'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-d', {prototype: proto}); + + var customElement = doc.createElement('x-d'); + doc.body.appendChild(customElement); + assert_equals(proto.createdCallbackThis.constructor.prototype, proto, + 'The custom element prototype is incorrect inside created callback'); +}, 'Test custom element prototype inside created callback when custom element is created ' + + 'via document.createElement() after registration of a custom element'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-e', {prototype: proto}); + var customElement = new GeneratedConstructor(); + + assert_equals(proto.createdCallbackThis.constructor.prototype, proto, + 'The custom element prototype is incorrect inside created callback'); +}, 'Test custom element prototype inside created callback when custom element is created ' + + 'via constructor returned by method registerElement'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + assert_equals(proto.createdCallbackThis.constructor.prototype, proto, + 'The custom element prototype is incorrect inside created callback'); +}, 'Test custom element prototype inside created callback when custom element is created ' + + 'by UA parser before registration of a custom element'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/created-callback-invocation-order-test.html b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/created-callback-invocation-order-test.html new file mode 100644 index 000000000..b0f2d5ee8 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/created-callback-invocation-order-test.html @@ -0,0 +1,242 @@ +<!DOCTYPE html> +<html> +<head> +<title>All other callbacks must not be enqueued until after the created callback's invocation had started</title> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="All other callbacks must not be enqueued until after the created callback's invocation had started."> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#types-of-callbacks"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +function newPrototypeWithCallbackLog() { + var proto = Object.create(HTMLElement.prototype); + proto.callbackLog = []; + proto.createdCallback = function() { + proto.callbackLog.push('created'); + }; + proto.attachedCallback = function() { + proto.callbackLog.push('attached'); + }; + proto.attributeChangedCallback = function() { + proto.callbackLog.push('attributeChanged'); + }; + proto.detachedCallback = function() { + proto.callbackLog.push('detached'); + }; + return proto; +} + +testInIFrame('../../resources/blank.html', function(doc) { + var proto = newPrototypeWithCallbackLog(); + doc.registerElement('x-a', {prototype: proto}); + doc.body.innerHTML = '<x-a></x-a>'; + + assert_equals(proto.callbackLog[0], 'created', 'The callback ' + + proto.callbackLog[0] + ' should be enqueued after created callback'); + assert_in_array('attached', proto.callbackLog, 'The callback ' + + 'attached should be called'); +}, 'Test attached callback is enqueued after created callback'); + + +testInIFrame('../../resources/blank.html', function(doc) { + var proto = newPrototypeWithCallbackLog(); + doc.registerElement('x-b', {prototype: proto}); + doc.body.innerHTML = '<x-b id="x-b"></x-b>'; + var customElement = doc.querySelector('#x-b'); + customElement.setAttribute('key', 'value'); + + assert_equals(proto.callbackLog[0], 'created', 'The callback ' + + proto.callbackLog[0] + ' should not be enqueued before created callback'); + assert_in_array('attributeChanged', proto.callbackLog, + 'The callback attributeChanged should be called'); +}, 'Test attributeChanged callback is enqueued after created callback. ' + + 'Document has browsing context'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newPrototypeWithCallbackLog(); + doc.registerElement('x-c', {prototype: proto}); + doc.body.innerHTML = '<x-c id="x-c"></x-c>'; + var customElement = doc.querySelector('#x-c'); + customElement.setAttribute('key', 'value'); + assert_equals(proto.callbackLog[0], 'created', 'The callback ' + + proto.callbackLog[0] + ' should not be enqueued before created callback'); + assert_in_array('attributeChanged', proto.callbackLog, + 'The callback attributeChanged should be called'); +}, 'Test attributeChanged callback is enqueued after created callback. ' + + 'Document has no browsing context'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newPrototypeWithCallbackLog(); + doc.registerElement('x-element', {prototype: proto}); + var customElement = doc.querySelector('#x-element'); + doc.body.removeChild(customElement); + + assert_equals(proto.callbackLog[0], 'created', 'The callback ' + + proto.callbackLog[0] + ' should not be enqueued before created callback'); + assert_in_array('detached', proto.callbackLog, + 'The callback detached should be called'); +}, 'Test detached callback is enqueued after created callback.'); + + +test(function() { + var doc = newHTMLDocument(); + var proto1 = newPrototypeWithCallbackLog(); + proto1.createdCallback = function() { + proto1.callbackLog.push('created'); + var xe = doc.querySelector('#x-e'); + xe.setAttribute('key', 'value'); + }; + var proto2 = newPrototypeWithCallbackLog(); + + doc.registerElement('x-d', {prototype: proto1}); + doc.registerElement('x-e', {prototype: proto2}); + doc.body.innerHTML = '<x-d><x-e id="x-e"></x-e></x-d>'; + + assert_array_equals(proto2.callbackLog, ['created'], + 'attributeChanged callback should not be enqueued before created callback'); +}, 'Test attributeChanged callback is not enqueued before created callback started. ' + + 'Document has no browsing context'); + + +testInIFrame('../../resources/blank.html', function(doc) { + var proto1 = newPrototypeWithCallbackLog(); + proto1.createdCallback = function() { + proto1.callbackLog.push('created'); + var xe = doc.querySelector('#x-g'); + xe.setAttribute('key', 'value'); + }; + var proto2 = newPrototypeWithCallbackLog(); + + doc.registerElement('x-f', {prototype: proto1}); + doc.registerElement('x-g', {prototype: proto2}); + doc.body.innerHTML = '<x-f><x-g id="x-g"></x-g></x-f>'; + + assert_array_equals(proto2.callbackLog, ['created', 'attached'], + 'attributeChanged callback should not be called before created callback started'); +}, 'Test attributeChanged callback is not enqueued before created callback started. ' + + 'Document has browsing context'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newPrototypeWithCallbackLog(); + proto.createdCallback = function() { + proto.callbackLog.push('created'); + this.setAttribute('key', 'value'); + }; + doc.registerElement('x-h', {prototype: proto}); + doc.body.innerHTML = '<x-h></x-h>'; + + assert_array_equals(proto.callbackLog, ['created', 'attributeChanged'], + 'attributeChanged callback should be enqueued after created callback'); +}, 'Test attributeChanged callback is enqueued after created callback started. ' + + 'Document has no browsing context'); + + +testInIFrame('../../resources/blank.html', function(docWithBrowsingContext) { + var docNoBrowsingContext = newHTMLDocument(); + + var proto1 = newPrototypeWithCallbackLog(); + var proto2 = newPrototypeWithCallbackLog(); + proto1.createdCallback = function() { + proto1.callbackLog.push('created'); + var xk = docNoBrowsingContext.querySelector('#x-k'); + assert_equals(proto2.callbackLog.length, 0, 'Created callback for x-k ' + + 'should not be called before created callback for x-i'); + docWithBrowsingContext.body.appendChild(xk); + }; + + docNoBrowsingContext.registerElement('x-i', {prototype: proto1}); + docNoBrowsingContext.registerElement('x-k', {prototype: proto2}); + docNoBrowsingContext.body.innerHTML = '<x-i><x-k id="x-k"></x-k></x-i>'; + + // Though at the moment of inserting <x-k> into docWithBrowsingContext + // created callback is not called for <x-k> yet, attached calback is enqueued + // anyway. Because specification for setting custom element prototype algorithm reads: + // .... + // 3. If ELEMENT is in a document and this document has a browsing context: + // 1. Enqueue attached callback for ELEMENT + // + // Changes in the specification will follow, to reflect this exceptional case. + assert_array_equals(proto2.callbackLog, ['created', 'attached'], + 'attached callback should be enqueued when custom element prototype is set'); +}, 'Test attached callback is enqueued after created callback, but before created callback had started'); + + +testInIFrame('../../resources/blank.html', function(docWithBrowsingContext) { + var docNoBrowsingContext = newHTMLDocument(); + + var proto = newPrototypeWithCallbackLog(); + proto.createdCallback = function() { + proto.callbackLog.push('created'); + docWithBrowsingContext.body.appendChild(this); + }; + + docNoBrowsingContext.registerElement('x-l', {prototype: proto}); + docNoBrowsingContext.body.innerHTML = '<x-l></x-l>'; + assert_array_equals(proto.callbackLog, ['created', 'attached'], + 'attached callback should be enqueued after created callback had started'); +}, 'Test attached callback is enqueued after created callback had started'); + + +testInIFrame('../../resources/blank.html', function(doc) { + var proto1 = newPrototypeWithCallbackLog(); + var proto2 = newPrototypeWithCallbackLog(); + proto1.createdCallback = function() { + proto1.callbackLog.push('created'); + var xn = doc.querySelector('#x-n'); + assert_equals(proto2.callbackLog.length, 0, 'Created callback for x-n ' + + 'should not be called before created callback for x-m'); + this.removeChild(xn); + }; + + doc.registerElement('x-m', {prototype: proto1}); + doc.registerElement('x-n', {prototype: proto2}); + doc.body.innerHTML = '<x-m><x-n id="x-n"></x-n></x-m>'; + assert_array_equals(proto2.callbackLog, ['created'], + 'detached callback should not be enqueued before created callback had started'); +}, 'Test detached callback is not enqueued before created callback had started'); + + +testInIFrame('../../resources/blank.html', function(doc) { + var proto = newPrototypeWithCallbackLog(); + proto.createdCallback = function() { + proto.callbackLog.push('created'); + this.remove(); + }; + + doc.registerElement('x-o', {prototype: proto}); + doc.body.innerHTML = '<x-o></x-o>'; + + assert_array_equals(proto.callbackLog, ['created', 'attached', 'detached'], + 'detached callback should be enqueued after created callback had started'); +}, 'Test detached callback is enqueued after created callback had started'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newPrototypeWithCallbackLog(); + doc.registerElement('x-element', {prototype: proto}); + + // Though at the moment of inserting <x-element> into the document + // created callback is not called for <x-element> yet, attached calback is enqueued + // anyway. Because specification for setting custom element prototype algorithm reads: + // .... + // 3. If ELEMENT is in a document and this document has a browsing context: + // 1. Enqueue attached callback for ELEMENT + // + // Changes in the specification will follow, to reflect this exceptional case. + assert_array_equals(proto.callbackLog, ['created', 'attached'], + 'attached callback should be enqueued when custom element prototype is set'); +}, 'Test attached callback is enqueued after created callback after registration ' + + 'of custom element type'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/created-callback-invocation-test.html b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/created-callback-invocation-test.html new file mode 100644 index 000000000..63814a80f --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/created-callback-invocation-test.html @@ -0,0 +1,167 @@ +<!DOCTYPE html> +<html> +<head> +<title>Created callback of a custom element should be invoked after custom element instance is created and its definition is registered</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="CREATED callback is invoked after custom element instance is created and its definition is registered"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#types-of-callbacks"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-a', {prototype: proto}); + assert_equals(proto.createdCallbackCalledCounter, 0, + 'Callback created should not be called before element instance was created'); + var customElement = new GeneratedConstructor(); + assert_equals(proto.createdCallbackCalledCounter, 1, + 'Callback created should be called after element instance was created'); +}, 'Test created callback when custom element is created by constructor'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-b', {prototype: proto}); + assert_equals(proto.createdCallbackCalledCounter, 0, + 'Callback created should not be called before element instance was created'); + doc.body.innerHTML = '<x-b></x-b>'; + assert_equals(proto.createdCallbackCalledCounter, 1, + 'Callback created should be called after element instance was created'); +}, 'Test created callback when custom element is created in HTML'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-c', {prototype: proto}); + assert_equals(proto.createdCallbackCalledCounter, 0, + 'Callback created should not be called before element instance was created'); + doc.body.innerHTML = '<div><x-c></x-c></div>'; + assert_equals(proto.createdCallbackCalledCounter, 1, + 'Callback created should be called after element instance was created'); +}, 'Test created callback when custom element is created in HTML as descendant of ' + + 'another element'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-d', {prototype: proto}); + assert_equals(proto.createdCallbackCalledCounter, 0, + 'Callback created should not be called before element instance was created'); + var customElement = doc.createElement('x-d'); + assert_equals(proto.createdCallbackCalledCounter, 1, + 'Callback created should be called after element instance was created'); +}, 'Test created callback when custom element is created by createElement'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + + doc.body.innerHTML = '<x-e></x-e>'; + doc.registerElement('x-e', {prototype: proto}); + assert_equals(proto.createdCallbackCalledCounter, 1, + 'Callback created should be called after element instance was created'); +}, 'Test created callback when custom element is created in HTML before ' + + 'registration of a custom element'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-f', {prototype: proto}); + doc.body.innerHTML = '<x-f-unresolved id="x-f-unresolved"></x-f-unresolved>'; + assert_equals(proto.createdCallbackCalledCounter, 0, + 'Callback created should not be called if custom element is unresolved'); + + var customElement = doc.querySelector('#x-f-unresolved'); + customElement.constructor.prototype = proto; + assert_equals(proto.createdCallbackCalledCounter, 0, + 'Created callback should not be called if custom element is unresolved'); +}, 'Test created callback if custom element is unresolved.'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + assert_equals(proto.createdCallbackCalledCounter, 1, 'Callback created should be called'); +}, 'Test created callback is called after custom element is created and registered. ' + + 'Document has browsing context'); + + +testInIFrame('../../resources/register-and-create-custom-element.html', function(doc) { + assert_equals(doc.querySelector('#log').textContent, 'Created callback was called', + 'Callback created should be called'); +}, 'Test created callback is called after custom element is registered and created. ' + + 'Document has browsing context'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var customElement = doc.createElement('x-g'); + + doc.registerElement('x-g', {prototype: proto}); + assert_equals(proto.createdCallbackCalledCounter, 1, + 'Callback created should be called after registration of custom element'); +}, 'Test created callback when custom element is created by createElement '+ + 'before registration of a custom element'); + + +test(function(){ + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var GeneratedConstructor = doc.registerElement('x-h', {prototype: proto}); + + var customElement1 = new GeneratedConstructor(); + assert_equals(proto.createdCallbackCalledCounter, 1, 'Callback created should be called'); + + var customElement2 = doc.createElement('x-h'); + assert_equals(proto.createdCallbackCalledCounter, 2, + 'Callback created should be called after element instance was created'); + + doc.body.innerHTML = '<x-h></x-h>'; + assert_equals(proto.createdCallbackCalledCounter, 3, + 'Callback created should be called after element instance was created'); + + doc.body.innerHTML = '<div><x-h></x-h></div>'; + assert_equals(proto.createdCallbackCalledCounter, 4, + 'Callback created should be called after element instance was created'); +}, 'Test created callback. Create several custom elements using different ways'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newHTMLElementPrototype(); + + var GeneratedConstructor = doc.registerElement('x-element', {prototype: proto}); + assert_equals(proto.createdCallbackCalledCounter, 1, + 'Callback created should be called for custom element in loaded document'); + + var customElement2 = new GeneratedConstructor(); + assert_equals(proto.createdCallbackCalledCounter, 2, + 'Callback created should be called after element instance was created'); + + var customElement3 = doc.createElement('x-element'); + assert_equals(proto.createdCallbackCalledCounter, 3, + 'Callback created should be called after element instance was created'); + + doc.body.innerHTML = '<x-element></x-element>'; + assert_equals(proto.createdCallbackCalledCounter, 4, + 'Callback created should be called after element instance was created'); + + doc.body.innerHTML = '<div><x-element></x-element></div>'; + assert_equals(proto.createdCallbackCalledCounter, 5, + 'Callback created should be called after element instance was created'); +}, 'Test created callback. Create several custom elements using different ways. ' + + 'Document has browsing context'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/detached-callback-move-element-test.html b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/detached-callback-move-element-test.html new file mode 100644 index 000000000..d69eb54db --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/detached-callback-move-element-test.html @@ -0,0 +1,130 @@ +<!DOCTYPE html> +<html> +<head> +<title>Test detached callback of a custom element when moving custom element between different documents</title> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="detached callback ... must be enqueued whenever custom element is removed from the document and this document has a browsing context."> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#types-of-callbacks"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-a', {prototype: proto}); + + var customElement = doc.createElement('x-a'); + doc.body.appendChild(customElement); + + var divElement = doc.createElement('div'); + doc.body.appendChild(divElement); + divElement.appendChild(customElement); + + assert_equals(proto.detachedCallbackCalledCounter, 0, + 'Callback detached should not be called in document without a browsing context'); +}, 'Test detached callback is not called if moving custom element inside document ' + + 'without browsing context'); + + +testInIFrame('../../resources/blank.html', function(docWithBrowsingContext) { + var docNoBrowsingContext = newHTMLDocument(); + var proto1 = newHTMLElementPrototype(); + docNoBrowsingContext.registerElement('x-b', {prototype: proto1}); + + var customElement = docNoBrowsingContext.createElement('x-b'); + docNoBrowsingContext.body.appendChild(customElement); + var proto2 = newHTMLElementPrototype(); + docWithBrowsingContext.registerElement('x-b', {prototype: proto2}); + docWithBrowsingContext.body.appendChild(customElement); + + assert_equals(proto1.detachedCallbackCalledCounter, 0, + 'Callback detached should not be called in document without browsing context'); + assert_equals(proto2.detachedCallbackCalledCounter, 0, + 'Callback detached, defined in receiving document, should not be called'); +}, 'Test detached callback is not called if moving custom element from ' + + 'document without browsing context to document with browsing context'); + + +testInIFrame('../../resources/blank.html', function(docWithBrowsingContext) { + var proto1 = newHTMLElementPrototype(); + docWithBrowsingContext.registerElement('x-c', {prototype: proto1}); + + var customElement = docWithBrowsingContext.createElement('x-c'); + docWithBrowsingContext.body.appendChild(customElement); + + var docNoBrowsingContext = newHTMLDocument(); + var proto2 = newHTMLElementPrototype(); + docNoBrowsingContext.registerElement('x-c', {prototype: proto2}); + docNoBrowsingContext.body.appendChild(customElement); + assert_equals(proto1.detachedCallbackCalledCounter, 1, + 'Callback detached should be called in documents with browsing context'); + assert_equals(proto2.detachedCallbackCalledCounter, 0, + 'Callback detached, defined in receiving document, should not be called'); +}, 'Test detached callback if moving custom element from ' + + 'document with browsing context to document without browsing context'); + + +testInIFrame('../../resources/blank.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-d', {prototype: proto}); + + var customElement = doc.createElement('x-d'); + doc.body.appendChild(customElement); + var divElement = doc.createElement('div'); + doc.body.appendChild(divElement); + divElement.appendChild(customElement); + assert_equals(proto.detachedCallbackCalledCounter, 1, + 'Callback detached should be called in documents with browsing context'); +}, 'Test detached callback if moving custom element inside document ' + + 'with browsing context'); + + +var moveTest = async_test('Test detached callback if moving custom element from ' + + 'document with browsing context to document with browsing context'); + +moveTest.step(function() { + var iframe1 = newIFrame('../../resources/blank.html'); + iframe1.onload = moveTest.step_func(function() { + var doc1 = iframe1.contentDocument; + + // register custom element type + var proto1 = newHTMLElementPrototype(); + doc1.registerElement('x-e', {prototype: proto1}); + + // create custom element + var customElement = doc1.createElement('x-e'); + doc1.body.appendChild(customElement); + assert_equals(proto1.detachedCallbackCalledCounter, 0, + 'Callback detached should not be called when element is created'); + + // create second iframe + var iframe2 = newIFrame('../../resources/x-element.html'); + iframe2.onload = moveTest.step_func(function() { + var doc2 = iframe2.contentDocument; + + // register custom element type + var proto2 = newHTMLElementPrototype(); + doc2.registerElement('x-e', {prototype: proto2}); + + // move element + doc2.body.appendChild(customElement); + assert_equals(proto1.detachedCallbackCalledCounter, 1, + 'Callback detached should be called in documents with browsing context'); + assert_equals(proto2.detachedCallbackCalledCounter, 0, + 'Callback detached, defined in receiving document, should not be called'); + + // test clean up + iframe1.remove(); + iframe2.remove(); + moveTest.done(); + }); + + }); +}); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/detached-callback-no-browsing-context-test.html b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/detached-callback-no-browsing-context-test.html new file mode 100644 index 000000000..2b420d11c --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/detached-callback-no-browsing-context-test.html @@ -0,0 +1,147 @@ +<!DOCTYPE html> +<html> +<head> +<title>Detached callback of a custom element should not be called if document has no browsing context</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="detached callback ... must be enqueued whenever custom element is removed from the document and this document has a browsing context."> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#types-of-callbacks"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.registerElement('x-a', {prototype: proto}); + doc.body.innerHTML = '<x-a id="x-a"></x-a>'; + var customElement = doc.querySelector('#x-a'); + doc.body.removeChild(customElement); + + assert_equals(proto.detachedCallbackCalledCounter, 0, + 'Callback detached should not be called if the document has no browsing context'); +}, 'Test detached callback if custom element is created via innerHTML property. ' + + 'Document has no browsing context'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + doc.body.innerHTML = '<x-b id="x-b"></x-b>'; + doc.registerElement('x-b', {prototype: proto}); + var customElement = doc.querySelector('#x-b'); + doc.body.removeChild(customElement); + + assert_equals(proto.detachedCallbackCalledCounter, 0, + 'Callback detached should not be called if the document has no browsing context'); +}, 'Test detached callback if custom element is via innerHTML property before ' + + 'registration of a custom element. Document has no browsing context'); + + +test(function() { + var doc = newHTMLDocument(); + doc.body.innerHTML = '<x-c id="x-c"></x-c>'; + var customElement = doc.querySelector('#x-c'); + + var proto = newHTMLElementPrototype(); + customElement.constructor.prototype = proto; + doc.body.removeChild(customElement); + + assert_equals(proto.detachedCallbackCalledCounter, 0, + 'Callback detached should not be called if the document has no browsing context'); +}, 'Test detached callback if custom element is unregistered. ' + + 'Document has no browsing context'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + + doc.registerElement('x-d', {prototype: proto}); + doc.body.innerHTML = '<x-d id="x-d"></x-d>'; + doc.body.innerHTML = ''; + + assert_equals(proto.detachedCallbackCalledCounter, 0, + 'Callback detached should not be called if the document has no browsing context'); +}, 'Test detached callback if removing custom element via innerHTML property. ' + + 'Document has no browsing context'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + + doc.registerElement('x-e', {prototype: proto}); + doc.body.innerHTML = '<div id="customParent"><x-e id="x-e"></x-e></div>'; + var parent = doc.querySelector('#customParent'); + doc.body.removeChild(parent); + + assert_equals(proto.detachedCallbackCalledCounter, 0, + 'Callback detached should not be called if the document has no browsing context'); +}, 'Test detached callback if removing perent of custom element. ' + + 'Document has no browsing context'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + + doc.registerElement('x-f', {prototype: proto}); + doc.body.innerHTML = '<div><x-f id="x-f"></x-f></div>'; + doc.body.innerHTML = ''; + + assert_equals(proto.detachedCallbackCalledCounter, 0, + 'Callback detached should not be called if the document has no browsing context'); +}, 'Test detached callback if removing perent of custom element via innerHTML property. ' + + 'Document has no browsing context'); + + +var loseBrowsingContextTest = async_test('Test detached callback is not called ' + + 'if document lose browsing context and custom element is removed'); + +loseBrowsingContextTest.step(function() { + var iframe = newIFrame('../../resources/x-element.html'); + iframe.onload = loseBrowsingContextTest.step_func(function(){ + var doc = iframe.contentDocument; + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + + var customElement = doc.querySelector('#x-element'); + iframe.remove(); + customElement.remove(); + + assert_equals(proto.detachedCallbackCalledCounter, 0, + 'Callback detached should not be called if the document has no browsing context'); + loseBrowsingContextTest.done(); + }); +}); + + +var navigateTest = async_test('Test detached callback is not called, ' + + 'if document\'s window is navigated to another document and custom element is removed'); + +navigateTest.step(function() { + var iframe = newIFrame('../../resources/x-element.html'); + iframe.onload = navigateTest.step_func(function() { + var doc = iframe.contentDocument; + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + customElement = doc.querySelector('#x-element'); + + iframe.onload = navigateTest.step_func(function() { + customElement.remove(); + assert_equals(proto.detachedCallbackCalledCounter, 0, + 'Callback detached should not be called ' + + 'if the document has no browsing context'); + navigateTest.done(); + iframe.remove(); + }); + iframe.src = '../../resources/blank.html'; + }); +}); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/detached-callback-with-browsing-context-test.html b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/detached-callback-with-browsing-context-test.html new file mode 100644 index 000000000..31b06a907 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/custom-element-lifecycle/types-of-callbacks/detached-callback-with-browsing-context-test.html @@ -0,0 +1,85 @@ +<!DOCTYPE html> +<html> +<head> +<title>Detached callback of a custom element should be called if document has browsing context</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="detached callback ... must be enqueued whenever custom element is removed from the document and this document has a browsing context."> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#types-of-callbacks"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + var customElement = doc.querySelector('#x-element'); + doc.body.removeChild(customElement); + assert_equals(proto.detachedCallbackCalledCounter, 1, 'Callback detached should be ' + + 'called if custom element is removed from the document with browsing context'); +}, 'Test detached callback is called if custom element is removed by method removeChild() ' + + 'from document with browsing context'); + + +testInIFrame('../../resources/blank.html', function(doc) { + doc.body.innerHTML = '<div id="x-a-parent"><x-a id="x-a"></x-a></div>'; + var proto = newHTMLElementPrototype(); + doc.registerElement('x-a', {prototype: proto}); + var div = doc.querySelector('#x-a-parent'); + doc.body.removeChild(div); + assert_equals(proto.detachedCallbackCalledCounter, 1, 'Callback detached should be ' + + 'called if custom element is removed from the document with browsing context'); +}, 'Test detached callback is called if ancestor node of custom element ' + + 'is removed by method removeChild() from document with browsing context'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + var customElement = doc.querySelector('#x-element'); + var div = doc.createElement('div'); + doc.body.replaceChild(div, customElement); + assert_equals(proto.detachedCallbackCalledCounter, 1, 'Callback detached should be ' + + 'called if custom element is removed from the document with browsing context'); +}, 'Test detached callback is called if custom element is removed by method replaceChild() ' + + 'from document with browsing context'); + + +testInIFrame('../../resources/blank.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-b', {prototype: proto}); + doc.body.innerHTML = '<div id="x-b-parent"><x-b id="x-b"></x-b></div>'; + var parent = doc.querySelector('#x-b-parent'); + var replacement = doc.createElement('div'); + doc.body.replaceChild(replacement, parent); + assert_equals(proto.detachedCallbackCalledCounter, 1, 'Callback detached should be ' + + 'called if custom element is removed from the document with browsing context'); +}, 'Test detached callback is called if ancestor node of custom element ' + + 'is removed by method replaceChild() from document with browsing context'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var proto = newHTMLElementPrototype(); + doc.registerElement('x-element', {prototype: proto}); + doc.body.innerHTML = ''; + assert_equals(proto.detachedCallbackCalledCounter, 1, 'Callback detached should be ' + + 'called if custom element is removed from the document with browsing context'); +}, 'Test detached callback is called after changing custom element direct parent ' + + 'innerHTML property in the document with browsing context'); + + +testInIFrame('../../resources/blank.html', function(doc) { + doc.body.innerHTML = '<div><x-c></x-c></div>'; + var proto = newHTMLElementPrototype(); + doc.registerElement('x-c', {prototype: proto}); + doc.body.innerHTML = ''; + assert_equals(proto.detachedCallbackCalledCounter, 1, 'Callback detached should be ' + + 'called if custom element is removed from the document with browsing context'); +}, 'Test detached callback is called after changing custom element ancestor ' + + 'innerHTML property in the document with browsing context'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/changing-is-attribute.html b/testing/web-platform/tests/custom-elements/v0/instantiating/changing-is-attribute.html new file mode 100644 index 000000000..fb4338840 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/changing-is-attribute.html @@ -0,0 +1,158 @@ +<!DOCTYPE html> +<html> +<head> +<title>Changing IS attribute of the custom element must not affect this element's custom element type, after element is instantiated</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="After a custom element is instantiated, changing the value of the IS attribute must not affect this element's custom element type"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#instantiating-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var GeneratedConstructor = doc.registerElement('x-a'); + var customElement = new GeneratedConstructor(); + doc.registerElement('x-b'); + customElement.setAttribute('is', 'x-b'); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be x-a'); +}, 'Test custom element type, after assigning IS attribute value. ' + + 'Element is created by constructor'); + + +test(function() { + var doc = newHTMLDocument(); + var GeneratedConstructor = doc.registerElement('x-c'); + doc.registerElement('x-d'); + doc.body.innerHTML = '<x-c id="x-c"></x-c>'; + var customElement = doc.querySelector('#x-c'); + customElement.setAttribute('is', 'x-d'); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be x-c'); +}, 'Test custom element type, after assigning IS attribute value. ' + + 'Element is created via innerHTML property'); + + +test(function() { + var doc = newHTMLDocument(); + doc.body.innerHTML = '<x-e id="x-e"></x-e>'; + var customElement = doc.querySelector('#x-e'); + customElement.setAttribute('is', 'x-f'); + var GeneratedConstructor = doc.registerElement('x-e'); + doc.registerElement('x-f'); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be x-e'); +}, 'Test custom element type, after assigning IS attribute value to unresolved element. ' + + 'Element is created via innerHTML property'); + + +testInIFrame('../resources/x-element.html', function(doc) { + var GeneratedConstructor = doc.registerElement('x-element'); + doc.registerElement('y-element'); + var customElement = doc.querySelector('#x-element'); + customElement.setAttribute('is', 'y-element'); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be x-element'); +}, 'Test custom element type, after assigning IS attribute value. ' + + 'Element is defined in loaded HTML document'); + + +testInIFrame('../resources/x-element.html', function(doc) { + var customElement = doc.querySelector('#x-element'); + customElement.setAttribute('is', 'y-element'); + var GeneratedConstructor = doc.registerElement('x-element'); + doc.registerElement('y-element'); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be x-element'); +}, 'Test custom element type, after assigning IS attribute value to unresolved element. ' + + 'Element is defined in loaded HTML document'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + if (HTML5_DOCUMENT_ELEMENTS.indexOf(tagName) !== -1) { + return; + } + var name = 'y-' + tagName; + var obj = doc.createElement(tagName); + var proto = Object.create(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement(name, {prototype: proto, extends: tagName}); + if (HTML5_TABLE_ELEMENTS.indexOf(tagName) !== -1) { + doc.body.innerHTML = + '<table>' + + '<' + tagName + ' id="custom-element" is="' + name + '"></' + tagName + '>' + + '</table>'; + } else { + doc.body.innerHTML = + '<' + tagName + ' id="custom-element" is="' + name + '"></' + tagName + '>'; + } + var customElement = doc.querySelector('#custom-element'); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be '+ name); + + var name2 = 'y-a-' + tagName; + doc.registerElement(name2); + customElement.setAttribute('is', name2); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be ' + name); + }); +}, 'Test custom element type after changing IS attribute value. ' + + 'Element is HTML5 element with IS attribute referring to custom element type'); + + +test(function() { + var doc = newHTMLDocument(); + var localName = 'z-a'; + var obj = doc.createElement('a'); + var proto = Object.create(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement(localName, {prototype: proto, extends: 'a'}); + doc.body.innerHTML = '<a id="custom-element" is="' + localName + '"></a>'; + var customElement = doc.querySelector('#custom-element'); + + HTML5_ELEMENTS.forEach(function(tagName) { + var name = 'z-a-' + tagName; + var htmlElement = doc.createElement(tagName); + var htmlElementProto = Object.create(htmlElement.constructor.prototype); + doc.registerElement(name, {prototype: htmlElementProto, extends: tagName}); + customElement.setAttribute('is', name); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be ' + localName); + }); +}, 'Test custom element type after changing IS attribute value several times. ' + + 'Element is HTML5 element with IS attribute referring to custom element type'); + + +test(function() { + var doc = newHTMLDocument(); + var GeneratedConstructor = doc.registerElement('x-g'); + doc.registerElement('x-h'); + doc.body.innerHTML = '<x-g id="x-g" is="x-h"></x-g>'; + var customElement = doc.querySelector('#x-g'); + customElement.removeAttribute('is'); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be x-g'); +}, 'Test custom element type, after removing IS attribute value. ' + + 'Element is created via innerHTML property'); + + +test(function() { + var doc = newHTMLDocument(); + var obj = doc.createElement('a'); + var proto = Object.create(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement('x-i', {prototype: proto, extends: 'a'}); + doc.body.innerHTML = '<a id="x-i" is="x-i"></a>'; + var customElement = doc.querySelector('#x-i'); + customElement.removeAttribute('is'); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be x-i'); +}, 'Test custom element type, after removing IS attribute value. ' + + 'Element is HTML5 element with IS attribute referring to custom element type'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-constructor-is-attribute.html b/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-constructor-is-attribute.html new file mode 100644 index 000000000..0e18bf651 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-constructor-is-attribute.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html> +<head> +<title>Custom element constructor sets value of IS attribute to custom element type, if it is not equal to name</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="If TYPE is not the same as NAME, set the value of ELEMENT's IS attribute to TYPE"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#instantiating-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + if (HTML5_DOCUMENT_ELEMENTS.indexOf(tagName) !== -1) { + return; + } + var obj = doc.createElement(tagName); + var name = 'x-a-' + tagName; + var proto = Object.create(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement(name, {prototype: proto, extends: tagName}); + var customElement = new GeneratedConstructor(); + + assert_equals(customElement.getAttribute('is'), name, + 'Value of the IS attribute should be set to type'); + }); +}, 'Test that the constructor of a type extension sets the IS attribute value to the type'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + if (HTML5_DOCUMENT_ELEMENTS.indexOf(tagName) !== -1) { + return; + } + var name = 'x-b-' + tagName; + var GeneratedConstructor = doc.registerElement(name); + var customElement = new GeneratedConstructor(); + + assert_false(customElement.hasAttribute('is'), + 'IS attribute should not present if local name is the same as type'); + }); +}, 'Test that the constructor of a custom element does not set the IS attribute if local name is the same as type'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-constructor-local-name.html b/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-constructor-local-name.html new file mode 100644 index 000000000..28f6ca33f --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-constructor-local-name.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<html> +<head> +<title>Custom element constructor sets local name to the name from custom element definition</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="Set ELEMENT's local name to NAME"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#instantiating-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var GeneratedConstructor = doc.registerElement('x-a'); + var customElement = new GeneratedConstructor(); + assert_equals(customElement.localName, 'x-a', + 'Custom element local name should be equal to the name in custom element definition'); +}, 'Custom element constructor sets local name to the name from custom element definition'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-b-' + tagName; + var proto = Object.create(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement(name, { + prototype: proto, + extends: tagName + }); + var customElement = new GeneratedConstructor(); + + assert_equals(customElement.localName, tagName, + 'Custom element local name should be equal to the name in custom element definition'); + }); +}, 'Custom element constructor sets local name to the name from custom element definition. ' + + 'Test constructor of extended HTML element'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-constructor-namespace.html b/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-constructor-namespace.html new file mode 100644 index 000000000..7278086ac --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-constructor-namespace.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html> +<head> +<title>Custom element constructor sets local namespace to the namespace from custom element definition</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="Custom element constructor sets custom element namespace to the namespace in custom element definition"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#instantiating-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var GeneratedConstructor = doc.registerElement('x-a'); + var customElement = new GeneratedConstructor(); + assert_equals(customElement.namespaceURI, HTML_NAMESPACE, + 'Custom element namespace should be equal to namespace in custom element definition'); +}, 'Custom element constructor sets namespace to the namespace from custom element definition'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-b-' + tagName; + var proto = Object.create(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement(name, { + prototype: proto, + extends: tagName + }); + var customElement = new GeneratedConstructor(); + + assert_equals(customElement.namespaceURI, HTML_NAMESPACE, + 'Custom element namespace should be equal to namespace in custom element definition'); + }); +}, 'Custom element constructor sets namespace to the namespace from custom element definition. ' + + 'Test constructor of extended HTML element'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-constructor-node-document.html b/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-constructor-node-document.html new file mode 100644 index 000000000..3112b36da --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-constructor-node-document.html @@ -0,0 +1,100 @@ +<!DOCTYPE html> +<html> +<head> +<title>Custom element constructor sets owner document to the document, where custom element type is registered</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="Custom element constructor sets custom element node document to the document, where custom element type is registered"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#instantiating-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var GeneratedConstructor = doc.registerElement('x-a'); + var customElement = new GeneratedConstructor(); + assert_equals(customElement.ownerDocument, doc, + 'Custom element owner document should be the document, where custom element ' + + 'type is registered'); +}, 'Custom element constructor sets owner document to the document, where custom element ' + + 'type is registered'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-b-' + tagName; + var proto = Object.create(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement(name, { + prototype: proto, + extends: tagName + }); + var customElement = new GeneratedConstructor(); + + assert_equals(customElement.ownerDocument, doc, + 'Custom element owner document should be the document, where custom element ' + + 'type is registered'); + }); +}, 'Custom element constructor sets owner document to the document, where custom element ' + + 'type is registered. Test constructor of extended HTML element'); + + +test(function() { + var doc = newHTMLDocument(); + var sharedRegistryDocument = doc.implementation.createHTMLDocument('Document 2'); + + var name = 'x-c'; + var GeneratedConstructor = doc.registerElement(name); + var customElement = new GeneratedConstructor(); + assert_equals(customElement.ownerDocument, doc, + 'Custom element owner document should be the document, where custom element ' + + 'type is registered'); + + var name2 = 'x-d'; + var GeneratedConstructor2 = sharedRegistryDocument.registerElement(name2); + var customElement2 = new GeneratedConstructor2(); + assert_equals(customElement2.ownerDocument, sharedRegistryDocument, + 'Custom element owner document should be the document, where custom element ' + + 'type is registered'); +}, 'Custom element constructor sets owner document to the document, where custom element ' + + 'type is registered. Test different documents with shared registry'); + + +test(function() { + var doc = newHTMLDocument(); + var sharedRegistryDocument = doc.implementation.createHTMLDocument('Document 2'); + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-e-' + tagName; + var proto = Object.create(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement(name, { + prototype: proto, + extends: tagName + }); + var customElement = new GeneratedConstructor(); + assert_equals(customElement.ownerDocument, doc, + 'Custom element owner document should be the document, where custom element ' + + 'type is registered'); + + var obj2 = sharedRegistryDocument.createElement(tagName); + var name2 = 'x-f-' + tagName; + var proto2 = Object.create(obj2.constructor.prototype); + var GeneratedConstructor2 = sharedRegistryDocument.registerElement(name2, { + prototype: proto2, + extends: tagName + }); + var customElement2 = new GeneratedConstructor2(); + assert_equals(customElement2.ownerDocument, sharedRegistryDocument, + 'Custom element owner document should be the document, where custom element ' + + 'type is registered'); + }); +}, 'Custom element constructor sets owner document to the document, where custom element ' + + 'type is registered. Test constructor of extended HTML element for different documents ' + + 'with shared registry'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-constructor-prototype.html b/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-constructor-prototype.html new file mode 100644 index 000000000..0158af511 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-constructor-prototype.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html> +<head> +<title>Custom element constructor prototype is the prototype object specified in element definition</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="Custom element constructor prototype is the prototype object specified in element definition"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#registering-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var proto = Object.create(SVGElement.prototype); + var GeneratedConstructor = doc.registerElement('x-a', { + prototype: proto, + extends: 'p' + }); + assert_true(GeneratedConstructor.prototype === proto, + 'Custom element constructor must have the prototype specified in registerElement()'); +}, 'If custom element type is registered with prototype, the custom element ' + + 'constructor should have the prototype specified in registerElement() call'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-prototype.html b/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-prototype.html new file mode 100644 index 000000000..2b298ea74 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-prototype.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html> +<head> +<title>Custom element prototype is the prototype object specified in element definition</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="Custom element prototype is the prototype object specified in element definition"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#instantiating-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var proto = Object.create(SVGElement.prototype); + var GeneratedConstructor = doc.registerElement('x-a', { + prototype: proto, + extends: 'p' + }); + var customElement = new GeneratedConstructor(); + + assert_true(Object.getPrototypeOf(customElement) === proto, + 'Custom element instance must have the prototype specified in registerElement()'); +}, 'If custom element type is registered with prototype, the custom element ' + + 'instance should have the prototype specified in registerElement() call'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-type-is-attribute.html b/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-type-is-attribute.html new file mode 100644 index 000000000..c18290d0e --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-type-is-attribute.html @@ -0,0 +1,73 @@ +<!DOCTYPE html> +<html> +<head> +<title>Instantiation of custom element: custom element type is given as the value of the IS attribute</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="The custom element type is given to a custom element at the time of its instantation in one of the two ways: ... 2. As the value of the IS attribute of the custom element."> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#instantiating-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + if (HTML5_DOCUMENT_ELEMENTS.indexOf(tagName) !== -1) { + return; + } + var obj = doc.createElement(tagName); + var name = 'x-a-' + tagName; + var proto = Object.create(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement(name, {prototype: proto, extends: tagName}); + + if (HTML5_TABLE_ELEMENTS.indexOf(tagName) !== -1) { + doc.body.innerHTML = + '<table>' + + '<' + tagName + ' id="custom-element" is="' + name + '"></' + tagName + '>' + + '</table>'; + } else { + doc.body.innerHTML = + '<' + tagName + ' id="custom-element" is="' + name + '"></' + tagName + '>'; + } + var customElement = doc.querySelector('#custom-element'); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be the type, specified as value of IS attribute'); + }); +}, 'Instantiation of custom element: custom element type is given as the value of ' + + 'the IS attribute'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + if (HTML5_DOCUMENT_ELEMENTS.indexOf(tagName) !== -1) { + return; + } + var obj = doc.createElement(tagName); + var name = 'x-b-' + tagName; + if (HTML5_TABLE_ELEMENTS.indexOf(tagName) !== -1) { + doc.body.innerHTML = + '<table>' + + '<' + tagName + ' id="custom-element" is="' + name + '"></' + tagName + '>' + + '</table>'; + } else { + doc.body.innerHTML = + '<' + tagName + ' id="custom-element" is="' + name + '"></' + tagName + '>'; + } + var proto = Object.create(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement(name, {prototype: proto, extends: tagName}); + + var customElement = doc.querySelector('#custom-element'); + + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be the type, specified as value of IS attribute'); + }); +}, 'Instantiation of custom element: custom element type is given as the value ' + + 'of the IS attribute. Custom element is unresolved at first'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-type-local-name-and-is-attribute.html b/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-type-local-name-and-is-attribute.html new file mode 100644 index 000000000..c55e2cc25 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-type-local-name-and-is-attribute.html @@ -0,0 +1,101 @@ +<!DOCTYPE html> +<html> +<head> +<title>Instantiation of custom element: the custom tag must win over the type extension</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="If both types of custom element types are provided at the time of element's instantiation, the custom tag must win over the type extension"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#instantiating-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var GeneratedConstructor = doc.registerElement('x-a'); + doc.registerElement('x-b'); + doc.body.innerHTML = '<x-a id="x-a" is="x-b"></x-a>'; + var customElement = doc.querySelector('#x-a'); + + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be the type, specified in the local name of the element'); +}, 'Custom element type must be taken from the local name of the element even ' + + 'if IS attribute provided.'); + + +test(function() { + var doc = newHTMLDocument(); + doc.registerElement('x-d'); + doc.body.innerHTML = '<x-c id="x-c" is="x-d"></x-c>'; + var customElement = doc.querySelector('#x-c'); + + var GeneratedConstructor = doc.registerElement('x-c'); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be the type, specified in the local name of the element'); +}, 'Custom element type must be taken from the local name of the element even ' + + 'if IS attribute provided. Custom element is unresolved at first'); + + +test(function() { + var doc = newHTMLDocument(); + var GeneratedConstructor = doc.registerElement('x-f'); + doc.body.innerHTML = '<x-f id="x-f" is="x-e"></x-f>'; + var customElement = doc.querySelector('#x-f'); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be the type, specified in local name of the element'); + + doc.registerElement('x-e'); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be the type, specified in local name of the element'); +}, 'Custom element type must be taken from the local name of the element even if IS ' + + 'attribute provided. There\'s no definition for the value of IS attribute at first'); + + +test(function() { + var doc = newHTMLDocument(); + var GeneratedConstructor = doc.registerElement('x-element'); + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-d-' + tagName; + doc.registerElement(name, { + prototype: Object.create(obj.constructor.prototype), + extends: tagName + }); + doc.body.innerHTML = '<x-element id="x-element" is="' + name + '"></x-element>'; + var customElement = doc.querySelector('#x-element'); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be the local name of the custom element'); + }); +}, 'Custom element type must be taken from the local name of the element even ' + + 'if IS attribute provided. IS attribute refers to another custom element type, ' + + 'which extends HTML5 elements'); + + +test(function() { + var doc = newHTMLDocument(); + doc.registerElement('y-element'); + + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-e-' + tagName; + var id = 'x-e-' + tagName; + doc.registerElement(name, { + prototype: Object.create(obj.constructor.prototype), + extends: tagName + }); + doc.body.innerHTML = '<' + name + ' id="' + id + '" is="y-element"></' + name + '>'; + var customElement = doc.querySelector('#' + id); + // We have <x-e-a is='y-element'>. Custom element type for this will be + // HTMLElement, not x-e-a (for x-e-a there should be <a is='x-e-a'>...) + assert_class_string(customElement, 'HTMLElement', + 'Custom element type should be HTMLElement'); + }); +}, 'Custom element type must be taken from the local name of the custom element even ' + + 'if IS attribute provided. The element extends HTML5 elements, IS attribute refers ' + + 'to another custom element type.'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-type-local-name.html b/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-type-local-name.html new file mode 100644 index 000000000..0f0d46f0a --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/custom-element-type-local-name.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html> +<head> +<title>Instantiation of custom element: custom element type is given via the local name of the custom element</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="The custom element type is given to a custom element at the time of its instantation in one of the two ways: 1. As the local name of the custom element."> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#instantiating-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var GeneratedConstructor = doc.registerElement('x-a'); + doc.body.innerHTML = '<x-a id="x-a"></x-a>'; + var customElement = doc.querySelector('#x-a'); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be the type, specified by the local name of ' + + 'the custom element'); +}, 'Test custom element type, which is given via the local name of the custom element. ' + + 'Custom element created via innerHTML property'); + + +testInIFrame('../resources/x-element.html', function(doc) { + var GeneratedConstructor = doc.registerElement('x-element'); + var xelement = doc.querySelector('#x-element'); + assert_equals(Object.getPrototypeOf(xelement), GeneratedConstructor.prototype, + 'Custom element type should be the type, specified by the local name of ' + + 'the custom element'); +}, 'Test custom element type, which is given via the local name of the custom element. ' + + 'Custom element is defined in loaded HTML document'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-interface-type-is-a-local-name.html b/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-interface-type-is-a-local-name.html new file mode 100644 index 000000000..f59d6dcb8 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-interface-type-is-a-local-name.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html> +<head> +<title>Document.createElement() and Document.createElementNS() create custom element of type, specified by localName argument</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="If an element definition with matching localName, namespace, and TYPE is not registered with token's document, set TYPE to localName"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#extensions-to-document-interface-to-instantiate"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var name1 = 'x-a'; + var name2 = 'x-b'; + var GeneratedConstructor = doc.registerElement(name1); + var customElement = doc.createElement(name1, name2); + + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be the local name of the custom element'); +}, 'Test Document.createElement() creates custom element of type, ' + + 'specified by localName argument, if an element definition with matching localName, ' + + 'namespace, and type is not registered'); + + +test(function() { + var doc = newHTMLDocument(); + var name1 = 'x-c'; + var name2 = 'x-d'; + var GeneratedConstructor = doc.registerElement(name1); + var customElement = doc.createElementNS(HTML_NAMESPACE, name1, name2); + + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be the local name of the custom element'); +}, 'Test Document.createElementNS() creates custom element of type, ' + + 'specified by localName argument, if an element definition with matching ' + + 'localName, namespace, and type is not registered'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-interface-type-is-a-type-extension.html b/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-interface-type-is-a-type-extension.html new file mode 100644 index 000000000..3df042676 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-interface-type-is-a-type-extension.html @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<html> +<head> +<title>Document.createElement() and Document.createElementNS() create custom element of type, specified by typeExtension argument</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="If an element definition with matching localName, namespace, and TYPE is registered then typeExtension is a TYPE"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#extensions-to-document-interface-to-instantiate"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var name1 = 'x-a'; + var GeneratedConstructor1 = doc.registerElement(name1); + var name2 = 'x-b'; + var GeneratedConstructor2 = doc.registerElement(name2); + var customElement = doc.createElement(name1, name2); + + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor2.prototype, + 'Custom element type should be the type extension of the custom element'); +}, 'Test Document.createElement() creates custom element of type, ' + + 'specified by typeExtension argument'); + + +test(function() { + var doc = newHTMLDocument(); + var name1 = 'x-c'; + var GeneratedConstructor1 = doc.registerElement(name1); + var name2 = 'x-d'; + var GeneratedConstructor2 = doc.registerElement(name2); + var customElement = doc.createElementNS(HTML_NAMESPACE, name1, name2); + + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor2.prototype, + 'Custom element type should be the type extension of the custom element'); +}, 'Test Document.createElementNS() creates custom element of type, ' + + 'specified by typeExtension argument'); + + +test(function() { + var doc = newHTMLDocument(); + var name1 = 'x-e'; + var name2 = 'x-f'; + var GeneratedConstructor2 = doc.registerElement(name2); + var customElement = doc.createElement(name1, name2); + + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor2.prototype, + 'Custom element type should be the type extension of the custom element'); +}, 'Test Document.createElement() creates custom element of type, ' + + 'specified by typeExtension argument. Definition for localName is absent'); + + +test(function() { + var doc = newHTMLDocument(); + var name1 = 'x-g'; + var name2 = 'x-h'; + var GeneratedConstructor2 = doc.registerElement(name2); + var customElement = doc.createElementNS(HTML_NAMESPACE, name1, name2); + + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor2.prototype, + 'Custom element type should be the type extension of the custom element'); +}, 'Test Document.createElementNS() creates custom element of type, ' + + 'specified by typeExtension argument. Definition for localName is absent'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-is-attribute.html b/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-is-attribute.html new file mode 100644 index 000000000..374ec5922 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-is-attribute.html @@ -0,0 +1,71 @@ +<!DOCTYPE html> +<html> +<head> +<title>Document.createElement() and Document.createElementNS() set IS attribute to type</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="If TYPE is not the same as localName, set the value of ELEMENT's IS attribute to TYPE"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#extensions-to-document-interface-to-instantiate"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-a-' + tagName; + var proto = Object.create(obj.constructor.prototype); + doc.registerElement(name, {prototype: proto, extends: tagName}); + var customElement = doc.createElement(tagName, name); + assert_equals(customElement.getAttribute('is'), name, + 'Value of the IS attribute should be set to type by Document.createElement()'); + }); +}, 'Test Document.createElement() sets the element\'s IS attribute value to type, ' + + 'if type is not the same as localName'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var name = 'x-b-' + tagName; + var customElement = doc.createElement(tagName, name); + assert_equals(customElement.getAttribute('is'), name, + 'Value of the IS attribute should be set to type by Document.createElement()'); + }); +}, 'Test Document.createElement() sets the element\'s IS attribute value to type, ' + + 'if type is not the same as localName and an element definition with matching ' + + 'localName, namespace, and type is not registered'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-c-' + tagName; + var proto = Object.create(obj.constructor.prototype); + doc.registerElement(name, {prototype: proto, extends: tagName}); + var customElement = doc.createElementNS(HTML_NAMESPACE, tagName, name); + assert_equals(customElement.getAttribute('is'), name, + 'Value of the IS attribute should be set to type by Document.createElementNS()'); + }); +}, 'Test Document.createElementNS() sets the element\'s IS attribute value to type, ' + + 'if type is not the same as localName'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var name = 'x-d-' + tagName; + var customElement = doc.createElementNS(HTML_NAMESPACE, tagName, name); + assert_equals(customElement.getAttribute('is'), name, + 'Value of the IS attribute should be set to type by Document.createElementNS()'); + }); +}, 'Test Document.createElementNS() sets the element\'s IS attribute value to type, ' + + 'if type is not the same as localNameand and an element definition with matching ' + + 'localName, namespace, and type is not registered '); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-namespace.html b/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-namespace.html new file mode 100644 index 000000000..60d501219 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-namespace.html @@ -0,0 +1,68 @@ +<!DOCTYPE html> +<html> +<head> +<title>Document.createElement() sets custom element namespace to HTML Namespace</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="Namespace for createElement is HTML Namespace"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#extensions-to-document-interface-to-instantiate"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var name = 'x-a'; + doc.registerElement(name); + var customElement = doc.createElement(name); + assert_equals(customElement.namespaceURI, HTML_NAMESPACE, + 'Custom element namespace should be HTML Namespace'); +}, 'Test Document.createElement() sets custom element namespace to HTML Namespace'); + + +test(function() { + var doc = newHTMLDocument(); + var name = 'x-b'; + var customElement = doc.createElement(name); + assert_equals(customElement.namespaceURI, HTML_NAMESPACE, + 'Custom element namespace should be HTML Namespace'); +}, 'Test Document.createElement() sets custom element namespace to HTML Namespace ' + + 'and an element definition with matching localName, namespace, and type is not registered'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-c-' + tagName; + var proto = Object.create(obj.constructor.prototype); + doc.registerElement(name, { + prototype: Object.create(proto), + extends: tagName + }); + var customElement = doc.createElement(tagName, name); + assert_equals(customElement.namespaceURI, HTML_NAMESPACE, + 'Custom element namespace for the element extending ' + tagName + + ' should be HTML Namespace'); + }); +}, 'Document.createElement() sets custom element namespace to HTML Namespace. ' + + 'Custom element is extending standard HTML tag'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var name = 'x-d-' + tagName; + var customElement = doc.createElement(tagName, name); + assert_equals(customElement.namespaceURI, HTML_NAMESPACE, + 'Custom element namespace for the element with tag name ' + tagName + + ' and type name ' + name + ' should be HTML Namespace'); + }); +}, 'Document.createElement() sets custom element namespace to HTML Namespace. ' + + 'Document.createElement() is called with standard HTML tag name and ' + + 'type without registered custom element of such type'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-type-extension-is-a-type.html b/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-type-extension-is-a-type.html new file mode 100644 index 000000000..ce7c933e2 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-type-extension-is-a-type.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<html> +<head> +<title>Document.createElement() and Document.createElementNS() create custom element of type, specified by typeExtension argument</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="Let TYPE be typeExtension, or localName if typeExtension is not present"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#extensions-to-document-interface-to-instantiate"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-a-' + tagName; + var proto = Object.create(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement(name, {prototype: proto, extends: tagName}); + var customElement = doc.createElement(tagName, name); + + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be ' + name); + }); +}, 'Test Document.createElement() creates custom element of type, ' + + 'specified by typeExtension argument'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-b-' + tagName; + var proto = Object.create(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement(name, {prototype: proto, extends: tagName}); + var customElement = doc.createElementNS(HTML_NAMESPACE, tagName, name); + + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be ' + name); + }); +}, 'Test Document.createElementNS() creates custom element of type, ' + + 'specified by typeExtension argument'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-type-extension-unresolved.html b/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-type-extension-unresolved.html new file mode 100644 index 000000000..3eaadf312 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-type-extension-unresolved.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html> +<head> +<title>Document.createElement() and Document.createElementNS() create custom element of type, specified by localName argument</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="If an element definition with matching localName, namespace, and TYPE is not registered with token's document, set TYPE to localName"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#extensions-to-document-interface-to-instantiate"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test (function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-a-' + tagName; + var proto = Object.create(obj.constructor.prototype); + var customElement = doc.createElement(tagName, name); + assert_equals(Object.getPrototypeOf(customElement), Object.getPrototypeOf(obj), + 'Unregistered custom element type should be a local name'); + + var GeneratedConstructor = doc.registerElement(name, {prototype: proto, extends: tagName}); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Registered custom element type should be the type extension'); + }); +}, 'If typeExtension is unresolved when createElement called then local name is a type'); + + +test (function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-b-' + tagName; + var proto = Object.create(obj.constructor.prototype); + var customElement = doc.createElementNS(HTML_NAMESPACE, tagName, name); + assert_equals(Object.getPrototypeOf(customElement), Object.getPrototypeOf(obj), + 'Custom element type should be a local name'); + + var GeneratedConstructor = doc.registerElement(name, {prototype: proto, extends: tagName}); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be the type extension'); + }); +}, 'If typeExtension is unresolved when createElementNS called then local name is a type'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-type-is-a-local-name.html b/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-type-is-a-local-name.html new file mode 100644 index 000000000..487b14b9d --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/create-element-type-is-a-local-name.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html> +<head> +<title>Document.createElement() and Document.createElementNS() create custom element of type, specified by single localName argument</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="Let TYPE be typeExtension, or localName if typeExtension is not present"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#extensions-to-document-interface-to-instantiate"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var name = 'x-a'; + var GeneratedConstructor = doc.registerElement(name); + var customElement = doc.createElement(name); + + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be the local name of the custom element'); +}, 'Test Document.createElement() creates custom element of type, ' + + 'specified by single localName argument'); + + +test(function() { + var doc = newHTMLDocument(); + var name = 'x-b'; + var GeneratedConstructor = doc.registerElement(name); + var customElement = doc.createElementNS(HTML_NAMESPACE, name); + assert_equals(Object.getPrototypeOf(customElement), GeneratedConstructor.prototype, + 'Custom element type should be the local name of the custom element'); +}, 'Test Document.createElementNS() creates custom element of type, ' + + 'specified by localName argument. Argument typeExtension is not passed'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/created-callback-create-element-ns.html b/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/created-callback-create-element-ns.html new file mode 100644 index 000000000..7bf09601c --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/created-callback-create-element-ns.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<html> +<head> +<title>Document.createElementNS() must enqueue created callback for registered custom element type</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="Document.createElementNS() must enqueue created callback for registered custom element type"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#extensions-to-document-interface-to-instantiate"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#types-of-callbacks"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var name = 'x-a'; + + doc.registerElement(name, {prototype: proto}); + var customElement = doc.createElementNS(HTML_NAMESPACE, name); + assert_equals(proto.createdCallbackCalledCounter, 1, + 'Callback created should be enqueued by Document.createElementNS()'); +}, 'Test Document.createElementNS() without typeExtension argument enqueues created callback'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var name = 'x-b'; + + doc.registerElement(name, {prototype: proto}); + var customElement = doc.createElementNS(HTML_NAMESPACE, name, name); + assert_equals(proto.createdCallbackCalledCounter, 1, + 'Callback created should be enqueued by Document.createElementNS()'); +}, 'Test Document.createElementNS() with typeExtension argument enqueues created callback'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var name = 'x-c'; + var customElement = doc.createElementNS(HTML_NAMESPACE, name); + assert_equals(proto.createdCallbackCalledCounter, 0, + 'Document.createElementNS() should not enqueue created callback ' + + 'for unresolved custom element'); + + doc.registerElement(name, {prototype: proto}); + assert_equals(proto.createdCallbackCalledCounter, 1, + 'Callback created should be called after custom element is registered'); +}, 'Document.createElementNS() should not enqueue created callback ' + + 'for unresolved custom element'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-d-' + tagName; + var proto = newCustomElementPrototype(Object.create(obj.constructor.prototype)); + doc.registerElement(name, {prototype: proto, extends: tagName}); + var customElement = doc.createElementNS(HTML_NAMESPACE, tagName, name); + + assert_equals(proto.createdCallbackCalledCounter, 1, + 'Callback created should be enqueued by Document.createElementNS()'); + }); +}, 'Test Document.createElementNS() enqueues created callback for custom elements ' + + 'that are extensions of HTML5 elements'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/created-callback-create-element.html b/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/created-callback-create-element.html new file mode 100644 index 000000000..dc05e01e1 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/extensions-to-document-interface/created-callback-create-element.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<html> +<head> +<title>Document.createElement() must enqueue created callback for registered custom element type</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="Document.createElement() must enqueue created callback for registered custom element type"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#extensions-to-document-interface-to-instantiate"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#types-of-callbacks"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var name = 'x-a'; + + doc.registerElement(name, {prototype: proto}); + var customElement = doc.createElement(name); + assert_equals(proto.createdCallbackCalledCounter, 1, + 'Callback created should be enqueued by Document.createElement()'); +}, 'Test Document.createElement() without typeExtension argument enqueues created callback'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var name = 'x-b'; + + doc.registerElement(name, {prototype: proto}); + var customElement = doc.createElement(name, name); + assert_equals(proto.createdCallbackCalledCounter, 1, + 'Callback created should be enqueued by Document.createElement()'); +}, 'Test Document.createElement() with typeExtension argument enqueues created callback'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = newHTMLElementPrototype(); + var name = 'x-c'; + var customElement = doc.createElement(name); + assert_equals(proto.createdCallbackCalledCounter, 0, + 'Document.createElement() should not enqueue created callback ' + + 'for unresolved custom element'); + + doc.registerElement(name, {prototype: proto}); + assert_equals(proto.createdCallbackCalledCounter, 1, + 'Callback created should be called after custom element is registered'); +}, 'Document.createElement() should not enqueue created callback ' + + 'for unresolved custom element'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-d-' + tagName; + var proto = newCustomElementPrototype(Object.create(obj.constructor.prototype)); + doc.registerElement(name, {prototype: proto, extends: tagName}); + var customElement = doc.createElement(tagName, name); + + assert_equals(proto.createdCallbackCalledCounter, 1, + 'Callback created should be enqueued by Document.createElement()'); + }); +}, 'Test Document.createElement() enqueues created callback for custom elements ' + + 'that are extensions of HTML5 elements'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/non-configurable-constructor-property.html b/testing/web-platform/tests/custom-elements/v0/instantiating/non-configurable-constructor-property.html new file mode 100644 index 000000000..ddb1ff9de --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/non-configurable-constructor-property.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html> +<head> +<title>If prototype has a non-configurable property named constructor, Document.registerElement() throws NotSupportedError</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="If PROTOTYPE has a non-configurable property named constructor, throw a NotSupportedError and stop"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#instantiating-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var proto = Object.create(HTMLElement.prototype); + Object.defineProperty(proto, 'constructor', {configurable: false}); + assert_throws('NotSupportedError', function() { + doc.registerElement('x-a', {prototype: proto}); + }, 'Exception should be thrown in case of attempt to register element ' + + 'with a non-configurable property named constructor'); +}, 'Test Document.registerElement() throws NotSupportedError ' + + 'if prototype has a non-configurable property named constructor'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = Object.create(HTMLElement.prototype); + Object.defineProperty(proto, 'constructor', {configurable: true}); + try { + doc.registerElement('x-b', {prototype: proto}); + } catch (e) { + assert_unreached('Exception should not be thrown in case of attempt to register ' + + 'element with a configurable property named constructor'); + } +}, 'Test Document.registerElement() accepts prototype with a configurable ' + + 'property named constructor without throwing errors'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/prototype-is-interface-prototype-object.html b/testing/web-platform/tests/custom-elements/v0/instantiating/prototype-is-interface-prototype-object.html new file mode 100644 index 000000000..ad7f454f5 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/prototype-is-interface-prototype-object.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<html> +<head> +<title>If prototype is already an interface prototype object, Document.registerElement() throws a NotSupportedError</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="If PROTOTYPE is already an interface prototype object for any interface object, throw a NotSupportedError and stop"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#instantiating-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-a-' + tagName; + assert_throws('NotSupportedError', function() { + doc.registerElement(name, {prototype: obj.constructor.prototype}); + }, 'Exception should be thrown in case of attempt to register element ' + + 'if prototype is already an interface prototype object (' + name + ')'); + }); +}, 'Test Document.registerElement() throws NotSupportedError ' + + 'if prototype is already an interface prototype object'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = Object.create(HTMLElement.prototype); + doc.registerElement('x-b', { + prototype: proto + }); + assert_throws('NotSupportedError', function() { + doc.registerElement('x-b', { + prototype: proto + }); + }, 'Exception should be thrown if registring custom element type with already used prototype'); +}, 'Test Document.registerElement() throws NotSupportedError ' + + 'if prototype is already used for another custom element type'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/instantiating/unchanged-attribute.html b/testing/web-platform/tests/custom-elements/v0/instantiating/unchanged-attribute.html new file mode 100644 index 000000000..3baa174cb --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/instantiating/unchanged-attribute.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Custom element's type is immutable.</title> +<meta name="author" title="Bon-Yong Lee" href="mailto:bylee78@gmail.com"> +<meta name="assert" content="After a custom element is instantiated, changing the value of the is attribute must not affect this element's custom element type."> +<link rel="help" href="http://w3c.github.io/webcomponents/spec/custom/#instantiating-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script type="text/javascript"> +test(function() { + var CustomButton = document.registerElement('custom-button', { + prototype: Object.create(HTMLButtonElement.prototype), + extends: 'button' + }); + var customButton = document.createElement('button', 'custom-button'); + + assert_true(customButton instanceof CustomButton, + 'A custom element is of the custom element type after ' + + 'instantiation'); + customButton.setAttribute('is', 'dirty'); + assert_equals('dirty', customButton.getAttribute('is'), + 'An attribute must be changed by method "setAttribute"'); + + assert_true(customButton instanceof CustomButton, + 'A custom element is of the original custom element type even ' + + 'after changing the \'is\' attribute'); +}, 'After a custom element is instantiated, changing the value of the is attribute must not affect this element\'s custom element type.'); +</script> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-default-namespace.html b/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-default-namespace.html new file mode 100644 index 000000000..6a1f532c7 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-default-namespace.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html> +<head> +<title>Default namespace is HTML namespace</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="Default namespace is HTML namespace"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#registering-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var GeneratedConstructor = doc.registerElement('x-a'); + var customElement = new GeneratedConstructor(); + + assert_equals(customElement.namespaceURI, HTML_NAMESPACE, + 'Custom element namespace should be HTML namespace'); +}, 'Default namespace is HTML namespace'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-b-' + tagName; + var proto = Object.create(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement(name, { + prototype: proto, + extends: tagName + }); + var customElement = new GeneratedConstructor(); + + assert_equals(customElement.namespaceURI, HTML_NAMESPACE, + 'Custom element namespace should be HTML namespace'); + }); +}, 'Default namespace is HTML namespace. Test constructor of extended HTML element'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-duplicate-definition.html b/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-duplicate-definition.html new file mode 100644 index 000000000..b3f661c4f --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-duplicate-definition.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html> +<head> +<title>Check duplicate definition</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="If there already exists a definition with the same TYPE, set ERROR to DuplicateDefinition and stop"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#registering-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var name = 'x-a'; + doc.registerElement(name); + assert_throws('NotSupportedError', function() { + doc.registerElement(name); + }, 'Exception should be thrown if definition with the same type already exists'); +}, 'Check duplicate definition'); + + +test(function() { + var doc = newHTMLDocument(); + var name = 'x-b'; + doc.registerElement(name); + HTML5_ELEMENTS.forEach(function(tagName) { + assert_throws('NotSupportedError', function() { + doc.registerElement(name, { + extends: tagName + }); + }, 'Exception should be thrown if definition with the same type already exists'); + }); +}, 'Check duplicate definition. Specify constructor'); + + +test(function() { + var doc = newHTMLDocument(); + var name = 'x-c'; + doc.registerElement(name, { + prototype: Object.create(HTMLAnchorElement.prototype), + extends: 'a' + }); + HTML5_ELEMENTS.forEach(function(tagName) { + assert_throws('NotSupportedError', function() { + doc.registerElement(name, { + extends: tagName + }); + }, 'Exception should be thrown if definition with the same type already exists'); + }); +}, 'Check duplicate definition. Test different prototypes and extends'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-invalid-type.html b/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-invalid-type.html new file mode 100644 index 000000000..5f2c09b4b --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-invalid-type.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html> +<head> +<title>If TYPE is an invalid custom element type, throw SyntaxError</title> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="If TYPE is an invalid custom element type, set ERROR to InvalidType and stop."> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#registering-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + assert_throws('SyntaxError', function() { doc.registerElement('1xa2'); }, + 'Registering invalid custom element type should throw SyntaxError'); +}, 'Registering invalid custom element type should throw SyntaxError'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-local-name-lowercased.html b/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-local-name-lowercased.html new file mode 100644 index 000000000..7725e2efa --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-local-name-lowercased.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html> +<head> +<title>Custom element local name should be converted to lower case if document is an HTML document</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="If DOCUMENT is an HTML document, convert NAME to lowercase"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#registering-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-a-' + tagName; + var proto = Object.create(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement(name, { + prototype: proto, + extends: tagName.toUpperCase() + }); + var customElement = new GeneratedConstructor(); + + assert_equals(customElement.localName, tagName, 'Local name should be lowercased'); + }); +}, 'Custom element local name should be lowercased if document is an HTML document'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-name-is-null.html b/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-name-is-null.html new file mode 100644 index 000000000..2b76818d6 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-name-is-null.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html> +<head> +<title>If NAME is null then localName must be set to TYPE</title> +<meta name="author" title="Vasiliy Degtyarev" href="mailto:vasya@unipro.ru"> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="If NAME is null then localName must be set to TYPE"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#registering-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var name = 'x-a'; + var proto = Object.create(HTMLElement.prototype); + var GeneratedConstructor = doc.registerElement(name, {prototype: proto}); + var customElement = new GeneratedConstructor(); + + assert_equals(customElement.localName, name, 'LocalName should be a type in case of ' + + 'attempt to register a custom element and local name is not provided'); +}, 'If NAME is not specified then localName must be set to TYPE'); + + +test(function() { + var doc = newHTMLDocument(); + var name = 'x-b'; + var proto = Object.create(HTMLElement.prototype); + var GeneratedConstructor = doc.registerElement(name, {prototype: proto, extends: null}); + var customElement = new GeneratedConstructor(); + + assert_equals(customElement.localName, name, 'LocalName should be a type in case of ' + + 'attempt to register a custom element and name is null'); +}, 'If NAME is null then localName must be set to TYPE'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-no-interface-for-name.html b/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-no-interface-for-name.html new file mode 100644 index 000000000..6d72d60df --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-no-interface-for-name.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html> +<head> +<title>If element interface for name doesn't exists then error must be thrown</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="If NAME was provided and is not null and if element interface for the name and namespace does not exist or is an interface for a custom element, set ERROR to InvalidName and stop"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#registering-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + doc.registerElement('x-a'); + assert_throws('NotSupportedError', function() { + doc.registerElement('x-b', {extends: 'x-a'}); + }, 'Exception should be thrown in case of attempt to register ' + + 'a custom element which extends another custom element'); +}, 'Exception should be thrown in case of attempt to register ' + + 'a custom element which extends another custom element'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-svg-namespace-name-is-null.html b/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-svg-namespace-name-is-null.html new file mode 100644 index 000000000..dd6b61059 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-svg-namespace-name-is-null.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> +<head> +<title>If namespace is SVG namespace and name is null then error must be thrown</title> +<meta name="author" title="Vasiliy Degtyarev" href="mailto:vasya@unipro.ru"> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="If namespace is SVG namespace and name is null then error must be thrown"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#registering-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var proto = Object.create(SVGElement.prototype); + assert_throws('NotSupportedError', function() { + doc.registerElement('x-svg-a', {prototype: proto}); + }, 'Exception should be thrown in case of attempt to register ' + + 'a custom element with SVG namespace and name is not specified'); +}, 'Error should be thrown if namespace is SVG and local name is not specified'); + + +test(function() { + var doc = newHTMLDocument(); + var proto = Object.create(SVGElement.prototype); + assert_throws('NotSupportedError', function() { + doc.registerElement('x-svg-b', {prototype: proto, extends: null}); + }, 'Exception should be thrown in case of attempt to register ' + + 'a custom element with SVG namespace and name is null'); +}, 'Error should be thrown if namespace is SVG and local name is null'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-svg-namespace.html b/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-svg-namespace.html new file mode 100644 index 000000000..497de2919 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-svg-namespace.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html> +<head> +<title>For SVG prototype namespace is SVG namespace</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="If PROTOTYPE's interface inherits from SVGElement, set NAMESPACE to SVG Namespace"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#registering-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var proto = Object.create(SVGElement.prototype); + var GeneratedConstructor = doc.registerElement('x-a', {prototype: proto, extends: 'a'}); + var customElement = new GeneratedConstructor(); + + assert_equals(customElement.namespaceURI, SVG_NAMESPACE, + 'Custom element namespace should be SVG namespace'); +}, 'For SVG prototype namespace is SVG namespace'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-type-name-lowercased.html b/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-type-name-lowercased.html new file mode 100644 index 000000000..38dce801c --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/definition-construction-algorithm-type-name-lowercased.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html> +<head> +<title>Custom element type should be converted to lower case</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="Custom element type should be lowercased"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#registering-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var GeneratedConstructor = doc.registerElement('X-A'); + var customElement = new GeneratedConstructor(); + + assert_equals(customElement.localName, 'x-a', 'Custom element type should be lowercased'); +}, 'Custom element type should be lowercased. Test constructor'); + + +test(function() { + var doc = newHTMLDocument(); + HTML5_ELEMENTS.forEach(function(tagName) { + var obj = doc.createElement(tagName); + var name = 'x-a-' + tagName; + var proto = Object.create(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement(name, { + prototype: proto, + extends: tagName.toUpperCase() + }); + var customElement = new GeneratedConstructor(); + + assert_equals(customElement.localName, tagName, 'Local name should be lowercased'); + }); +}, 'Custom element type should be lowercased. Test constructor of extended HTML element'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/element-registration-algorithm-no-registry.html b/testing/web-platform/tests/custom-elements/v0/registering/element-registration-algorithm-no-registry.html new file mode 100644 index 000000000..6e9c20be3 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/element-registration-algorithm-no-registry.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<html> +<head> +<title>If document has no registry NotSupportedError is thrown</title> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="If REGISTRY does not exist, set ERROR to NoRegistry and stop."> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#dfn-element-registration-algorithm"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = document.implementation.createDocument(null, 'test', null); + assert_throws('NotSupportedError', function(){ + doc.registerElement('x-a'); + }, 'Registering valid custom element in document ' + + 'without registry should throw NotSupportedError'); + +}, 'Registering valid custom element without options in document ' + + 'without registry should throw NotSupportedError'); + + +test(function() { + var doc = document.implementation.createDocument(null, 'test', null); + var proto = Object.create(HTMLElement.prototype); + + assert_throws('NotSupportedError', function(){ + doc.registerElement('x-b', { prototype: proto, extends: 'a'}); + }, 'Registering valid custom element in document ' + + 'without registry should throw NotSupportedError'); + +}, 'Registering valid custom element with options in document ' + + 'without registry should throw NotSupportedError'); +</script> +</body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/custom-elements/v0/registering/extensions-to-document-interface/custom-element-name.html b/testing/web-platform/tests/custom-elements/v0/registering/extensions-to-document-interface/custom-element-name.html new file mode 100644 index 000000000..e276e834a --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/extensions-to-document-interface/custom-element-name.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> +<head> +<title>Custom element local name is the lowercased value of the EXTENDS property, supplied to Document.registerElement()</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="Custom element local name is the lowercased value of the EXTENDS property"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#extensions-to-document-interface-to-register"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + + HTML5_ELEMENTS.forEach(function(tagName) { + var name = 'x-' + tagName; + var obj = doc.createElement(tagName); + var proto = Object.create(obj.constructor.prototype); + var GeneratedConstructor = doc.registerElement(name, { + prototype: proto, + extends: tagName + }); + var customElement = new GeneratedConstructor(); + + assert_equals(customElement.localName, tagName.toLowerCase(), + 'Custom element local name should be a lowercased value of the EXTENDS property, ' + + 'supplied to Document.registerElement()'); + }); +}, 'Custom element local name is the lowercased value of the EXTENDS property, ' + + 'supplied to Document.registerElement()'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/extensions-to-document-interface/custom-element-prototype.html b/testing/web-platform/tests/custom-elements/v0/registering/extensions-to-document-interface/custom-element-prototype.html new file mode 100644 index 000000000..de397aacd --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/extensions-to-document-interface/custom-element-prototype.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html> +<head> +<title>Test prototype object of a custom element</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="If PROTOTYPE is null, let PROTOTYPE be the result of invoking Object.create with HTMLElement's interface prototype object as only argument"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#extensions-to-document-interface-to-register"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var GeneratedConstructor = doc.registerElement('x-a'); + var customElement = new GeneratedConstructor(); + + assert_class_string(customElement, 'HTMLElement', + 'Custom element should be a HTMLElement, ' + + 'if its type is registered without prototype'); +}, 'Custom element should have HTMLElement prototype, ' + + 'if its type is registered without prototype'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-custom-tag-ref.html b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-custom-tag-ref.html new file mode 100644 index 000000000..33c36463c --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-custom-tag-ref.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<title>The :unresolved pseudoclass reference file</title> +<link rel="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<style> + body { + background-color: white; + } +</style> +<body> + <p>Test passes if x-element background below is red</p> + <x-element style="background-color: red;"> + x-element + </x-element> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-custom-tag.html b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-custom-tag.html new file mode 100644 index 000000000..290e5b15c --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-custom-tag.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> +<title>The :unresolved pseudoclass matching custom tag</title> +<link rel="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="The :unresolved pseudoclass must match all custom elements whose created callback has not yet been invoked"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#unresolved-element-pseudoclass"> +<link rel="match" href="unresolved-element-pseudoclass-css-test-custom-tag-ref.html"> +<style> + :unresolved { + background-color: red; + } + body { + background-color: white; + } +</style> +<body> + <p>Test passes if x-element background below is red</p> + <x-element> + x-element + </x-element> +</body> +</html> + diff --git a/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-registered-custom-tag-ref.html b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-registered-custom-tag-ref.html new file mode 100644 index 000000000..d49f3d768 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-registered-custom-tag-ref.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<title>The :unresolved pseudoclass reference file</title> +<link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<style> + body { + background-color: white; + } +</style> +<body> + <p>Test passes if x-element background below is yellow</p> + <x-element style="background-color: yellow;"> + x-element + </x-element> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-registered-custom-tag.html b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-registered-custom-tag.html new file mode 100644 index 000000000..3cd7b41d6 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-registered-custom-tag.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html> +<title>The :unresolved pseudoclass matching custom tag</title> +<link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="The :unresolved pseudoclass must match all custom elements whose created callback has not yet been invoked"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#unresolved-element-pseudoclass"> +<link rel="match" href="unresolved-element-pseudoclass-css-test-registered-custom-tag-ref.html"> +<style> + :unresolved { + background-color: red; + } + x-element { + background-color: yellow; + } + body { + background-color: white; + } +</style> +<body onload="document.registerElement('x-element');"> + <p>Test passes if x-element background below is yellow</p> + <x-element> + x-element + </x-element> +</body> +</html> + diff --git a/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-registered-type-extension-ref.html b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-registered-type-extension-ref.html new file mode 100644 index 000000000..90baf9554 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-registered-type-extension-ref.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<title>The :unresolved pseudoclass reference file</title> +<link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<style> + body { + background-color: white; + } +</style> +<body> + <p>Test passes if x-element background below is yellow</p> + <a is="x-element" style="background-color: yellow;"> + x-element + </a> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-registered-type-extension.html b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-registered-type-extension.html new file mode 100644 index 000000000..65921bd41 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-registered-type-extension.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html> +<title>The :unresolved pseudoclass matching type extension</title> +<link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="The :unresolved pseudoclass must match all custom elements whose created callback has not yet been invoked"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#unresolved-element-pseudoclass"> +<link rel="match" href="unresolved-element-pseudoclass-css-test-registered-type-extension-ref.html"> +<style> + :unresolved { + background-color: red; + } + a { + background-color: yellow; + } + body { + background-color: white; + } +</style> +<script> +function registerXElement() { + var obj = document.createElement('a'); + var proto = Object.create(obj.constructor.prototype); + document.registerElement('x-element', { prototype: proto, extends: 'a'}); +} +</script> +<body onload="registerXElement();"> + <p>Test passes if x-element background below is yellow</p> + <a is="x-element"> + x-element + </a> +</body> +</html> + diff --git a/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-type-extension-ref.html b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-type-extension-ref.html new file mode 100644 index 000000000..9865f2e39 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-type-extension-ref.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<title>The :unresolved pseudoclass reference file</title> +<link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<style> + body { + background-color: white; + } +</style> +<body> + <p>Test passes if x-element background below is red</p> + <a is="x-element" style="background-color: red;"> + x-element + </a> +</body> +</html> + diff --git a/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-type-extension.html b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-type-extension.html new file mode 100644 index 000000000..60f39125a --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-css-test-type-extension.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> +<title>The :unresolved pseudoclass matching type extension</title> +<link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="The :unresolved pseudoclass must match all custom elements whose created callback has not yet been invoked"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#unresolved-element-pseudoclass"> +<link rel="match" href="unresolved-element-pseudoclass-css-test-type-extension-ref.html"> +<style> + :unresolved { + background-color: red; + } + body { + background-color: white; + } +</style> +<body> + <p>Test passes if x-element background below is red</p> + <a is="x-element"> + x-element + </a> +</body> +</html> + diff --git a/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-matching-query-selector-all.html b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-matching-query-selector-all.html new file mode 100644 index 000000000..2ddc2afc2 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-matching-query-selector-all.html @@ -0,0 +1,190 @@ +<!DOCTYPE html> +<html> +<head> +<title>The :unresolved pseudoclass matching with Document.querySelectorAll()</title> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="assert" content="The :unresolved pseudoclass must match all custom elements whose created callback has not yet been invoked"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#unresolved-element-pseudoclass"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + doc.body.innerHTML = '<x-a></x-a>'; + var queryResult = doc.querySelectorAll(':unresolved'); + + assert_equals(queryResult.length, 1, + 'Unresolved custom element should be accessible by :unresolved pseudoclass'); + assert_equals(queryResult.item(0).localName, 'x-a', + 'Document.querySelectorAll(\':unresolved\') should return x-a element'); +}, 'Test that single unresolved custom element is accessible ' + + 'by Document.querySelectorAll(\':unresolved\')'); + + +test(function() { + var doc = newHTMLDocument(); + doc.body.innerHTML = '<x-b></x-b>'; + var queryResult = doc.querySelectorAll(':unresolved'); + + assert_equals(queryResult.length, 1, + 'Unresolved custom element should be accessible by :unresolved pseudoclass'); + assert_equals(queryResult.item(0).localName, 'x-b', + 'Document.querySelectorAll(\':unresolved\') should return x-b element'); + + doc.registerElement('x-b'); + queryResult = doc.querySelectorAll(':unresolved'); + + assert_equals(queryResult.length, 0, + 'Registered custom element should not be accessible by :unresolved pseudoclass'); +}, 'Test that single registered custom element is not accessible by :unresolved'); + + +test(function() { + var doc = newHTMLDocument(); + doc.body.innerHTML = '<x-c></x-c><x-d></x-d>'; + var queryResult = doc.querySelectorAll(':unresolved'); + + assert_equals(queryResult.length, 2, + 'All unresolved custom element should be accessible by :unresolved pseudoclass'); + assert_equals(queryResult.item(0).localName, 'x-c', + 'First custom element returned by Document.querySelectorAll(\':unresolved\') ' + + 'should be x-c'); + assert_equals(queryResult.item(1).localName, 'x-d', + 'Second custom element returned by Document.querySelectorAll(\':unresolved\') ' + + 'should be x-d'); + + doc.registerElement('x-c'); + queryResult = doc.querySelectorAll(':unresolved'); + + assert_equals(queryResult.length, 1, + 'Only unresolved custom elements should be accessible by :unresolved pseudoclass'); + assert_equals(queryResult.item(0).localName, 'x-d', + 'Custom element returned by Document.querySelectorAll(\':unresolved\') ' + + 'should be x-d'); +}, 'If there are more than one unresolved custom element then all of them accessible ' + + 'by Document.querySelectorAll(\':unresolved\')'); + + +test(function() { + var doc = newHTMLDocument(); + var customElement = doc.createElement('x-e'); + doc.body.appendChild(customElement); + var queryResult = doc.querySelectorAll(':unresolved'); + + assert_equals(queryResult.length, 1, + 'Unresolved custom element should be accessible by :unresolved pseudoclass'); + assert_equals(queryResult.item(0).localName, 'x-e', + 'Custom element returned by Document.querySelectorAll(\':unresolved\') ' + + 'should be x-e'); +}, 'Unresolved custom element, created via Document.createElement(), should be ' + + 'accessible by Document.querySelectorAll(\':unresolved\')'); + + +test(function() { + var doc = newHTMLDocument(); + doc.body.innerHTML = '<x-f><x-g></x-g></x-f>'; + var queryResult = doc.querySelectorAll(':unresolved'); + + assert_equals(queryResult.length, 2, + 'All unresolved custom element should be accessible by :unresolved pseudoclass'); + assert_equals(queryResult.item(0).localName, 'x-f', + 'First custom element returned by Document.querySelectorAll(\':unresolved\') ' + + 'should be x-f'); + assert_equals(queryResult.item(1).localName, 'x-g', + 'Second custom element returned by Document.querySelectorAll(\':unresolved\') ' + + 'should be x-g'); + + doc.registerElement('x-g'); + queryResult = doc.querySelectorAll(':unresolved'); + + assert_equals(queryResult.length, 1, + 'Only unresolved custom elements should be accessible by :unresolved pseudoclass'); + assert_equals(queryResult.item(0).localName, 'x-f', + 'Custom element returned by Document.querySelectorAll(\':unresolved\') ' + + 'should be x-f'); +}, 'All unresolved custom element including nested ones are accessible ' + + 'by Document.querySelectorAll(\':unresolved\')'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var queryResult = doc.querySelectorAll(':unresolved'); + + assert_equals(queryResult.length, 1, + 'Unresolved custom element should be accessible by ' + + 'Document.querySelectorAll(\':unresolved\')'); + assert_equals(queryResult.item(0).localName, 'x-element', + 'Custom element returned by Document.querySelectorAll(\':unresolved\') ' + + 'should be x-element'); +}, 'Unresolved custom element should be accessible by ' + + 'Document.querySelectorAll(\':unresolved\') in a loaded document'); + + +test(function() { + var doc = newHTMLDocument(); + + HTML5_ELEMENTS.forEach(function(tagName) { + if (HTML5_DOCUMENT_ELEMENTS.indexOf(tagName) === -1) { + var obj = doc.createElement(tagName); + var name = 'x-h-' + tagName; + var id = 'x-h-' + tagName + '-id'; + if (HTML5_TABLE_ELEMENTS.indexOf(tagName) !== -1) { + doc.body.innerHTML = + '<table>' + + '<' + tagName + ' id="' + id + '" is="' + name + '"></' + tagName + '>' + + '</table>'; + } else { + doc.body.innerHTML = + '<' + tagName + ' id="' + id + '" is="' + name + '"></' + tagName + '>'; + } + var queryResult = doc.querySelectorAll(':unresolved'); + + assert_not_equals(queryResult, null, + 'Unresolved custom element should be accessible by :unresolved pseudoclass'); + assert_equals(queryResult.item(0).id, id, + 'ID of element returned by Document.querySelectorAll(\':unresolved\') ' + + 'should be ' + id); + + var proto = Object.create(obj.constructor.prototype); + doc.registerElement(name, {prototype: proto, extends: tagName}); + var queryResult2 = doc.querySelectorAll(':unresolved'); + + assert_equals(queryResult2.length, 0, + 'Registered custom element should not be accessible by :unresolved pseudoclass'); + } + }); +}, 'Test that Document.querySelectorAll(\':unresolved\') returns unresolved custom elements, ' + + 'extending HTML elements by IS attribute'); + + +test(function() { + var doc = newHTMLDocument(); + doc.body.innerHTML = '<x-i><a is="x-j"></a></x-i><x-k></x-k><b is="x-l"/>'; + var queryResult = doc.querySelectorAll(':unresolved'); + + assert_equals(queryResult.length, 4, + 'All unresolved custom element should be accessible by :unresolved pseudoclass'); + + var elementNames = [ + queryResult.item(0).localName, + queryResult.item(1).localName, + queryResult.item(2).localName, + queryResult.item(3).localName]; + assert_array_equals(elementNames, ['x-i', 'a', 'x-k', 'b'], + 'Document.querySelectorAll(\':unresolved\') return unexpected result'); + + var isAttributes = [ + queryResult.item(0).getAttribute('is'), + queryResult.item(1).getAttribute('is'), + queryResult.item(2).getAttribute('is'), + queryResult.item(3).getAttribute('is')]; + assert_array_equals(isAttributes, [null, 'x-j', null, 'x-l'], + 'Document.querySelectorAll(\':unresolved\') return unexpected result'); +}, 'Test Document.querySelectorAll(\':unresolved\') returns mix of custom elements of different types'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-matching-query-selector.html b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-matching-query-selector.html new file mode 100644 index 000000000..47b7e5c47 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-element-pseudoclass/unresolved-element-pseudoclass-matching-query-selector.html @@ -0,0 +1,161 @@ +<!DOCTYPE html> +<html> +<head> +<title>The :unresolved pseudoclass matching with Document.querySelector()</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="The :unresolved pseudoclass must match all custom elements whose created callback has not yet been invoked"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#unresolved-element-pseudoclass"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + doc.body.innerHTML = '<x-a></x-a>'; + var customElement = doc.querySelector(':unresolved'); + + assert_not_equals(customElement, null, + 'Unresolved custom element should be accessible by :unresolved'); + assert_equals(customElement.localName, 'x-a', + 'Custom element returned by Document.querySelector(\':unresolved\') should be x-a'); +}, 'Test that unresolved custom element is accessible by Document.querySelector(\':unresolved\')'); + + +test(function() { + var doc = newHTMLDocument(); + doc.body.innerHTML = '<x-b></x-b>'; + var customElement = doc.querySelector(':unresolved'); + assert_not_equals(customElement, null, + 'Unresolved custom element should be accessible by :unresolved'); + assert_equals(customElement.localName, 'x-b', + 'Custom element returned by Document.querySelector(\':unresolved\') should be x-b'); + + doc.registerElement('x-b'); + customElement = doc.querySelector(':unresolved'); + assert_equals(customElement, null, + 'Registered custom element should not be accessible by :unresolved pseudoclass'); +}, 'Test that registered custom element are not accessible by :unresolved'); + + +test(function() { + var doc = newHTMLDocument(); + doc.body.innerHTML = '<x-c></x-c><x-d></x-d>'; + var customElement = doc.querySelector(':unresolved'); + assert_not_equals(customElement, null, + 'Unresolved custom element should be accessible by :unresolved pseudoclass'); + assert_equals(customElement.localName, 'x-c', + 'Custom element returned by Document.querySelector(\':unresolved\') should be x-c'); + + doc.registerElement('x-c'); + customElement = doc.querySelector(':unresolved'); + assert_not_equals(customElement, null, + 'Unresolved custom elements should be accessible by :unresolved pseudoclass'); + assert_equals(customElement.localName, 'x-d', + 'Custom element returned by Document.querySelector(\':unresolved\') should be x-d'); +}, 'If there are more than one unresolved custom element, all of them should be ' + + 'accessible by :unresolved pseudoclass'); + + +test(function() { + var doc = newHTMLDocument(); + var customElement = doc.createElement('x-e'); + doc.body.appendChild(customElement); + var queryResult = doc.querySelector(':unresolved'); + + assert_not_equals(queryResult, null, + 'Unresolved custom element should be accessible by :unresolved'); + assert_equals(queryResult.localName, 'x-e', + 'Custom element returned by Document.querySelector(\':unresolved\') should be x-e'); +}, 'Unresolved custom element, created via Document.createElement(), should be ' + + 'accessible by Document.querySelector(\':unresolved\')'); + + +test(function() { + var doc = newHTMLDocument(); + doc.body.innerHTML = '<div><x-f></x-f><div>'; + var customElement = doc.querySelector(':unresolved'); + + assert_not_equals(customElement, null, + 'Unresolved custom element should be accessible by :unresolved'); + assert_equals(customElement.localName, 'x-f', + 'Custom element returned by Document.querySelector(\':unresolved\') should be x-f'); +}, 'Unresolved custom element inside div element should be accessible by ' + + ':unresolved pseudoclass'); + + +test(function() { + var doc = newHTMLDocument(); + doc.body.innerHTML = '<x-h><x-g></x-g></x-h>'; + var customElement = doc.querySelector(':unresolved'); + + assert_not_equals(customElement, null, + 'Unresolved custom element should be accessible by :unresolved pseudoclass'); + assert_equals(customElement.localName, 'x-h', + 'Custom element returned by Document.querySelector(\':unresolved\') ' + + 'should be x-h'); + + doc.registerElement('x-h'); + customElement = doc.querySelector(':unresolved'); + + assert_not_equals(customElement, null, + 'Unresolved custom element should be accessible by :unresolved pseudoclass'); + assert_equals(customElement.localName, 'x-g', + 'Custom element returned by Document.querySelector(\':unresolved\') ' + + 'should be x-g'); +}, 'All unresolved custom element including nested ones should be accessible ' + + 'by Document.querySelector(\':unresolved\')'); + + +testInIFrame('../../resources/x-element.html', function(doc) { + var customElement = doc.querySelector(':unresolved'); + + assert_not_equals(customElement, null, + 'Unresolved custom element should be accessible by :unresolved'); + assert_equals(customElement.localName, 'x-element', + 'Custom element returned by Document.querySelector(\':unresolved\') should be x-element'); +}, 'Document.querySelector(): Unresolved custom element should be accessible by :unresolved ' + + 'in loaded document'); + + +test(function() { + var doc = newHTMLDocument(); + + HTML5_ELEMENTS.forEach(function(tagName) { + if (HTML5_DOCUMENT_ELEMENTS.indexOf(tagName) === -1) { + var obj = doc.createElement(tagName); + var name = 'x-i-' + tagName; + var id = 'x-i-' + tagName + '-id'; + if (HTML5_TABLE_ELEMENTS.indexOf(tagName) !== -1) { + doc.body.innerHTML = + '<table>' + + '<' + tagName + ' id="' + id + '" is="' + name + '"></' + tagName + '>' + + '</table>'; + } else { + doc.body.innerHTML = + '<' + tagName + ' id="' + id + '" is="' + name + '"></' + tagName + '>'; + } + var customElement = doc.querySelector(':unresolved'); + + assert_not_equals(customElement, null, + 'Unresolved custom element should be accessible by :unresolved pseudoclass'); + assert_equals(customElement.id, id, + 'ID of element returned by Document.querySelector(\':unresolved\') ' + + 'should be ' + id); + + var proto = Object.create(obj.constructor.prototype); + doc.registerElement(name, {prototype: proto, extends: tagName}); + var customElement2 = doc.querySelector(':unresolved'); + + assert_equals(customElement2, null, + 'Registered custom element should not be accessible by :unresolved pseudoclass'); + } + }); +}, 'Test that Document.querySelector(\':unresolved\') returns custom element, ' + + 'extending HTML elements by IS attribute'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/unresolved-elements-interface-html-element.html b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-elements-interface-html-element.html new file mode 100644 index 000000000..492c75b72 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-elements-interface-html-element.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html> +<head> +<title>Unresolved element interface must be HTMLElement, if the namespace is HTML Namespace</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="When an unresolved element is created, it's element interface must be HTMLElement, if the namespace is HTML Namespace"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#registering-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + doc.body.innerHTML = '<x-a id="x-a"></x-a>'; + var customElement = doc.querySelector('#x-a'); + + assert_not_equals(customElement, null, 'Unregistered custom element should not be null'); + + assert_class_string(customElement, 'HTMLElement', + 'Unresolved custom element must be a HTML element'); +}, 'Test interface of unresolved element, created via innerHTML property'); + + +test(function() { + var doc = newHTMLDocument(); + var customElement = doc.createElement('x-b'); + + assert_class_string(customElement, 'HTMLElement', + 'Unresolved custom element must be a HTML element'); +}, 'Test interface of unresolved element, created by Document.createElement'); + + +test(function() { + var doc = newHTMLDocument(); + var customElement = doc.createElementNS(HTML_NAMESPACE, 'x-c'); + + assert_class_string(customElement, 'HTMLElement', + 'Unresolved custom element must be a HTML element'); +}, 'Test interface of unresolved element, created by Document.createElementNS'); + + +testInIFrame('../resources/x-element.html', function(doc) { + var customElement = doc.getElementById('x-element'); + + assert_not_equals(customElement, null, 'Unregistered custom element should not be null'); + + assert_class_string(customElement, 'HTMLElement', + 'Unresolved custom element must be a HTML element'); +}, 'Test unresolved element interface in loaded HTML document'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/unresolved-elements-interface-html-unknown-element.html b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-elements-interface-html-unknown-element.html new file mode 100644 index 000000000..70c23d3c9 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-elements-interface-html-unknown-element.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<html> +<head> +<title>Unresolved element interface must be HTMLUnknownElement, if the namespace is neither HTML Namespace nor SVG Namespace</title> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="When an unresolved element is created, it's element interface must be HTMLUnknownElement, if the namespace is neither HTML Namespace nor SVG Namespace"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#registering-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML'; + +test(function() { + var doc = newHTMLDocument(); + doc.body.innerHTML = + '<math xmlns="' + MATHML_NAMESPACE + '">' + + '<x-a id="x-a"></x-a>' + + '</math>'; + var xa = doc.querySelector('#x-a'); + + assert_not_equals(xa, null, 'Unregistered custom element should not be null'); + + // According https://code.google.com/p/chromium/issues/detail?id=336377 + // expected class string is Element + assert_class_string(xa, 'Element', 'Unresolved custom element must be an Element'); +}, 'Test interface of unresolved element with MathML namespace, created via innerHTML property'); + + +test(function() { + var doc = newHTMLDocument(); + var xa = doc.createElementNS(MATHML_NAMESPACE, 'x-b'); + + assert_class_string(xa, 'Element', + 'Unresolved custom element must be a HTMLUnknownElement'); +}, 'Test interface of unresolved element with MathML namespace, ' + + 'created by Document.createElementNS'); + + +testInIFrame('../resources/x-mathml-element.html', function(doc) { + var customElement = doc.getElementById('x-math-element'); + + assert_not_equals(customElement, null, 'Unregistered custom element should not be null'); + + assert_class_string(customElement, 'Element', + 'Unresolved custom element must be a Element'); +}, 'Test interface of unresolved element in loaded HTML document with embedded MathML elements'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/registering/unresolved-elements-interface-svg-element.html b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-elements-interface-svg-element.html new file mode 100644 index 000000000..4164b62dc --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/registering/unresolved-elements-interface-svg-element.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<html> +<head> +<title>Unresolved element interface must be SVGElement, if the namespace is SVG Namespace</title> +<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +<meta name="author" title="Vasiliy Degtyarev" href="mailto:vasya@unipro.ru"> +<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +<meta name="assert" content="When an unresolved element is created, it's element interface must be SVGElement if the namespace is SVG Namespace"> +<link rel="help" href="http://www.w3.org/TR/custom-elements/#registering-custom-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + var doc = newHTMLDocument(); + var xsvg = doc.createElementNS(SVG_NAMESPACE, 'x-svg'); + + assert_class_string(xsvg, 'SVGElement', 'Unresolved custom element must be a SVG element'); +}, 'Test interface of unresolved element with valid name, created by Document.createElementNS()'); + + +test(function() { + var doc = newHTMLDocument(); + doc.body.innerHTML = + '<svg xmlns=' + SVG_NAMESPACE + ' version="1.1">' + + '<x-svg-a id="x-svg"></x-svg-a>' + + '</svg>'; + var xsvg = doc.querySelector('#x-svg'); + + assert_class_string(xsvg, 'SVGElement', 'Unresolved custom element must be a SVG element'); +}, 'Test interface of unresolved element with valid name, created via innerHTML property'); + + +testInIFrame('../resources/x-svg-element.html', function(doc) { + var xsvg = doc.getElementById('x-svg-element'); + + assert_not_equals(xsvg, null, 'Unresolved custom element should not be null'); + + assert_class_string(xsvg, 'SVGElement', + 'Unresolved custom element must be a SVG element'); +}, 'Test interface of unresolved element in loaded HTML document with embedded SVG elements'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/resources/blank.html b/testing/web-platform/tests/custom-elements/v0/resources/blank.html new file mode 100644 index 000000000..2e5697ba1 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/resources/blank.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> + <title>Blank document</title> + <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +</head> +<body> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/resources/import-master-async.html b/testing/web-platform/tests/custom-elements/v0/resources/import-master-async.html new file mode 100644 index 000000000..0ecaafb29 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/resources/import-master-async.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> + <head> + <title>Import Master Document (asynchronous)</title> + <link rel="import" href="import.html" async> + </head> + <body> + <p>Master document body</p> + </body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/resources/import-master.html b/testing/web-platform/tests/custom-elements/v0/resources/import-master.html new file mode 100644 index 000000000..d91bcb9cd --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/resources/import-master.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> + <head> + <title>Import Master Document</title> + <link rel="import" href="import.html"> + </head> + <body> + <p>Master document body</p> + </body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/resources/import.html b/testing/web-platform/tests/custom-elements/v0/resources/import.html new file mode 100644 index 000000000..dddc46701 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/resources/import.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <head> + <title>Import Document</title> + </head> + <body> + <p>Import Document body</p> + </body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/resources/register-and-create-custom-element.html b/testing/web-platform/tests/custom-elements/v0/resources/register-and-create-custom-element.html new file mode 100644 index 000000000..3aabff244 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/resources/register-and-create-custom-element.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<head> + <title>Register and create custom element</title> + <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> + <link rel="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +</head> +<body> + <div id="log"></div> + <script> + var proto = Object.create(HTMLElement.prototype); + proto.createdCallback = function() { + document.querySelector('#log').textContent = 'Created callback was called'; + }; + document.registerElement('x-element', {prototype: proto}); + </script> + <x-element id="x-element"></x-element> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/resources/x-element.html b/testing/web-platform/tests/custom-elements/v0/resources/x-element.html new file mode 100644 index 000000000..0c0d2daad --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/resources/x-element.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> + <title>x-element custom element</title> + <link rel="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"> +</head> +<body> + <x-element id="x-element"></x-element> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/resources/x-mathml-element.html b/testing/web-platform/tests/custom-elements/v0/resources/x-mathml-element.html new file mode 100644 index 000000000..3efdc2d00 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/resources/x-mathml-element.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html> +<head> + <title>Custom element in MathML namespace</title> + <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +</head> +<body> + <math xmlns="http://www.w3.org/1998/Math/MathML"> + <x-math-element id="x-math-element"></x-math-element> + </math> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/resources/x-svg-element.html b/testing/web-platform/tests/custom-elements/v0/resources/x-svg-element.html new file mode 100644 index 000000000..6cea85758 --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/resources/x-svg-element.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html> +<head> + <title>SVG custom element</title> + <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"> +</head> +<body> + <svg height="100" width="100"> + <x-svg-element id="x-svg-element"></x-element> + </svg> +</body> +</html> diff --git a/testing/web-platform/tests/custom-elements/v0/testcommon.js b/testing/web-platform/tests/custom-elements/v0/testcommon.js new file mode 100644 index 000000000..5cddbe8bc --- /dev/null +++ b/testing/web-platform/tests/custom-elements/v0/testcommon.js @@ -0,0 +1,286 @@ +/* +Distributed under both the W3C Test Suite License [1] and the W3C +3-clause BSD License [2]. To contribute to a W3C Test Suite, see the +policies and contribution forms [3]. + +[1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license +[2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license +[3] http://www.w3.org/2004/10/27-testcases + */ + +"use strict"; + +var HTML5_ELEMENTS = [ 'a', 'abbr', 'address', 'area', 'article', 'aside', + 'audio', 'b', 'base', 'bdi', 'bdo', 'blockquote', 'body', 'br', + 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', + 'command', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', + 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', + 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', + 'hgroup', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', + 'keygen', 'label', 'legend', 'li', 'link', 'map', 'mark', 'menu', + 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', + 'option', 'output', 'p', 'param', 'pre', 'progress', 'q', 'rp', 'rt', + 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', + 'span', 'strong', 'style', 'sub', 'table', 'tbody', 'td', 'textarea', + 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', + 'var', 'video', 'wbr' ]; + +var HTML5_DOCUMENT_ELEMENTS = [ 'html', 'head', 'body' ]; + +var HTML5_TABLE_ELEMENTS = [ 'caption', 'col', 'colgroup', 'tbody', 'td', + 'tfoot', 'th', 'thead', 'tr' ]; + +var EXTENDER_CHARS = [ 0x00B7, 0x02D0, 0x02D1, 0x0387, 0x0640, 0x0E46, 0x0EC6, + 0x3005, 0x3031, 0x3032, 0x3033, 0x3034, 0x3035, 0x309D, 0x309E, 0x30FC, + 0x30FD, 0x30FE ]; + +var COMBINING_CHARS = [ 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, + 0x0307, 0x0308, 0x0309, 0x030A, 0x030B, 0x030C, 0x030D, 0x030E, 0x030F, + 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317, 0x0318, + 0x0319, 0x031A, 0x031B, 0x031C, 0x031D, 0x031E, 0x031F, 0x0320, 0x0321, + 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327, 0x0328, 0x0329, 0x032A, + 0x032B, 0x032C, 0x032D, 0x032E, 0x032F, 0x0330, 0x0331, 0x0332, 0x0333, + 0x0334, 0x0335, 0x0336, 0x0337, 0x0338, 0x0339, 0x033A, 0x033B, 0x033C, + 0x033D, 0x033E, 0x033F, 0x0340, 0x0341, 0x0342, 0x0343, 0x0344, 0x0345, + 0x0360, 0x0361, 0x0483, 0x0484, 0x0485, 0x0486, 0x0591, 0x0592, 0x0593, + 0x0594, 0x0595, 0x0596, 0x0597, 0x0598, 0x0599, 0x05A0, 0x05A1, 0x05A3, + 0x05A4, 0x05A5, 0x05A6, 0x05A7, 0x05A8, 0x05A9, 0x05AA, 0x05AB, 0x05AC, + 0x05AD, 0x05AE, 0x05AF, 0x05B0, 0x05B1, 0x05B2, 0x05B3, 0x05B4, 0x05B5, + 0x05B6, 0x05B7, 0x05B8, 0x05B9, 0x05BB, 0x05BC, 0x05BD, 0x05BF, 0x05C1, + 0x05C2, 0x05C4, 0x064B, 0x064C, 0x064D, 0x064E, 0x064F, 0x0650, 0x0651, + 0x0652, 0x0670, 0x06D6, 0x06D7, 0x06D8, 0x06D9, 0x06DA, 0x06DB, 0x06DC, + 0x06DD, 0x06DE, 0x06DF, 0x06E0, 0x06E1, 0x06E2, 0x06E3, 0x06E4, 0x06E7, + 0x06E8, 0x06EA, 0x06EB, 0x06EC, 0x06ED, 0x0901, 0x0902, 0x0903, 0x093C, + 0x093E, 0x093F, 0x0940, 0x0941, 0x0942, 0x0943, 0x0944, 0x0945, 0x0946, + 0x0947, 0x0948, 0x0949, 0x094A, 0x094B, 0x094C, 0x094D, 0x0951, 0x0952, + 0x0953, 0x0954, 0x0962, 0x0963, 0x0981, 0x0982, 0x0983, 0x09BC, 0x09BE, + 0x09BF, 0x09C0, 0x09C1, 0x09C2, 0x09C3, 0x09C4, 0x09C7, 0x09C8, 0x09CB, + 0x09CC, 0x09CD, 0x09D7, 0x09E2, 0x09E3, 0x0A02, 0x0A3C, 0x0A3E, 0x0A3F, + 0x0A40, 0x0A41, 0x0A42, 0x0A47, 0x0A48, 0x0A4B, 0x0A4C, 0x0A4D, 0x0A70, + 0x0A71, 0x0A81, 0x0A82, 0x0A83, 0x0ABC, 0x0ABE, 0x0ABF, 0x0AC0, 0x0AC1, + 0x0AC2, 0x0AC3, 0x0AC4, 0x0AC5, 0x0AC7, 0x0AC8, 0x0AC9, 0x0ACB, 0x0ACC, + 0x0ACD, 0x0B01, 0x0B02, 0x0B03, 0x0B3C, 0x0B3E, 0x0B3F, 0x0B40, 0x0B41, + 0x0B42, 0x0B43, 0x0B47, 0x0B48, 0x0B4B, 0x0B4C, 0x0B4D, 0x0B56, 0x0B57, + 0x0B82, 0x0B83, 0x0BBE, 0x0BBF, 0x0BC0, 0x0BC1, 0x0BC2, 0x0BC6, 0x0BC7, + 0x0BC8, 0x0BCA, 0x0BCB, 0x0BCC, 0x0BCD, 0x0BD7, 0x0C01, 0x0C02, 0x0C03, + 0x0C3E, 0x0C3F, 0x0C40, 0x0C41, 0x0C42, 0x0C43, 0x0C44, 0x0C46, 0x0C47, + 0x0C48, 0x0C4A, 0x0C4B, 0x0C4C, 0x0C4D, 0x0C55, 0x0C56, 0x0C82, 0x0C83, + 0x0CBE, 0x0CBF, 0x0CC0, 0x0CC1, 0x0CC2, 0x0CC3, 0x0CC4, 0x0CC6, 0x0CC7, + 0x0CC8, 0x0CCA, 0x0CCB, 0x0CCC, 0x0CCD, 0x0CD5, 0x0CD6, 0x0D02, 0x0D03, + 0x0D3E, 0x0D3F, 0x0D40, 0x0D41, 0x0D42, 0x0D43, 0x0D46, 0x0D47, 0x0D48, + 0x0D4A, 0x0D4B, 0x0D4C, 0x0D4D, 0x0D57, 0x0E31, 0x0E34, 0x0E35, 0x0E36, + 0x0E37, 0x0E38, 0x0E39, 0x0E3A, 0x0E47, 0x0E48, 0x0E49, 0x0E4A, 0x0E4B, + 0x0E4C, 0x0E4D, 0x0E4E, 0x0EB1, 0x0EB4, 0x0EB5, 0x0EB6, 0x0EB7, 0x0EB8, + 0x0EB9, 0x0EBB, 0x0EBC, 0x0EC8, 0x0EC9, 0x0ECA, 0x0ECB, 0x0ECC, 0x0ECD, + 0x0F18, 0x0F19, 0x0F35, 0x0F37, 0x0F39, 0x0F3E, 0x0F3F, 0x0F71, 0x0F72, + 0x0F73, 0x0F74, 0x0F75, 0x0F76, 0x0F77, 0x0F78, 0x0F79, 0x0F7A, 0x0F7B, + 0x0F7C, 0x0F7D, 0x0F7E, 0x0F7F, 0x0F80, 0x0F81, 0x0F82, 0x0F83, 0x0F84, + 0x0F86, 0x0F87, 0x0F88, 0x0F89, 0x0F8A, 0x0F8B, 0x0F90, 0x0F91, 0x0F92, + 0x0F93, 0x0F94, 0x0F95, 0x0F97, 0x0F99, 0x0F9A, 0x0F9B, 0x0F9C, 0x0F9D, + 0x0F9E, 0x0F9F, 0x0FA0, 0x0FA1, 0x0FA2, 0x0FA3, 0x0FA4, 0x0FA5, 0x0FA6, + 0x0FA7, 0x0FA8, 0x0FA9, 0x0FAA, 0x0FAB, 0x0FAC, 0x0FAD, 0x0FB1, 0x0FB2, + 0x0FB3, 0x0FB4, 0x0FB5, 0x0FB6, 0x0FB7, 0x0FB9, 0x20D0, 0x20D1, 0x20D2, + 0x20D3, 0x20D4, 0x20D5, 0x20D6, 0x20D7, 0x20D8, 0x20D9, 0x20DA, 0x20DB, + 0x20DC, 0x20E1, 0x302A, 0x302B, 0x302C, 0x302D, 0x302E, 0x302F, 0x3099, + 0x309A ]; + +var BASE_CHARS_SINGLE = [ 0x0386, 0x038C, 0x03DA, 0x03DC, 0x03DE, 0x03E0, + 0x0559, 0x06D5, 0x093D, 0x09B2, 0x0A5E, 0x0A8D, 0x0ABD, 0x0AE0, 0x0B3D, + 0x0B9C, 0x0CDE, 0x0E30, 0x0E84, 0x0E8A, 0x0E8D, 0x0EA5, 0x0EA7, 0x0EB0, + 0x0EBD, 0x1100, 0x1109, 0x113C, 0x113E, 0x1140, 0x114C, 0x114E, 0x1150, + 0x1159, 0x1163, 0x1165, 0x1167, 0x1169, 0x1175, 0x119E, 0x11A8, 0x11AB, + 0x11BA, 0x11EB, 0x11F0, 0x11F9, 0x1F59, 0x1F5B, 0x1F5D, 0x1FBE, 0x2126, + 0x212E ]; + +var BASE_CHARS_RANGES = [ 0x0041, 0x005A, 0x0061, 0x007A, 0x00C0, 0x00D6, + 0x00D8, 0x00F6, 0x00F8, 0x00FF, 0x0100, 0x0131, 0x0134, 0x013E, 0x0141, + 0x0148, 0x014A, 0x017E, 0x0180, 0x01C3, 0x01CD, 0x01F0, 0x01F4, 0x01F5, + 0x01FA, 0x0217, 0x0250, 0x02A8, 0x02BB, 0x02C1, 0x0388, 0x038A, 0x038E, + 0x03A1, 0x03A3, 0x03CE, 0x03D0, 0x03D6, 0x03E2, 0x03F3, 0x0401, 0x040C, + 0x040E, 0x044F, 0x0451, 0x045C, 0x045E, 0x0481, 0x0490, 0x04C4, 0x04C7, + 0x04C8, 0x04CB, 0x04CC, 0x04D0, 0x04EB, 0x04EE, 0x04F5, 0x04F8, 0x04F9, + 0x0531, 0x0556, 0x0561, 0x0586, 0x05D0, 0x05EA, 0x05F0, 0x05F2, 0x0621, + 0x063A, 0x0641, 0x064A, 0x0671, 0x06B7, 0x06BA, 0x06BE, 0x06C0, 0x06CE, + 0x06D0, 0x06D3, 0x06E5, 0x06E6, 0x0905, 0x0939, 0x0958, 0x0961, 0x0985, + 0x098C, 0x098F, 0x0990, 0x0993, 0x09A8, 0x09AA, 0x09B0, 0x09B6, 0x09B9, + 0x09DC, 0x09DD, 0x09DF, 0x09E1, 0x09F0, 0x09F1, 0x0A05, 0x0A0A, 0x0A0F, + 0x0A10, 0x0A13, 0x0A28, 0x0A2A, 0x0A30, 0x0A32, 0x0A33, 0x0A35, 0x0A36, + 0x0A38, 0x0A39, 0x0A59, 0x0A5C, 0x0A72, 0x0A74, 0x0A85, 0x0A8B, 0x0A8F, + 0x0A91, 0x0A93, 0x0AA8, 0x0AAA, 0x0AB0, 0x0AB2, 0x0AB3, 0x0AB5, 0x0AB9, + 0x0B05, 0x0B0C, 0x0B0F, 0x0B10, 0x0B13, 0x0B28, 0x0B2A, 0x0B30, 0x0B32, + 0x0B33, 0x0B36, 0x0B39, 0x0B5C, 0x0B5D, 0x0B5F, 0x0B61, 0x0B85, 0x0B8A, + 0x0B8E, 0x0B90, 0x0B92, 0x0B95, 0x0B99, 0x0B9A, 0x0B9E, 0x0B9F, 0x0BA3, + 0x0BA4, 0x0BA8, 0x0BAA, 0x0BAE, 0x0BB5, 0x0BB7, 0x0BB9, 0x0C05, 0x0C0C, + 0x0C0E, 0x0C10, 0x0C12, 0x0C28, 0x0C2A, 0x0C33, 0x0C35, 0x0C39, 0x0C60, + 0x0C61, 0x0C85, 0x0C8C, 0x0C8E, 0x0C90, 0x0C92, 0x0CA8, 0x0CAA, 0x0CB3, + 0x0CB5, 0x0CB9, 0x0CE0, 0x0CE1, 0x0D05, 0x0D0C, 0x0D0E, 0x0D10, 0x0D12, + 0x0D28, 0x0D2A, 0x0D39, 0x0D60, 0x0D61, 0x0E01, 0x0E2E, 0x0E32, 0x0E33, + 0x0E40, 0x0E45, 0x0E81, 0x0E82, 0x0E87, 0x0E88, 0x0E94, 0x0E97, 0x0E99, + 0x0E9F, 0x0EA1, 0x0EA3, 0x0EAA, 0x0EAB, 0x0EAD, 0x0EAE, 0x0EB2, 0x0EB3, + 0x0EC0, 0x0EC4, 0x0F40, 0x0F47, 0x0F49, 0x0F69, 0x10A0, 0x10C5, 0x10D0, + 0x10F6, 0x1102, 0x1103, 0x1105, 0x1107, 0x110B, 0x110C, 0x110E, 0x1112, + 0x1154, 0x1155, 0x115F, 0x1161, 0x116D, 0x116E, 0x1172, 0x1173, 0x11AE, + 0x11AF, 0x11B7, 0x11B8, 0x11BC, 0x11C2, 0x1E00, 0x1E9B, 0x1EA0, 0x1EF9, + 0x1F00, 0x1F15, 0x1F18, 0x1F1D, 0x1F20, 0x1F45, 0x1F48, 0x1F4D, 0x1F50, + 0x1F57, 0x1F5F, 0x1F7D, 0x1F80, 0x1FB4, 0x1FB6, 0x1FBC, 0x1FC2, 0x1FC4, + 0x1FC6, 0x1FCC, 0x1FD0, 0x1FD3, 0x1FD6, 0x1FDB, 0x1FE0, 0x1FEC, 0x1FF2, + 0x1FF4, 0x1FF6, 0x1FFC, 0x212A, 0x212B, 0x2180, 0x2182, 0x3041, 0x3094, + 0x30A1, 0x30FA, 0x3105, 0x312C, 0xAC00, 0xD7A3 ]; + +var IDEOGRAPHIC_CHARS_SINGLE = [ 0x3007 ]; + +var IDEOGRAPHIC_CHARS_RANGES = [ 0x3021, 0x3029, 0x4E00, 0x9FA5 ]; + +var DIGIT_CHARS_RANGES = [ 0x0030, 0x0039, 0x0660, 0x0669, 0x06F0, 0x06F9, + 0x0966, 0x096F, 0x09E6, 0x09EF, 0x0A66, 0x0A6F, 0x0AE6, 0x0AEF, 0x0B66, + 0x0B6F, 0x0BE7, 0x0BEF, 0x0C66, 0x0C6F, 0x0CE6, 0x0CEF, 0x0D66, 0x0D6F, + 0x0E50, 0x0E59, 0x0ED0, 0x0ED9, 0x0F20, 0x0F29 ]; + +function CharsArray(array) { + this.array = array; +} + +CharsArray.prototype.testEach = function(namingFunction, checkFunction) { + if (this.array != null) { + this.array.forEach(function(value) { + checkFunction(namingFunction(getCharCode(value))); + }); + } +}; + +function CharRangesArray(array) { + this.array = array; +} + +CharRangesArray.prototype.testEach = function(namingFunction, checkFunction) { + if (this.array != null) { + for (var i = 0; i < this.array.length; i += 2) { + var rangeStart = getCharCode(this.array[i]); + var rangeEnd = getCharCode(this.array[i+1]); + for (var c = rangeStart; c <= rangeEnd; c++) { + checkFunction(namingFunction(c)); + } + } + } +}; + +function testCharCode(charCode, namingFunction, checkFunction) { + checkFunction(namingFunction(charCode)); +} + +var extenderChars = new CharsArray(EXTENDER_CHARS); +var combiningChars = new CharsArray(COMBINING_CHARS); +var baseCharsSingle = new CharsArray(BASE_CHARS_SINGLE); +var baseCharsRanges = new CharRangesArray(BASE_CHARS_RANGES); +var ideographicCharsSingle = new CharsArray(IDEOGRAPHIC_CHARS_SINGLE); +var ideographicCharsRanges = new CharRangesArray(IDEOGRAPHIC_CHARS_RANGES); +var digitCharsRanges = new CharRangesArray(DIGIT_CHARS_RANGES); + +// Helper function, which verifies that given custom element name is valid +function checkValidName(name) { + var doc = newHTMLDocument(); + try { + doc.registerElement(name); + } catch (e) { + assert_unreached('The custom element name \'' + name + + '\' should be registered without errors'); + } +} + +// Helper function, which verifies that given custom element name is invalid +function checkInvalidName(name) { + var doc = newHTMLDocument(); + assert_throws('SyntaxError', function() { + doc.registerElement(name); + }, 'Registering invalid custom element name \'' + name + '\' should fail'); +} + +// Helper function to extract character code from given object +// expected input: either charater code or one character long string. +function getCharCode(c) { + if (typeof(c) === 'string') { + assert_equals(1, c.length, 'Error in test: input string should be one character long'); + c = c.charCodeAt(0); + } + assert_equals('number', typeof(c), 'Error in test: unexpected type for charater code'); + return c; +} + +var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'; +var SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; + +function newHTMLDocument() { + return document.implementation.createHTMLDocument('Test Document'); +} + +// Creates new iframe and loads given url into it. +// Returns reference to created iframe. +function newIFrame(url) { + assert_not_equals(url, null, 'argument url should not be null'); + var iframe = document.createElement('iframe'); + iframe.src = url; + document.body.appendChild(iframe); + return iframe; +} + +// Creates new iframe and loads given url into it. +// Function f is bound to the iframe's onload event. +// Function f receives iframe's contentDocument as argument. +// The iframe is disposed after function f is executed. +function testInIFrame(url, f, testName, testProps) { + var t = async_test(testName, testProps); + t.step(function() { + var iframe = newIFrame(url); + iframe.onload = t.step_func(function() { + try { + f(iframe.contentDocument); + t.done(); + } finally { + iframe.remove(); + } + }); + }); +} + +// Helper function to create a prototype for custom element +// with predefined callbacks +function newHTMLElementPrototype() { + return newCustomElementPrototype(HTMLElement.prototype); +} + +// Helper function to create a prototype for custom element +// with predefined callbacks +function newCustomElementPrototype(parent) { + var proto = Object.create(parent); + proto.createdCallbackThis = null; + proto.createdCallbackCalledCounter = 0; + + proto.attachedCallbackThis = null; + proto.attachedCallbackCalledCounter = 0; + + proto.detachedCallbackThis = null; + proto.detachedCallbackCalledCounter = 0; + + proto.attributeChangedCallbackThis = null; + proto.attributeChangedCallbackCalledCounter = 0; + proto.attributeChangedCallbackArgs = null; + + proto.createdCallback = function() { + proto.createdCallbackThis = this; + proto.createdCallbackCalledCounter++; + }; + proto.attachedCallback = function() { + proto.attachedCallbackThis = this; + proto.attachedCallbackCalledCounter++; + }; + proto.detachedCallback = function() { + proto.detachedCallbackThis = this; + proto.detachedCallbackCalledCounter++; + }; + proto.attributeChangedCallback = function(arg1, arg2, arg3) { + proto.attributeChangedCallbackThis = this; + proto.attributeChangedCallbackCalledCounter++; + proto.attributeChangedCallbackArgs = [arg1, arg2, arg3]; + }; + return proto; +} |