blob: e6b3842870b1a0607f3fa1db488604b4d26bccd6 [file] [log] [blame]
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
<body>
<script>
'use strict';
// Looks up the preceeding element (which should be a template
// element) and creates a Promise test. The test name is taken from
// the template's data-test attribute.
//
// The content of the template is loaded into an iframe. On load, f
// is passed the frame's content window to run assertions.
function test_with_content(f) {
let t = document.currentScript.previousElementSibling;
test_with_window(f, t.dataset.test, t.innerHTML);
}
// Searches the document for an iframe with the specified content window.
function findFrameWithWindow(w) {
return Array.prototype.find.call(document.querySelectorAll('iframe'), (f) => {
return f.contentWindow === w;
});
}
test_with_window((w) => {
assert_equals(findFrameWithWindow(w).contentWindow, w,
'should find the frame with this window');
assert_equals(findFrameWithWindow(window), undefined,
'should return undefined if there is no such frame');
}, 'sanity check the findFrameWithWindow function');
</script>
<template data-test="the parser synchronously creates elements">
<script>
'use strict';
window.invocations = [];
customElements.define('a-a', class extends HTMLElement {
constructor() {
super();
invocations.push('constructor');
}
static get observedAttributes() { return ['x']; }
attributeChangedCallback(name, oldValue, newValue, nsuri) {
invocations.push(`${name}="${newValue}"`);
}
connectedCallback() {
invocations.push('connected');
}
});
</script>
<a-a x="y">
<script>
'use strict';
invocations.push('script');
</script>
</a-a>
</template>
<script>
'use strict';
test_with_content((w) => {
assert_array_equals(w.invocations,
['constructor', 'x="y"', 'connected', 'script']);
});
</script>
<template data-test="element creation failure produces unknown element">
<script>
'use strict';
customElements.define('a-a', class extends HTMLElement {
constructor() {
super();
// Returning this different, in-use element causes element
// creation to fail in
// https://dom.spec.whatwg.org/#concept-create-element steps
// 6.4-9, eg: "If result has children then then throw a
// NotSupportedError."
return document.documentElement;
}
});
</script>
<a-a>
</template>
<script>
'use strict';
test_with_content((w) => {
let e = w.document.querySelector('a-a');
assert_true(e.matches(':not(:defined)'));
assert_equals(Object.getPrototypeOf(e), w.HTMLUnknownElement.prototype);
});
</script>
<template data-test="modify tree during creation">
<script>
'use strict';
customElements.define('a-a', class extends HTMLElement {
constructor() {
super();
document.querySelector('b').remove();
}
});
</script>
<b>
<a-a>
</b>
</template>
<script>
'use strict';
test_with_content((w) => {
assert_equals(w.document.querySelectorAll('b').length, 0);
});
</script>
<template data-test="destructive writes are blocked during construction">
<script>
// Custom element constructors do not set the insertion point, which
// makes document.write() "destructive." However they increment the
// ignore-destructive-writes counter, which blocks document.write.
// https://html.spec.whatwg.org/#create-an-element-for-the-token
// https://github.com/whatwg/html/issues/1630
// https://html.spec.whatwg.org/#document.write()
'use strict';
window.invocations = [];
customElements.define('a-a', class extends HTMLElement {
constructor() {
super();
invocations.push('constructor');
document.write(
`<script>'use strict'; invocations.push('written');</scr${'i'}pt>`);
}
connectedCallback() {
invocations.push('connected');
}
});
</script>
<a-a>
<script>
'use strict';
invocations.push('parsed');
</script>
</template>
<script>
'use strict';
test_with_content((w) => {
assert_array_equals(
w.invocations,
['constructor', 'connected', 'parsed'],
'the destructive document.write content should have been ignored');
});
</script>
<template data-test="non-destructive writes are not blocked">
<script>
// Script running sets the insertion point, which makes makes
// document.write() "non-destructive." Custom elements do not block
// non-destructive writes.
// https://html.spec.whatwg.org/#create-an-element-for-the-token
// https://html.spec.whatwg.org/#document.write()
'use strict';
window.invocations = [];
customElements.define('a-a', class extends HTMLElement {
constructor() {
super();
invocations.push('constructor');
document.write(
`<script>'use strict'; invocations.push('written');</scr${'i'}pt>`);
}
connectedCallback() {
invocations.push('connected');
}
});
document.write('<a-a>');
invocations.push('post write');
</script>
<script>
'use strict';
invocations.push('parsed');
</script>
</template>
<script>
'use strict';
test_with_content((w) => {
assert_array_equals(
w.invocations,
['constructor', 'connected', 'post write', 'written', 'parsed'],
'the non-destructive document.write content should have been inserted');
});
</script>
<template data-test="innerHTML is not blocked by custom element constructors">
<script>
'use strict';
window.invocations = [];
customElements.define('a-a', class extends HTMLElement {
constructor() {
super();
invocations.push(`construct ${this.id}`);
if (!this.id) {
// If the ID attribute is not set, this was created
// synchronously by the parser. Adding children at this point
// would cause creation to fail, so embiggen the previous
// element instead.
document.querySelector('span').innerHTML = `<a-a id="r">`;
}
}
connectedCallback() {
invocations.push(`connected ${this.parentNode.localName}/${this.id}`);
}
});
</script>
<span></span>
<a-a id="q"></a-a>
<script>
'use strict';
invocations.push('parsed');
</script>
</template>
<script>
'use strict';
test_with_content((w) => {
assert_array_equals(
w.invocations,
['construct ', 'construct r', 'connected span/r', 'connected body/q',
'parsed'],
'custom element constructors should not block innerHTML');
});
</script>
<template data-test="parsing without a browsing context should not create custom elements">
<body>
<script>
'use strict';
let f = parent.findFrameWithWindow(window);
f.invocations = [];
customElements.define('a-a', class extends HTMLElement {
constructor() {
super();
f.invocations.push(this);
}
});
</script>
<a-a></a-a>
<script>
f.detached = document.implementation.createHTMLDocument();
f.detached.documentElement.appendChild(document.body);
</script>
<a-a></a-a>
</template>
<script>
'use strict';
test_with_content((w) => {
let f = findFrameWithWindow(w);
assert_array_equals(f.invocations,
[f.detached.querySelector('a-a:first-of-type')],
'one element should have been constructed');
assert_true(f.invocations[0].matches(':defined'),
'the element should have been created successfully');
let elements = f.detached.querySelectorAll('a-a');
console.log(f.invocations[0].parentNode);
assert_equals(elements.length, 2,
'two elements should have been created');
assert_equals(Object.getPrototypeOf(elements[1]), w.HTMLElement.prototype,
'the second element should be un-upgraded, not failed');
});
</script>