Refactoring to avoid recursion issues

This commit is contained in:
Julien Lengrand-Lambert
2017-12-12 15:22:28 +01:00
parent 25488152e0
commit 40bc11b92b
190 changed files with 86 additions and 20816 deletions

View File

@@ -11,4 +11,9 @@ This repo was created to support [a workshop](https://www.meetup.com/Coffee-Code
* Testing component
* Shadow DOM
* Stencil ?
* Naming - , or uppercase?
* Naming - , or uppercase?
* Using templates
* WebServer
* Polymer tutorial
* Finish PPT
* https://developer.mozilla.org/en-US/docs/Web/Web_Components/Custom_Elements/Custom_Elements_with_Classes

View File

@@ -1,36 +0,0 @@
{
"name": "custom-elements",
"main": "custom-elements.min.js",
"homepage": "http://webcomponents.org",
"authors": [
"The Polymer Authors"
],
"repository": {
"type": "git",
"url": "https://github.com/webcomponents/custom-elements.git"
},
"keywords": [
"webcomponents"
],
"license": "BSD-3-Clause",
"ignore": [],
"devDependencies": {
"web-component-tester": "^6.0.0",
"webcomponents-platform": "webcomponents/webcomponents-platform#^1.0.0",
"es6-promise": "stefanpenner/es6-promise#^4.0.0",
"html-imports": "webcomponents/html-imports#^1.0.0",
"template": "webcomponents/template#^1.2.0",
"shadydom": "webcomponents/shadydom#^1.0.6"
},
"version": "1.0.6",
"_release": "1.0.6",
"_resolution": {
"type": "version",
"tag": "v1.0.6",
"commit": "21967541c1a0b2e3b20d2666ad5c6f25ee4a296e"
},
"_source": "https://github.com/webcomponents/custom-elements.git",
"_target": "^1.0.6",
"_originalSource": "webcomponents/custom-elements",
"_direct": true
}

View File

@@ -1,4 +0,0 @@
node_modules
bower_components
npm-debug.log*

View File

@@ -1,23 +0,0 @@
language: node_js
node_js: 6
dist: trusty
addons:
firefox: latest
apt:
sources:
- google-chrome
packages:
- google-chrome-stable
before_script:
- export PATH=$PWD/node_modules/.bin:$PATH
- npm run bower-install
script:
- npm run build
- xvfb-run wct
- if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then wct -s 'windows 10/microsoftedge@14'
-s 'windows 8.1/internet explorer@11' -s 'os x 10.11/safari@10' -s 'os x 10.11/safari@9';
fi
env:
global:
- secure: NDKE0TviiIuKwDJd+eOmd+T42K2en8HbuBvLpfMQb+7564dqRgy+a539IhY9z0CTdMZ7Z0cGcSVwXCQz4SmtA36ARNy4EWlldNSMYU8dYbNyAK0JF4KH6kTrzljB4SPIxb5psN8QY/mD4MjN3LhwzWVF7giumeAUnPkuv+lGHOVVZ+budPIEDemQXc22IZUCYbzzaBpFLf/vdkeyA3ieLcyMnCGbP29zgB1swa1vniTqdcgzuNWwZ46lW5ZQEMj4NPHYUXjiq9rUa/zcaAQoC75YNSVqAxduXLfKvuqZTEWO1mosOV2xpWLBvT981i+UP0t2dAIX2dFgLKKUzlcOwq2Yi5SXcrG+5TJFvuF/+uhfg5Sdu9uBjeql0Vx0NlCydeNBYltRKAWDDj2ePzs2/69Ori54QXQLu3fcC2RBe6te6QMVg91F4PiZJPhkHzw5qgAa4k5X8kpC4JzT6aigPyUUkBd3oYe8TnAsjfp6Z/GbSfJI7fdgqtDnO8lLvE4aF9f6l1rDGsyWL0lwEbAZvWdzqY/Bzhq46BcjrEBSZCh98up15+d6w6IuFgFqPPwXRloOQUiW8IokCExgv3RDilP0CxPWZdf4yHc2nTn7HhvVrzI54UG7eVkh7wywseRFVmIL/Cr5kciWjc200zKwDT2kTZ6EuRMAnTXYbbDZQOU=
- secure: OEht4eHm8qMKz+5Q9d7tKDOKH1YOCMyYbxdHzudtbWlNX5NHOCpHOfMOKYTM4WGBWU+GPCqgjNAnf4AfBKUWuLyxM0QB08d9k+ggHLMN+2Fh2h6KqInatrxlIetgGbRqgPFpACTRoEovdH+IVo8vaHn9SW6CyT3L84eRgvbIqlfgZrSpgaxrS3ChO0AYCS4yhN1ui3SeqThUtq/kFYiwB2QYvw5DvFbyQ7YAbXyt/+9tcEOyNt3WGKyFZWhYBQ8gmo5S+CnZnny/DXle6Wx3h53pMhkDjeeRWuNufdvxOumy1WZhYQ46zc3eu6JWTI/3aqKxeABdPIqfPQ19mhWHDgAdXiyLkTQIiOxnnEZklSEkgeqbWfZDJ7ta6Vbl0PhOsqH/0H/7jC3XzgDAaHxqkeRL1f0QSUG0/8jfZzwHMN+AhfoVw8DOAIpQSnVVRTRWN2Y/g1ygdaaJ1Z+8/WMyheFc3/XZASA6S4h2+Jrlde6By9/j1d9kUES2qiUVUwdko5/Kxxq0rkfvo7jrx6ojNOEIuBWxyYcNOciYZKhA5VtLZ3YiD8C7DXGR5un73ktWxw0r5wX0GJgfMsdkjCYR7GEBLLGsiPDah7TVqCGWHzzBFaXq6HAKY6jce+TDp2ReFUh0h0siOKKCX1RFBMzDyK/HUZ9F7JUcPJ8Hwtze3j8=

View File

@@ -1,64 +0,0 @@
# Contributing
Want to contribute to webcomponents.js? Great!
We are more than happy to accept external contributions to the project in the form of [bug reports](../../issues) and pull requests.
## Contributor License Agreement
Before we can accept patches, there's a quick web form you need to fill out.
- If you're contributing as an individual (e.g. you own the intellectual property), fill out [this form](http://code.google.com/legal/individual-cla-v1.0.html).
- If you're contributing under a company, fill out [this form](http://code.google.com/legal/corporate-cla-v1.0.html) instead.
This CLA asserts that contributions are owned by you and that we can license all work under our [license](LICENSE).
Other projects require a similar agreement: jQuery, Firefox, Apache, Node, and many more.
[More about CLAs](https://www.google.com/search?q=Contributor%20License%20Agreement)
## Initial setup
1. Setup Gulp: `sudo npm install -g gulp`
1. Fork the project on github and pull down your copy.
> replace the {{ username }} with your username and {{ repository }} with the repository name
git clone git@github.com:{{ username }}/{{ repository }}.git
1. Test your change results in a working build.
> in the repo you've made changes to, try generating a build:
cd $REPO
npm install
gulp build
The builds will be placed into the `dist/` directory if all goes well.
1. Commit your code and make a pull request.
That's it for the one time setup. Now you're ready to make a change.
## Submitting a pull request
We iterate fast! To avoid potential merge conflicts, it's a good idea to pull from the main project before making a change and submitting a pull request. The easiest way to do this is setup a remote called `upstream` and do a pull before working on a change:
git remote add upstream git://github.com/polymer/webcomponentsjs.git
Then before making a change, do a pull from the upstream `master` branch:
git pull upstream master
To make life easier, add a "pull upstream" alias in your `.gitconfig`:
[alias]
pu = !"git fetch origin -v; git fetch upstream -v; git merge upstream/master"
That will pull in changes from your forked repo, the main (upstream) repo, and merge the two. Then it's just a matter of running `git pu` before a change and pushing to your repo:
git checkout master
git pu
# make change
git commit -a -m 'Awesome things.'
git push
Lastly, don't forget to submit the pull request.

View File

@@ -1,167 +0,0 @@
# Custom Elements v1
## Status
The polyfill should be mostly feature complete now. It supports defining
custom elements, the custom element reactions, and upgrading existing elements. It integrates with native Shadow DOM v1, and native and polyfilled HTML Imports.
The implementation could use more tests, especially around ordering of
reactions. The source references old versions of the spec.
### To do
1. Implement Node#isConnected
2. Implement built-in element extension (is=)
3. Add reaction callback ordering tests
4. Reorganize tests to be closer to spec structure
5. Performance tests
## Building & Running Tests
1. Install web-component-tester
```bash
$ npm i -g web-component-tester
```
2. Checkout the webcomponentsjs v1 branch
```bash
$ git clone https://github.com/webcomponents/webcomponentsjs.git
$ cd webcomponentsjs
$ npm i
$ gulp build
```
3. Run tests
```bash
$ wct tests/CustomElements/v1/index.html -l chrome
```
4. Bower link to use in another project
```bash
$ bower link
$ cd {your project directory}
$ bower link webcomponentsjs
```
## Implementation approach and browser support
The polyfill leans heavily on MutationObservers to drive custom element creation and reactions. This means that the polyfill requires native or polyfilled MutationObservers. The polyfill also uses Map and Set, though those would be easy to polyfill or remove.
The implementation should work without additional polyfills in IE 11, Edge, Safari >6, Firefox, Chrome, Android Browser >4.4, and Opera (not Mini).
IE 10 should be supported with the Mutation Observer polyfill.
This branch does not pass the CI tests yet, but it passes locally in Chrome 52, Safari 9.1, Safari Technical Preview, and Firefox 47.
Because MutationObservers are used to create custom element instances and react to document structure and attribute changes, all reactions are asynchronous while in the specs, some are synchronous. This is by design, since some of the synchronous reactions are impossible to polyfill. We consider it more important that reactions have the same relative timing to each other as the spec, for example attributeChangedCallback happens after connectedCallback.
### Implementing the "Constructor-call Trick"
The HTMLElement constructor is now specified to return a different object than `this`, so that the parser and upgrades can call the constructor on elements that have already been allocated. JavaScript allows this, but some compilers, such as TypeScript and Closure currently don't.
The HTMLElement constructor is also specified to look up the tag name associated with a constructor by using `new.target`. `new.target` isn't available in ES5, is not polyfillable, but `this.constructor` behaves similarly for most ES5 class patterns, including the output of Babel, TypeScript and Closure, so the polyfill uses that to look up tag names.
`new.target` isn't even feature detectable, since it's a syntax error in ES5. Because of this, the polyfill can't check `new.target` first and fallback to `this.constructor`. This also means that ES5-style constructors can't conditionally make a "super" call to the HTMLElement constructor (with `Reflect.construct`) in non-ES6 environments to be compatible with native Custom Elements.
To allow for elements that work in both ES5 and ES6 environments, we provide a shim to be used in browsers that have native Custom Elements v1 support, that overrides the HTMLElement constructor and calls `Reflect.construct` with either the value of `new.target` or `this.constructor`. This shim can only be executed in ES6 environments that support `new.target`, and so should be conditionally loaded. The shim and the polyfill should not be loaded at the same time.
## Building
The Custom Elements V1 polyfill does not use the same module or build system as
the other webcomponentsjs polyfills, and its build is not integrated into the default build tasks yet.
To build run:
gulp CustomElementsV1
This creates a CustomElementsV1.min.js file in dist/
## Custom Elements in the DOM Spec
### 4.2.3 Mutation Algorithms
https://dom.spec.whatwg.org/#mutation-algorithms
* insert 6.5.2.1: call connectedCallback
* insert 6.5.2.2: upgrade
* remove 14: call disconnectedCallback
* remove 15.2: call disconnectedCallback
### 4.4 Node
https://dom.spec.whatwg.org/#concept-node-clone
* clone 2.1: performs create-element
* clone 2.2: append attributes
isConnected looks like it's Custom Elements related, but it's actually part of Shadow DOM. We might want to implement it for on non-native Shadow DOM environments, since we already watch for all connections and disconnections.
### 4.5 Document
https://dom.spec.whatwg.org/#interface-document
* createElement and createElementNS take ElementCreationOptions
https://dom.spec.whatwg.org/#dom-document-createelement
* createElement 3, 4, 5, 7
* createElementNS 2, 3, 4, 5
### 4.9 Element
https://dom.spec.whatwg.org/#concept-element-custom-element-state
https://dom.spec.whatwg.org/#concept-create-element
* create an element 2, 3, 4, 5, 6
https://dom.spec.whatwg.org/#concept-element-attributes-change
* change an attribute 2
https://dom.spec.whatwg.org/#concept-element-attributes-append
* append an attribute 2
https://dom.spec.whatwg.org/#concept-element-attributes-remove
* remove an attribute 2
https://dom.spec.whatwg.org/#concept-element-attributes-replace
* replace an attribute 2
https://dom.spec.whatwg.org/#dom-element-attachshadow
* attachShadow 2 (nothing to implement)
## Custom Elements in the HTML Spec
### 3.2.3 HTML element constructors
https://html.spec.whatwg.org/multipage/dom.html#html-element-constructors
* HTML element constructors 1-9
### 4.13 Custom Elements
https://html.spec.whatwg.org/multipage/scripting.html#custom-elements
https://html.spec.whatwg.org/multipage/scripting.html#valid-custom-element-name
https://html.spec.whatwg.org/multipage/scripting.html#customelementsregistry
### 7.3 Window
https://html.spec.whatwg.org/multipage/browsers.html#window
* Window#customElements
https://html.spec.whatwg.org/multipage/syntax.html#create-an-element-for-the-token
* create an element for a token 3, 4, 5, 6, 7, 8, 9

View File

@@ -1,19 +0,0 @@
# License
Everything in this repo is BSD style license unless otherwise specified.
Copyright (c) 2015 The Polymer Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,211 +0,0 @@
# Custom Elements (v1) Polyfill [![Build Status](https://travis-ci.org/webcomponents/custom-elements.svg?branch=master)](https://travis-ci.org/webcomponents/custom-elements)
A polyfill for the [custom elements](https://html.spec.whatwg.org/multipage/scripting.html#custom-elements)
v1 spec.
## Using
Include `custom-elements.min.js` at the beginning of your page, *before* any code that
manipulates the DOM:
```html
<script src="custom-elements.min.js"></script>
```
## Developing
1. Install and build
```
npm install
npm run build
```
(Or, `npm i && gulp`, if [gulp](https://github.com/gulpjs/gulp) is installed globally.)
1. Test
```
npm run test
```
(Or, [`wct`](https://github.com/Polymer/web-component-tester), if installed
globally.)
## Custom element reactions in the DOM and HTML specs
API which might trigger custom element reactions in the [DOM](https://dom.spec.whatwg.org/)
and [HTML](https://html.spec.whatwg.org/) specifications are marked with the
[`CEReactions` extended attribute](https://html.spec.whatwg.org/multipage/scripting.html#cereactions).
## Known Bugs and Limitations
- `adoptedCallback` is not supported.
- Changing an attribute of a customizable (but uncustomized) element will not
cause that element to upgrade.
- Only DOM API is patched. Notably, this excludes API from the HTML spec marked
with the `CEReactions` extended attribute.
- Unpatched API from the DOM spec:
- Setters on `Element` for `id`, `className`, and `slot`.
- `DOMTokenList` (`element.classList`)
- `NamedNodeMap` (`element.attributes`)
- `Attr` (`element.attributes.getNamedItem('attr-name')`)
- The [custom element reactions stack](https://html.spec.whatwg.org/multipage/scripting.html#custom-element-reactions-stack)
is not implemented.
- Typically, DOM operations patched in this polyfill gather the list of
elements to which a given callback would apply and then iterate that list,
calling the callback on each element. This mechanism breaks down if an
element's callback performs another DOM operation that manipulates an area
of the tree that was captured in the outer operation's list of elements.
When this happens, the callbacks from the inner DOM operation will be called
*before* those of the outer DOM operation (typically, depending on the patch
implementation), as opposed to a spec-compliant implementation where the
callbacks are always run in the order they were inserted into each
particular element's reaction queue.
- Custom elements created by the UA's parser are customized as if they were
upgraded, rather than constructed.
- These elements are only learned about *after* they have been constructed,
and typically after their descendants have been constructed. When these
elements are constructed, their children are visible and editable *even
though they would not yet exist and manipulating them would throw in a
spec-compliant implementation of custom elements!*
- The [requirements for custom element constructors](https://html.spec.whatwg.org/multipage/scripting.html#custom-element-conformance)
are not enforced.
- These requirements are not generally enforcable in user script because of
the ability to use the `new` operator on a custom element constructor. This
means there is no way to know when a call to a constructor has begun or
finished.
- Methods of the `ParentNode` and `ChildNode` interfaces do not support
`DocumentFragment`s as arguments.
- Your custom element constructor's prototype *must* have a property named
`constructor` which is that constructor.
- By default, for every constructable function `F`, `F.prototype.constructor === F`.
If you replace the prototype of your constructor `F`, you must make sure
that `F.prototype.constructor === F` remains true. Otherwise, the polyfill
will not be able to create or upgrade your custom elements.
- The [`:defined` CSS pseudo-class](https://html.spec.whatwg.org/multipage/semantics-other.html#pseudo-classes)
is not supported.
### ES5 vs ES2015
The custom elements v1 spec is not compatible with ES5 style classes. This means
ES2015 code compiled to ES5 will not work with a native implementation of Custom
Elements.[0] While it's possible to force the custom elements polyfill to be
used to workaround this issue (by setting (`customElements.forcePolyfill = true;`
before loading the polyfill), you will not be using the UA's native
implementation in that case.
Since this is not ideal, we've provided an alternative:
[native-shim.js](https://github.com/webcomponents/custom-elements/blob/master/src/native-shim.js).
Loading this shim minimally augments the native implementation to be compatible
with ES5 code. We are also working on some future refinements to this approach
that will improve the implementation and automatically detect if it's needed.
[0] The spec requires that an element call the `HTMLElement` constructor.
Typically an ES5 style class would do something like `HTMLElement.call(this)` to
emulate `super()`. However, `HTMLElement` *must* be called as a constructor and
not as a plain function, i.e. with `Reflect.construct(HTMLElement, [], MyCEConstructor)`,
or it will throw.
### Parser-created elements in the main document
By default, the polyfill uses a `MutationObserver` to learn about and upgrade
elements in the main document as they are parsed. This `MutationObserver` is
attached to `document` synchronously when the script is run.
- If you attach a `MutationObserver` earlier before loading the polyfill, that
mutation observer will not see upgraded custom elements.
- If you move a node with descendants that have not yet been inserted by the
parser out of the main document, those nodes will not be noticed or upgraded
(until another action would trigger an upgrade).
Note: Using `polyfillWrapFlushCallback` disconnects this `MutationObserver`.
### `customElements.polyfillWrapFlushCallback`
tl;dr: The polyfill gets slower as the size of your page and number of custom
element definitons increases. You can use `polyfillWrapFlushCallback` to prevent
redundant work.
To avoid a potential memory leak, the polyfill does not maintain a list of upgrade
candidates. This means that calling `customElements.define` causes a synchronous,
full-document walk to search for elements with `localName`s matching the new
definition. Given that this operation is potentially expensive and, if your page
loads many custom element definitions before using any of them, highly redundant,
an extra method is added to the `CustomElementRegistry` prototype -
`polyfillWrapFlushCallback`.
`polyfillWrapFlushCallback` allows you to block the synchronous, full-document
upgrade attempts made when calling `define` and perform them later. Call
`polyfillWrapFlushCallback` with a function; the next time `customElements.define`
is called and a full-document upgrade would happen, your function will be called
instead. The only argument to your function is *another* function which, when
called, will run the full-document upgrade attempt.
For example, if you wanted to delay upgrades until the document's ready state
was `'complete'`, you could use the following:
```javascript
customElements.polyfillWrapFlushCallback(function(flush) {
if (document.readyState === 'complete') {
// If the document is already complete, flush synchronously.
flush();
} else {
// Otherwise, wait until it is complete.
document.addEventListener('readystatechange', function() {
if (document.readyState === 'complete') {
flush();
}
});
}
});
```
Once your wrapper function is called (because the polyfill wants to upgrade the
document), it will not be called again until you have triggered the
full-document upgrade attempt. If multiple definitions are registered before you
trigger upgrades, all of those definitions will apply when you trigger upgrades -
don't call the provided function multiple times.
Promises returned by `customElements.whenDefined` will not resolve until a
full-document upgrade attempt has been performed *after* the given local name
has been defined.
```javascript
let flush;
customElements.polyfillWrapFlushCallback(f => flush = f);
const p = customElements.whenDefined('c-e', () => console.log('c-e defined'));
customElements.define('c-e', class extends HTMLElement {});
// `p` is not yet resolved; `flush` is now a function.
flush(); // Resolves `p`.
```
You can't remove a callback given to `polyfillWrapFlushCallback`. If the
condition your callback was intended to wait on is no longer important, your
callback should call the given function synchronously. (See the
`document.readyState` example above.)
**Calling `polyfillWrapFlushCallback` disconnects the `MutationObserver` watching
the main document.** This means that you must delay until at least
`document.readyState !== 'loading'` to be sure that all elements in the main
document are found (subject to exceptions mentioned in the section above).
You can call `polyfillWrapFlushCallback` multiple times, each function given
will automatically wrap and delay any previous wrappers:
```javascript
customElements.polyfillWrapFlushCallback(function(flush) {
console.log('added first');
flush();
});
customElements.polyfillWrapFlushCallback(function(flush) {
console.log('added second');
setTimeout(() => flush(), 1000);
});
customElements.define('c-e', class extends HTMLElement {});
// 'added second'
// ~1s delay
// 'added first'
// The document is walked to attempt upgrades.
```

View File

@@ -1,25 +0,0 @@
{
"name": "custom-elements",
"main": "custom-elements.min.js",
"homepage": "http://webcomponents.org",
"authors": [
"The Polymer Authors"
],
"repository": {
"type": "git",
"url": "https://github.com/webcomponents/custom-elements.git"
},
"keywords": [
"webcomponents"
],
"license": "BSD-3-Clause",
"ignore": [],
"devDependencies": {
"web-component-tester": "^6.0.0",
"webcomponents-platform": "webcomponents/webcomponents-platform#^1.0.0",
"es6-promise": "stefanpenner/es6-promise#^4.0.0",
"html-imports": "webcomponents/html-imports#^1.0.0",
"template": "webcomponents/template#^1.2.0",
"shadydom": "webcomponents/shadydom#^1.0.6"
}
}

View File

@@ -1,38 +0,0 @@
(function(){
'use strict';var h=new function(){};var aa=new Set("annotation-xml color-profile font-face font-face-src font-face-uri font-face-format font-face-name missing-glyph".split(" "));function n(b){var a=aa.has(b);b=/^[a-z][.0-9_a-z]*-[\-.0-9_a-z]*$/.test(b);return!a&&b}function p(b){var a=b.isConnected;if(void 0!==a)return a;for(;b&&!(b.__CE_isImportDocument||b instanceof Document);)b=b.parentNode||(window.ShadowRoot&&b instanceof ShadowRoot?b.host:void 0);return!(!b||!(b.__CE_isImportDocument||b instanceof Document))}
function q(b,a){for(;a&&a!==b&&!a.nextSibling;)a=a.parentNode;return a&&a!==b?a.nextSibling:null}
function t(b,a,c){c=c?c:new Set;for(var d=b;d;){if(d.nodeType===Node.ELEMENT_NODE){var e=d;a(e);var f=e.localName;if("link"===f&&"import"===e.getAttribute("rel")){d=e.import;if(d instanceof Node&&!c.has(d))for(c.add(d),d=d.firstChild;d;d=d.nextSibling)t(d,a,c);d=q(b,e);continue}else if("template"===f){d=q(b,e);continue}if(e=e.__CE_shadowRoot)for(e=e.firstChild;e;e=e.nextSibling)t(e,a,c)}d=d.firstChild?d.firstChild:q(b,d)}}function u(b,a,c){b[a]=c};function v(){this.a=new Map;this.o=new Map;this.f=[];this.b=!1}function ba(b,a,c){b.a.set(a,c);b.o.set(c.constructor,c)}function w(b,a){b.b=!0;b.f.push(a)}function x(b,a){b.b&&t(a,function(a){return y(b,a)})}function y(b,a){if(b.b&&!a.__CE_patched){a.__CE_patched=!0;for(var c=0;c<b.f.length;c++)b.f[c](a)}}function z(b,a){var c=[];t(a,function(b){return c.push(b)});for(a=0;a<c.length;a++){var d=c[a];1===d.__CE_state?b.connectedCallback(d):A(b,d)}}
function B(b,a){var c=[];t(a,function(b){return c.push(b)});for(a=0;a<c.length;a++){var d=c[a];1===d.__CE_state&&b.disconnectedCallback(d)}}
function C(b,a,c){c=c?c:{};var d=c.w||new Set,e=c.s||function(a){return A(b,a)},f=[];t(a,function(a){if("link"===a.localName&&"import"===a.getAttribute("rel")){var c=a.import;c instanceof Node&&(c.__CE_isImportDocument=!0,c.__CE_hasRegistry=!0);c&&"complete"===c.readyState?c.__CE_documentLoadHandled=!0:a.addEventListener("load",function(){var c=a.import;if(!c.__CE_documentLoadHandled){c.__CE_documentLoadHandled=!0;var f=new Set(d);f.delete(c);C(b,c,{w:f,s:e})}})}else f.push(a)},d);if(b.b)for(a=0;a<
f.length;a++)y(b,f[a]);for(a=0;a<f.length;a++)e(f[a])}
function A(b,a){if(void 0===a.__CE_state){var c=a.ownerDocument;if(c.defaultView||c.__CE_isImportDocument&&c.__CE_hasRegistry)if(c=b.a.get(a.localName)){c.constructionStack.push(a);var d=c.constructor;try{try{if(new d!==a)throw Error("The custom element constructor did not produce the element being upgraded.");}finally{c.constructionStack.pop()}}catch(m){throw a.__CE_state=2,m;}a.__CE_state=1;a.__CE_definition=c;if(c.attributeChangedCallback)for(c=c.observedAttributes,d=0;d<c.length;d++){var e=c[d],
f=a.getAttribute(e);null!==f&&b.attributeChangedCallback(a,e,null,f,null)}p(a)&&b.connectedCallback(a)}}}v.prototype.connectedCallback=function(b){var a=b.__CE_definition;a.connectedCallback&&a.connectedCallback.call(b)};v.prototype.disconnectedCallback=function(b){var a=b.__CE_definition;a.disconnectedCallback&&a.disconnectedCallback.call(b)};
v.prototype.attributeChangedCallback=function(b,a,c,d,e){var f=b.__CE_definition;f.attributeChangedCallback&&-1<f.observedAttributes.indexOf(a)&&f.attributeChangedCallback.call(b,a,c,d,e)};function D(b,a){this.c=b;this.a=a;this.b=void 0;C(this.c,this.a);"loading"===this.a.readyState&&(this.b=new MutationObserver(this.f.bind(this)),this.b.observe(this.a,{childList:!0,subtree:!0}))}function E(b){b.b&&b.b.disconnect()}D.prototype.f=function(b){var a=this.a.readyState;"interactive"!==a&&"complete"!==a||E(this);for(a=0;a<b.length;a++)for(var c=b[a].addedNodes,d=0;d<c.length;d++)C(this.c,c[d])};function ca(){var b=this;this.b=this.a=void 0;this.f=new Promise(function(a){b.b=a;b.a&&a(b.a)})}function F(b){if(b.a)throw Error("Already resolved.");b.a=void 0;b.b&&b.b(void 0)};function G(b){this.i=!1;this.c=b;this.m=new Map;this.j=function(b){return b()};this.g=!1;this.l=[];this.u=new D(b,document)}
G.prototype.define=function(b,a){var c=this;if(!(a instanceof Function))throw new TypeError("Custom element constructors must be functions.");if(!n(b))throw new SyntaxError("The element name '"+b+"' is not valid.");if(this.c.a.get(b))throw Error("A custom element with name '"+b+"' has already been defined.");if(this.i)throw Error("A custom element is already being defined.");this.i=!0;var d,e,f,m,l;try{var g=function(b){var a=k[b];if(void 0!==a&&!(a instanceof Function))throw Error("The '"+b+"' callback must be a function.");
return a},k=a.prototype;if(!(k instanceof Object))throw new TypeError("The custom element constructor's prototype is not an object.");d=g("connectedCallback");e=g("disconnectedCallback");f=g("adoptedCallback");m=g("attributeChangedCallback");l=a.observedAttributes||[]}catch(r){return}finally{this.i=!1}a={localName:b,constructor:a,connectedCallback:d,disconnectedCallback:e,adoptedCallback:f,attributeChangedCallback:m,observedAttributes:l,constructionStack:[]};ba(this.c,b,a);this.l.push(a);this.g||
(this.g=!0,this.j(function(){return da(c)}))};function da(b){if(!1!==b.g){b.g=!1;for(var a=b.l,c=[],d=new Map,e=0;e<a.length;e++)d.set(a[e].localName,[]);C(b.c,document,{s:function(a){if(void 0===a.__CE_state){var e=a.localName,f=d.get(e);f?f.push(a):b.c.a.get(e)&&c.push(a)}}});for(e=0;e<c.length;e++)A(b.c,c[e]);for(;0<a.length;){for(var f=a.shift(),e=f.localName,f=d.get(f.localName),m=0;m<f.length;m++)A(b.c,f[m]);(e=b.m.get(e))&&F(e)}}}G.prototype.get=function(b){if(b=this.c.a.get(b))return b.constructor};
G.prototype.whenDefined=function(b){if(!n(b))return Promise.reject(new SyntaxError("'"+b+"' is not a valid custom element name."));var a=this.m.get(b);if(a)return a.f;a=new ca;this.m.set(b,a);this.c.a.get(b)&&!this.l.some(function(a){return a.localName===b})&&F(a);return a.f};G.prototype.v=function(b){E(this.u);var a=this.j;this.j=function(c){return b(function(){return a(c)})}};window.CustomElementRegistry=G;G.prototype.define=G.prototype.define;G.prototype.get=G.prototype.get;
G.prototype.whenDefined=G.prototype.whenDefined;G.prototype.polyfillWrapFlushCallback=G.prototype.v;var H=window.Document.prototype.createElement,ea=window.Document.prototype.createElementNS,fa=window.Document.prototype.importNode,ga=window.Document.prototype.prepend,ha=window.Document.prototype.append,ia=window.DocumentFragment.prototype.prepend,ja=window.DocumentFragment.prototype.append,I=window.Node.prototype.cloneNode,J=window.Node.prototype.appendChild,K=window.Node.prototype.insertBefore,L=window.Node.prototype.removeChild,M=window.Node.prototype.replaceChild,N=Object.getOwnPropertyDescriptor(window.Node.prototype,
"textContent"),O=window.Element.prototype.attachShadow,P=Object.getOwnPropertyDescriptor(window.Element.prototype,"innerHTML"),Q=window.Element.prototype.getAttribute,R=window.Element.prototype.setAttribute,S=window.Element.prototype.removeAttribute,T=window.Element.prototype.getAttributeNS,U=window.Element.prototype.setAttributeNS,ka=window.Element.prototype.removeAttributeNS,la=window.Element.prototype.insertAdjacentElement,ma=window.Element.prototype.prepend,na=window.Element.prototype.append,
V=window.Element.prototype.before,oa=window.Element.prototype.after,pa=window.Element.prototype.replaceWith,qa=window.Element.prototype.remove,ra=window.HTMLElement,W=Object.getOwnPropertyDescriptor(window.HTMLElement.prototype,"innerHTML"),sa=window.HTMLElement.prototype.insertAdjacentElement;function ta(){var b=X;window.HTMLElement=function(){function a(){var a=this.constructor,d=b.o.get(a);if(!d)throw Error("The custom element being constructed was not registered with `customElements`.");var e=d.constructionStack;if(!e.length)return e=H.call(document,d.localName),Object.setPrototypeOf(e,a.prototype),e.__CE_state=1,e.__CE_definition=d,y(b,e),e;var d=e.length-1,f=e[d];if(f===h)throw Error("The HTMLElement constructor was either called reentrantly for this constructor or called multiple times.");
e[d]=h;Object.setPrototypeOf(f,a.prototype);y(b,f);return f}a.prototype=ra.prototype;return a}()};function Y(b,a,c){function d(a){return function(d){for(var c=[],e=0;e<arguments.length;++e)c[e-0]=arguments[e];for(var e=[],f=[],k=0;k<c.length;k++){var r=c[k];r instanceof Element&&p(r)&&f.push(r);if(r instanceof DocumentFragment)for(r=r.firstChild;r;r=r.nextSibling)e.push(r);else e.push(r)}a.apply(this,c);for(c=0;c<f.length;c++)B(b,f[c]);if(p(this))for(c=0;c<e.length;c++)f=e[c],f instanceof Element&&z(b,f)}}c.h&&(a.prepend=d(c.h));c.append&&(a.append=d(c.append))};function ua(){var b=X;u(Document.prototype,"createElement",function(a){if(this.__CE_hasRegistry){var c=b.a.get(a);if(c)return new c.constructor}a=H.call(this,a);y(b,a);return a});u(Document.prototype,"importNode",function(a,c){a=fa.call(this,a,c);this.__CE_hasRegistry?C(b,a):x(b,a);return a});u(Document.prototype,"createElementNS",function(a,c){if(this.__CE_hasRegistry&&(null===a||"http://www.w3.org/1999/xhtml"===a)){var d=b.a.get(c);if(d)return new d.constructor}a=ea.call(this,a,c);y(b,a);return a});
Y(b,Document.prototype,{h:ga,append:ha})};function va(){var b=X;function a(a,d){Object.defineProperty(a,"textContent",{enumerable:d.enumerable,configurable:!0,get:d.get,set:function(a){if(this.nodeType===Node.TEXT_NODE)d.set.call(this,a);else{var c=void 0;if(this.firstChild){var e=this.childNodes,l=e.length;if(0<l&&p(this))for(var c=Array(l),g=0;g<l;g++)c[g]=e[g]}d.set.call(this,a);if(c)for(a=0;a<c.length;a++)B(b,c[a])}}})}u(Node.prototype,"insertBefore",function(a,d){if(a instanceof DocumentFragment){var c=Array.prototype.slice.apply(a.childNodes);
a=K.call(this,a,d);if(p(this))for(d=0;d<c.length;d++)z(b,c[d]);return a}c=p(a);d=K.call(this,a,d);c&&B(b,a);p(this)&&z(b,a);return d});u(Node.prototype,"appendChild",function(a){if(a instanceof DocumentFragment){var c=Array.prototype.slice.apply(a.childNodes);a=J.call(this,a);if(p(this))for(var e=0;e<c.length;e++)z(b,c[e]);return a}c=p(a);e=J.call(this,a);c&&B(b,a);p(this)&&z(b,a);return e});u(Node.prototype,"cloneNode",function(a){a=I.call(this,a);this.ownerDocument.__CE_hasRegistry?C(b,a):x(b,a);
return a});u(Node.prototype,"removeChild",function(a){var c=p(a),e=L.call(this,a);c&&B(b,a);return e});u(Node.prototype,"replaceChild",function(a,d){if(a instanceof DocumentFragment){var e=Array.prototype.slice.apply(a.childNodes);a=M.call(this,a,d);if(p(this))for(B(b,d),d=0;d<e.length;d++)z(b,e[d]);return a}var e=p(a),c=M.call(this,a,d),m=p(this);m&&B(b,d);e&&B(b,a);m&&z(b,a);return c});N&&N.get?a(Node.prototype,N):w(b,function(b){a(b,{enumerable:!0,configurable:!0,get:function(){for(var a=[],b=
0;b<this.childNodes.length;b++)a.push(this.childNodes[b].textContent);return a.join("")},set:function(a){for(;this.firstChild;)L.call(this,this.firstChild);J.call(this,document.createTextNode(a))}})})};function wa(b){var a=Element.prototype;function c(a){return function(c){for(var d=[],e=0;e<arguments.length;++e)d[e-0]=arguments[e];for(var e=[],l=[],g=0;g<d.length;g++){var k=d[g];k instanceof Element&&p(k)&&l.push(k);if(k instanceof DocumentFragment)for(k=k.firstChild;k;k=k.nextSibling)e.push(k);else e.push(k)}a.apply(this,d);for(d=0;d<l.length;d++)B(b,l[d]);if(p(this))for(d=0;d<e.length;d++)l=e[d],l instanceof Element&&z(b,l)}}V&&(a.before=c(V));V&&(a.after=c(oa));pa&&u(a,"replaceWith",function(a){for(var d=
[],c=0;c<arguments.length;++c)d[c-0]=arguments[c];for(var c=[],m=[],l=0;l<d.length;l++){var g=d[l];g instanceof Element&&p(g)&&m.push(g);if(g instanceof DocumentFragment)for(g=g.firstChild;g;g=g.nextSibling)c.push(g);else c.push(g)}l=p(this);pa.apply(this,d);for(d=0;d<m.length;d++)B(b,m[d]);if(l)for(B(b,this),d=0;d<c.length;d++)m=c[d],m instanceof Element&&z(b,m)});qa&&u(a,"remove",function(){var a=p(this);qa.call(this);a&&B(b,this)})};function xa(){var b=X;function a(a,c){Object.defineProperty(a,"innerHTML",{enumerable:c.enumerable,configurable:!0,get:c.get,set:function(a){var d=this,e=void 0;p(this)&&(e=[],t(this,function(a){a!==d&&e.push(a)}));c.set.call(this,a);if(e)for(var f=0;f<e.length;f++){var k=e[f];1===k.__CE_state&&b.disconnectedCallback(k)}this.ownerDocument.__CE_hasRegistry?C(b,this):x(b,this);return a}})}function c(a,c){u(a,"insertAdjacentElement",function(a,d){var e=p(d);a=c.call(this,a,d);e&&B(b,d);p(a)&&z(b,d);
return a})}O&&u(Element.prototype,"attachShadow",function(a){return this.__CE_shadowRoot=a=O.call(this,a)});P&&P.get?a(Element.prototype,P):W&&W.get?a(HTMLElement.prototype,W):w(b,function(b){a(b,{enumerable:!0,configurable:!0,get:function(){return I.call(this,!0).innerHTML},set:function(a){var b="template"===this.localName,d=b?this.content:this,c=H.call(document,this.localName);for(c.innerHTML=a;0<d.childNodes.length;)L.call(d,d.childNodes[0]);for(a=b?c.content:c;0<a.childNodes.length;)J.call(d,
a.childNodes[0])}})});u(Element.prototype,"setAttribute",function(a,c){if(1!==this.__CE_state)return R.call(this,a,c);var d=Q.call(this,a);R.call(this,a,c);c=Q.call(this,a);b.attributeChangedCallback(this,a,d,c,null)});u(Element.prototype,"setAttributeNS",function(a,c,f){if(1!==this.__CE_state)return U.call(this,a,c,f);var d=T.call(this,a,c);U.call(this,a,c,f);f=T.call(this,a,c);b.attributeChangedCallback(this,c,d,f,a)});u(Element.prototype,"removeAttribute",function(a){if(1!==this.__CE_state)return S.call(this,
a);var c=Q.call(this,a);S.call(this,a);null!==c&&b.attributeChangedCallback(this,a,c,null,null)});u(Element.prototype,"removeAttributeNS",function(a,c){if(1!==this.__CE_state)return ka.call(this,a,c);var d=T.call(this,a,c);ka.call(this,a,c);var e=T.call(this,a,c);d!==e&&b.attributeChangedCallback(this,c,d,e,a)});sa?c(HTMLElement.prototype,sa):la?c(Element.prototype,la):console.warn("Custom Elements: `Element#insertAdjacentElement` was not patched.");Y(b,Element.prototype,{h:ma,append:na});wa(b)};/*
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
var Z=window.customElements;if(!Z||Z.forcePolyfill||"function"!=typeof Z.define||"function"!=typeof Z.get){var X=new v;ta();ua();Y(X,DocumentFragment.prototype,{h:ia,append:ja});va();xa();document.__CE_hasRegistry=!0;var customElements=new G(X);Object.defineProperty(window,"customElements",{configurable:!0,enumerable:!0,value:customElements})};
}).call(self);
//# sourceMappingURL=custom-elements.min.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,58 +0,0 @@
/** @type {boolean|undefined} */
CustomElementRegistry.prototype.forcePolyfill;
class AlreadyConstructedMarker {}
/**
* @enum {number}
*/
const CustomElementState = {
custom: 1,
failed: 2,
};
/**
* @typedef {{
* localName: string,
* constructor: !Function,
* connectedCallback: Function,
* disconnectedCallback: Function,
* adoptedCallback: Function,
* attributeChangedCallback: Function,
* observedAttributes: !Array<string>,
* constructionStack: !Array<!HTMLElement|!AlreadyConstructedMarker>,
* }}
*/
let CustomElementDefinition;
// These properties are defined in the closure externs so that they will not be
// renamed during minification.
// Used for both Documents and Nodes which represent documents in the HTML
// Imports polyfill.
/** @type {boolean|undefined} */
Node.prototype.__CE_hasRegistry;
/** @type {boolean|undefined} */
Node.prototype.__CE_isImportDocument;
/** @type {boolean|undefined} */
Node.prototype.__CE_documentLoadHandled;
// Apply generally to Node.
/** @type {boolean|undefined} */
Node.prototype.__CE_patched;
// Apply generally to Element.
/** @type {!CustomElementState|undefined} */
Element.prototype.__CE_state;
/** @type {!CustomElementDefinition|undefined} */
Element.prototype.__CE_definition;
/** @type {!DocumentFragment|undefined} */
Element.prototype.__CE_shadowRoot;

View File

@@ -1,51 +0,0 @@
/**
* @license
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
'use strict';
const compilerPackage = require('google-closure-compiler');
const gulp = require('gulp');
const sourcemaps = require('gulp-sourcemaps');
const rollup = require('rollup-stream');
const source = require('vinyl-source-stream');
const closureCompiler = compilerPackage.gulp();
gulp.task('default', () => {
return gulp.src('./src/**/*.js', {base: './'})
.pipe(sourcemaps.init())
.pipe(closureCompiler({
compilation_level: 'ADVANCED',
warning_level: 'VERBOSE',
language_in: 'ECMASCRIPT6_STRICT',
language_out: 'ECMASCRIPT5_STRICT',
externs: ['externs/custom-elements.js'],
dependency_mode: 'STRICT',
entry_point: ['/src/custom-elements'],
js_output_file: 'custom-elements.min.js',
output_wrapper: '(function(){\n%output%\n}).call(self);',
assume_function_wrapper: true,
new_type_inf: true,
rewrite_polyfills: false,
}))
.pipe(sourcemaps.write('/'))
.pipe(gulp.dest('./'));
});
gulp.task('debug', () => {
return rollup({
entry: './src/custom-elements.js',
format: 'iife',
sourceMap: false,
indent: true,
})
.pipe(source('custom-elements.min.js'))
.pipe(gulp.dest('./'));
});

View File

@@ -1,40 +0,0 @@
{
"name": "@webcomponents/custom-elements",
"version": "1.0.6",
"description": "HTML Custom Elements Polyfill",
"main": "custom-elements.min.js",
"directories": {
"test": "tests"
},
"repository": {
"type": "git",
"url": "https://github.com/webcomponents/custom-elements.git"
},
"author": "The Polymer Authors",
"license": "BSD-3-Clause",
"bugs": {
"url": "https://github.com/webcomponents/custom-elements/issues"
},
"scripts": {
"bower-install": "$(npm bin)/bower install",
"build": "gulp",
"test": "wct"
},
"homepage": "http://webcomponents.org",
"devDependencies": {
"@webcomponents/html-imports": "^1.0.0",
"@webcomponents/template": "^1.0.0",
"@webcomponents/webcomponents-platform": "^1.0.0",
"bower": "^1.8.0",
"es6-promise": "^4.1.0",
"google-closure-compiler": "^20170409.0.0",
"gulp": "^3.8.8",
"gulp-sourcemaps": "^1.6.0",
"rollup-stream": "^1.14.0",
"vinyl-source-stream": "^1.1.0",
"web-component-tester": "^6.0.0"
},
"publishConfig": {
"access": "public"
}
}

View File

@@ -1,10 +0,0 @@
/**
* This class exists only to work around Closure's lack of a way to describe
* singletons. It represents the 'already constructed marker' used in custom
* element construction stacks.
*
* https://html.spec.whatwg.org/#concept-already-constructed-marker
*/
class AlreadyConstructedMarker {}
export default new AlreadyConstructedMarker();

View File

@@ -1,333 +0,0 @@
import * as Utilities from './Utilities.js';
import CEState from './CustomElementState.js';
export default class CustomElementInternals {
constructor() {
/** @type {!Map<string, !CustomElementDefinition>} */
this._localNameToDefinition = new Map();
/** @type {!Map<!Function, !CustomElementDefinition>} */
this._constructorToDefinition = new Map();
/** @type {!Array<!function(!Node)>} */
this._patches = [];
/** @type {boolean} */
this._hasPatches = false;
}
/**
* @param {string} localName
* @param {!CustomElementDefinition} definition
*/
setDefinition(localName, definition) {
this._localNameToDefinition.set(localName, definition);
this._constructorToDefinition.set(definition.constructor, definition);
}
/**
* @param {string} localName
* @return {!CustomElementDefinition|undefined}
*/
localNameToDefinition(localName) {
return this._localNameToDefinition.get(localName);
}
/**
* @param {!Function} constructor
* @return {!CustomElementDefinition|undefined}
*/
constructorToDefinition(constructor) {
return this._constructorToDefinition.get(constructor);
}
/**
* @param {!function(!Node)} listener
*/
addPatch(listener) {
this._hasPatches = true;
this._patches.push(listener);
}
/**
* @param {!Node} node
*/
patchTree(node) {
if (!this._hasPatches) return;
Utilities.walkDeepDescendantElements(node, element => this.patch(element));
}
/**
* @param {!Node} node
*/
patch(node) {
if (!this._hasPatches) return;
if (node.__CE_patched) return;
node.__CE_patched = true;
for (let i = 0; i < this._patches.length; i++) {
this._patches[i](node);
}
}
/**
* @param {!Node} root
*/
connectTree(root) {
const elements = [];
Utilities.walkDeepDescendantElements(root, element => elements.push(element));
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
if (element.__CE_state === CEState.custom) {
this.connectedCallback(element);
} else {
this.upgradeElement(element);
}
}
}
/**
* @param {!Node} root
*/
disconnectTree(root) {
const elements = [];
Utilities.walkDeepDescendantElements(root, element => elements.push(element));
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
if (element.__CE_state === CEState.custom) {
this.disconnectedCallback(element);
}
}
}
/**
* Upgrades all uncustomized custom elements at and below a root node for
* which there is a definition. When custom element reaction callbacks are
* assumed to be called synchronously (which, by the current DOM / HTML spec
* definitions, they are *not*), callbacks for both elements customized
* synchronously by the parser and elements being upgraded occur in the same
* relative order.
*
* NOTE: This function, when used to simulate the construction of a tree that
* is already created but not customized (i.e. by the parser), does *not*
* prevent the element from reading the 'final' (true) state of the tree. For
* example, the element, during truly synchronous parsing / construction would
* see that it contains no children as they have not yet been inserted.
* However, this function does not modify the tree, the element will
* (incorrectly) have children. Additionally, self-modification restrictions
* for custom element constructors imposed by the DOM spec are *not* enforced.
*
*
* The following nested list shows the steps extending down from the HTML
* spec's parsing section that cause elements to be synchronously created and
* upgraded:
*
* The "in body" insertion mode:
* https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody
* - Switch on token:
* .. other cases ..
* -> Any other start tag
* - [Insert an HTML element](below) for the token.
*
* Insert an HTML element:
* https://html.spec.whatwg.org/multipage/syntax.html#insert-an-html-element
* - Insert a foreign element for the token in the HTML namespace:
* https://html.spec.whatwg.org/multipage/syntax.html#insert-a-foreign-element
* - Create an element for a token:
* https://html.spec.whatwg.org/multipage/syntax.html#create-an-element-for-the-token
* - Will execute script flag is true?
* - (Element queue pushed to the custom element reactions stack.)
* - Create an element:
* https://dom.spec.whatwg.org/#concept-create-element
* - Sync CE flag is true?
* - Constructor called.
* - Self-modification restrictions enforced.
* - Sync CE flag is false?
* - (Upgrade reaction enqueued.)
* - Attributes appended to element.
* (`attributeChangedCallback` reactions enqueued.)
* - Will execute script flag is true?
* - (Element queue popped from the custom element reactions stack.
* Reactions in the popped stack are invoked.)
* - (Element queue pushed to the custom element reactions stack.)
* - Insert the element:
* https://dom.spec.whatwg.org/#concept-node-insert
* - Shadow-including descendants are connected. During parsing
* construction, there are no shadow-*excluding* descendants.
* However, the constructor may have validly attached a shadow
* tree to itself and added descendants to that shadow tree.
* (`connectedCallback` reactions enqueued.)
* - (Element queue popped from the custom element reactions stack.
* Reactions in the popped stack are invoked.)
*
* @param {!Node} root
* @param {{
* visitedImports: (!Set<!Node>|undefined),
* upgrade: (!function(!Element)|undefined),
* }=} options
*/
patchAndUpgradeTree(root, options = {}) {
const visitedImports = options.visitedImports || new Set();
const upgrade = options.upgrade || (element => this.upgradeElement(element));
const elements = [];
const gatherElements = element => {
if (element.localName === 'link' && element.getAttribute('rel') === 'import') {
// The HTML Imports polyfill sets a descendant element of the link to
// the `import` property, specifically this is *not* a Document.
const importNode = /** @type {?Node} */ (element.import);
if (importNode instanceof Node) {
importNode.__CE_isImportDocument = true;
// Connected links are associated with the registry.
importNode.__CE_hasRegistry = true;
}
if (importNode && importNode.readyState === 'complete') {
importNode.__CE_documentLoadHandled = true;
} else {
// If this link's import root is not available, its contents can't be
// walked. Wait for 'load' and walk it when it's ready.
element.addEventListener('load', () => {
const importNode = /** @type {!Node} */ (element.import);
if (importNode.__CE_documentLoadHandled) return;
importNode.__CE_documentLoadHandled = true;
// Clone the `visitedImports` set that was populated sync during
// the `patchAndUpgradeTree` call that caused this 'load' handler to
// be added. Then, remove *this* link's import node so that we can
// walk that import again, even if it was partially walked later
// during the same `patchAndUpgradeTree` call.
const clonedVisitedImports = new Set(visitedImports);
clonedVisitedImports.delete(importNode);
this.patchAndUpgradeTree(importNode, {visitedImports: clonedVisitedImports, upgrade});
});
}
} else {
elements.push(element);
}
};
// `walkDeepDescendantElements` populates (and internally checks against)
// `visitedImports` when traversing a loaded import.
Utilities.walkDeepDescendantElements(root, gatherElements, visitedImports);
if (this._hasPatches) {
for (let i = 0; i < elements.length; i++) {
this.patch(elements[i]);
}
}
for (let i = 0; i < elements.length; i++) {
upgrade(elements[i]);
}
}
/**
* @param {!Element} element
*/
upgradeElement(element) {
const currentState = element.__CE_state;
if (currentState !== undefined) return;
// Prevent elements created in documents without a browsing context from
// upgrading.
//
// https://html.spec.whatwg.org/multipage/custom-elements.html#look-up-a-custom-element-definition
// "If document does not have a browsing context, return null."
//
// https://html.spec.whatwg.org/multipage/window-object.html#dom-document-defaultview
// "The defaultView IDL attribute of the Document interface, on getting,
// must return this Document's browsing context's WindowProxy object, if
// this Document has an associated browsing context, or null otherwise."
const ownerDocument = element.ownerDocument;
if (
!ownerDocument.defaultView &&
!(ownerDocument.__CE_isImportDocument && ownerDocument.__CE_hasRegistry)
) return;
const definition = this.localNameToDefinition(element.localName);
if (!definition) return;
definition.constructionStack.push(element);
const constructor = definition.constructor;
try {
try {
let result = new (constructor)();
if (result !== element) {
throw new Error('The custom element constructor did not produce the element being upgraded.');
}
} finally {
definition.constructionStack.pop();
}
} catch (e) {
element.__CE_state = CEState.failed;
throw e;
}
element.__CE_state = CEState.custom;
element.__CE_definition = definition;
if (definition.attributeChangedCallback) {
const observedAttributes = definition.observedAttributes;
for (let i = 0; i < observedAttributes.length; i++) {
const name = observedAttributes[i];
const value = element.getAttribute(name);
if (value !== null) {
this.attributeChangedCallback(element, name, null, value, null);
}
}
}
if (Utilities.isConnected(element)) {
this.connectedCallback(element);
}
}
/**
* @param {!Element} element
*/
connectedCallback(element) {
const definition = element.__CE_definition;
if (definition.connectedCallback) {
definition.connectedCallback.call(element);
}
}
/**
* @param {!Element} element
*/
disconnectedCallback(element) {
const definition = element.__CE_definition;
if (definition.disconnectedCallback) {
definition.disconnectedCallback.call(element);
}
}
/**
* @param {!Element} element
* @param {string} name
* @param {?string} oldValue
* @param {?string} newValue
* @param {?string} namespace
*/
attributeChangedCallback(element, name, oldValue, newValue, namespace) {
const definition = element.__CE_definition;
if (
definition.attributeChangedCallback &&
definition.observedAttributes.indexOf(name) > -1
) {
definition.attributeChangedCallback.call(element, name, oldValue, newValue, namespace);
}
}
}

View File

@@ -1,257 +0,0 @@
import CustomElementInternals from './CustomElementInternals.js';
import DocumentConstructionObserver from './DocumentConstructionObserver.js';
import Deferred from './Deferred.js';
import * as Utilities from './Utilities.js';
/**
* @unrestricted
*/
export default class CustomElementRegistry {
/**
* @param {!CustomElementInternals} internals
*/
constructor(internals) {
/**
* @private
* @type {boolean}
*/
this._elementDefinitionIsRunning = false;
/**
* @private
* @type {!CustomElementInternals}
*/
this._internals = internals;
/**
* @private
* @type {!Map<string, !Deferred<undefined>>}
*/
this._whenDefinedDeferred = new Map();
/**
* The default flush callback triggers the document walk synchronously.
* @private
* @type {!Function}
*/
this._flushCallback = fn => fn();
/**
* @private
* @type {boolean}
*/
this._flushPending = false;
/**
* @private
* @type {!Array<!CustomElementDefinition>}
*/
this._pendingDefinitions = [];
/**
* @private
* @type {!DocumentConstructionObserver}
*/
this._documentConstructionObserver = new DocumentConstructionObserver(internals, document);
}
/**
* @param {string} localName
* @param {!Function} constructor
*/
define(localName, constructor) {
if (!(constructor instanceof Function)) {
throw new TypeError('Custom element constructors must be functions.');
}
if (!Utilities.isValidCustomElementName(localName)) {
throw new SyntaxError(`The element name '${localName}' is not valid.`);
}
if (this._internals.localNameToDefinition(localName)) {
throw new Error(`A custom element with name '${localName}' has already been defined.`);
}
if (this._elementDefinitionIsRunning) {
throw new Error('A custom element is already being defined.');
}
this._elementDefinitionIsRunning = true;
let connectedCallback;
let disconnectedCallback;
let adoptedCallback;
let attributeChangedCallback;
let observedAttributes;
try {
/** @type {!Object} */
const prototype = constructor.prototype;
if (!(prototype instanceof Object)) {
throw new TypeError('The custom element constructor\'s prototype is not an object.');
}
function getCallback(name) {
const callbackValue = prototype[name];
if (callbackValue !== undefined && !(callbackValue instanceof Function)) {
throw new Error(`The '${name}' callback must be a function.`);
}
return callbackValue;
}
connectedCallback = getCallback('connectedCallback');
disconnectedCallback = getCallback('disconnectedCallback');
adoptedCallback = getCallback('adoptedCallback');
attributeChangedCallback = getCallback('attributeChangedCallback');
observedAttributes = constructor['observedAttributes'] || [];
} catch (e) {
return;
} finally {
this._elementDefinitionIsRunning = false;
}
const definition = {
localName,
constructor,
connectedCallback,
disconnectedCallback,
adoptedCallback,
attributeChangedCallback,
observedAttributes,
constructionStack: [],
};
this._internals.setDefinition(localName, definition);
this._pendingDefinitions.push(definition);
// If we've already called the flush callback and it hasn't called back yet,
// don't call it again.
if (!this._flushPending) {
this._flushPending = true;
this._flushCallback(() => this._flush());
}
}
_flush() {
// If no new definitions were defined, don't attempt to flush. This could
// happen if a flush callback keeps the function it is given and calls it
// multiple times.
if (this._flushPending === false) return;
this._flushPending = false;
const pendingDefinitions = this._pendingDefinitions;
/**
* Unupgraded elements with definitions that were defined *before* the last
* flush, in document order.
* @type {!Array<!Element>}
*/
const elementsWithStableDefinitions = [];
/**
* A map from `localName`s of definitions that were defined *after* the last
* flush to unupgraded elements matching that definition, in document order.
* @type {!Map<string, !Array<!Element>>}
*/
const elementsWithPendingDefinitions = new Map();
for (let i = 0; i < pendingDefinitions.length; i++) {
elementsWithPendingDefinitions.set(pendingDefinitions[i].localName, []);
}
this._internals.patchAndUpgradeTree(document, {
upgrade: element => {
// Ignore the element if it has already upgraded or failed to upgrade.
if (element.__CE_state !== undefined) return;
const localName = element.localName;
// If there is an applicable pending definition for the element, add the
// element to the list of elements to be upgraded with that definition.
const pendingElements = elementsWithPendingDefinitions.get(localName);
if (pendingElements) {
pendingElements.push(element);
// If there is *any other* applicable definition for the element, add it
// to the list of elements with stable definitions that need to be upgraded.
} else if (this._internals.localNameToDefinition(localName)) {
elementsWithStableDefinitions.push(element);
}
},
});
// Upgrade elements with 'stable' definitions first.
for (let i = 0; i < elementsWithStableDefinitions.length; i++) {
this._internals.upgradeElement(elementsWithStableDefinitions[i]);
}
// Upgrade elements with 'pending' definitions in the order they were defined.
while (pendingDefinitions.length > 0) {
const definition = pendingDefinitions.shift();
const localName = definition.localName;
// Attempt to upgrade all applicable elements.
const pendingUpgradableElements = elementsWithPendingDefinitions.get(definition.localName);
for (let i = 0; i < pendingUpgradableElements.length; i++) {
this._internals.upgradeElement(pendingUpgradableElements[i]);
}
// Resolve any promises created by `whenDefined` for the definition.
const deferred = this._whenDefinedDeferred.get(localName);
if (deferred) {
deferred.resolve(undefined);
}
}
}
/**
* @param {string} localName
* @return {Function|undefined}
*/
get(localName) {
const definition = this._internals.localNameToDefinition(localName);
if (definition) {
return definition.constructor;
}
return undefined;
}
/**
* @param {string} localName
* @return {!Promise<undefined>}
*/
whenDefined(localName) {
if (!Utilities.isValidCustomElementName(localName)) {
return Promise.reject(new SyntaxError(`'${localName}' is not a valid custom element name.`));
}
const prior = this._whenDefinedDeferred.get(localName);
if (prior) {
return prior.toPromise();
}
const deferred = new Deferred();
this._whenDefinedDeferred.set(localName, deferred);
const definition = this._internals.localNameToDefinition(localName);
// Resolve immediately only if the given local name has a definition *and*
// the full document walk to upgrade elements with that local name has
// already happened.
if (definition && !this._pendingDefinitions.some(d => d.localName === localName)) {
deferred.resolve(undefined);
}
return deferred.toPromise();
}
polyfillWrapFlushCallback(outer) {
this._documentConstructionObserver.disconnect();
const inner = this._flushCallback;
this._flushCallback = flush => outer(() => inner(flush));
}
}
// Closure compiler exports.
window['CustomElementRegistry'] = CustomElementRegistry;
CustomElementRegistry.prototype['define'] = CustomElementRegistry.prototype.define;
CustomElementRegistry.prototype['get'] = CustomElementRegistry.prototype.get;
CustomElementRegistry.prototype['whenDefined'] = CustomElementRegistry.prototype.whenDefined;
CustomElementRegistry.prototype['polyfillWrapFlushCallback'] = CustomElementRegistry.prototype.polyfillWrapFlushCallback;

View File

@@ -1,9 +0,0 @@
/**
* @enum {number}
*/
const CustomElementState = {
custom: 1,
failed: 2,
};
export default CustomElementState;

View File

@@ -1,52 +0,0 @@
/**
* @template T
*/
export default class Deferred {
constructor() {
/**
* @private
* @type {T|undefined}
*/
this._value = undefined;
/**
* @private
* @type {Function|undefined}
*/
this._resolve = undefined;
/**
* @private
* @type {!Promise<T>}
*/
this._promise = new Promise(resolve => {
this._resolve = resolve;
if (this._value) {
resolve(this._value);
}
});
}
/**
* @param {T} value
*/
resolve(value) {
if (this._value) {
throw new Error('Already resolved.');
}
this._value = value;
if (this._resolve) {
this._resolve(value);
}
}
/**
* @return {!Promise<T>}
*/
toPromise() {
return this._promise;
}
}

View File

@@ -1,65 +0,0 @@
import CustomElementInternals from './CustomElementInternals.js';
export default class DocumentConstructionObserver {
constructor(internals, doc) {
/**
* @type {!CustomElementInternals}
*/
this._internals = internals;
/**
* @type {!Document}
*/
this._document = doc;
/**
* @type {MutationObserver|undefined}
*/
this._observer = undefined;
// Simulate tree construction for all currently accessible nodes in the
// document.
this._internals.patchAndUpgradeTree(this._document);
if (this._document.readyState === 'loading') {
this._observer = new MutationObserver(this._handleMutations.bind(this));
// Nodes created by the parser are given to the observer *before* the next
// task runs. Inline scripts are run in a new task. This means that the
// observer will be able to handle the newly parsed nodes before the inline
// script is run.
this._observer.observe(this._document, {
childList: true,
subtree: true,
});
}
}
disconnect() {
if (this._observer) {
this._observer.disconnect();
}
}
/**
* @param {!Array<!MutationRecord>} mutations
*/
_handleMutations(mutations) {
// Once the document's `readyState` is 'interactive' or 'complete', all new
// nodes created within that document will be the result of script and
// should be handled by patching.
const readyState = this._document.readyState;
if (readyState === 'interactive' || readyState === 'complete') {
this.disconnect();
}
for (let i = 0; i < mutations.length; i++) {
const addedNodes = mutations[i].addedNodes;
for (let j = 0; j < addedNodes.length; j++) {
const node = addedNodes[j];
this._internals.patchAndUpgradeTree(node);
}
}
}
}

View File

@@ -1,78 +0,0 @@
import Native from './Native.js';
import CustomElementInternals from '../CustomElementInternals.js';
import * as Utilities from '../Utilities.js';
import PatchParentNode from './Interface/ParentNode.js';
/**
* @param {!CustomElementInternals} internals
*/
export default function(internals) {
Utilities.setPropertyUnchecked(Document.prototype, 'createElement',
/**
* @this {Document}
* @param {string} localName
* @return {!Element}
*/
function(localName) {
// Only create custom elements if this document is associated with the registry.
if (this.__CE_hasRegistry) {
const definition = internals.localNameToDefinition(localName);
if (definition) {
return new (definition.constructor)();
}
}
const result = /** @type {!Element} */
(Native.Document_createElement.call(this, localName));
internals.patch(result);
return result;
});
Utilities.setPropertyUnchecked(Document.prototype, 'importNode',
/**
* @this {Document}
* @param {!Node} node
* @param {boolean=} deep
* @return {!Node}
*/
function(node, deep) {
const clone = Native.Document_importNode.call(this, node, deep);
// Only create custom elements if this document is associated with the registry.
if (!this.__CE_hasRegistry) {
internals.patchTree(clone);
} else {
internals.patchAndUpgradeTree(clone);
}
return clone;
});
const NS_HTML = "http://www.w3.org/1999/xhtml";
Utilities.setPropertyUnchecked(Document.prototype, 'createElementNS',
/**
* @this {Document}
* @param {?string} namespace
* @param {string} localName
* @return {!Element}
*/
function(namespace, localName) {
// Only create custom elements if this document is associated with the registry.
if (this.__CE_hasRegistry && (namespace === null || namespace === NS_HTML)) {
const definition = internals.localNameToDefinition(localName);
if (definition) {
return new (definition.constructor)();
}
}
const result = /** @type {!Element} */
(Native.Document_createElementNS.call(this, namespace, localName));
internals.patch(result);
return result;
});
PatchParentNode(internals, Document.prototype, {
prepend: Native.Document_prepend,
append: Native.Document_append,
});
};

View File

@@ -1,13 +0,0 @@
import CustomElementInternals from '../CustomElementInternals.js';
import Native from './Native.js';
import PatchParentNode from './Interface/ParentNode.js';
/**
* @param {!CustomElementInternals} internals
*/
export default function(internals) {
PatchParentNode(internals, DocumentFragment.prototype, {
prepend: Native.DocumentFragment_prepend,
append: Native.DocumentFragment_append,
});
};

View File

@@ -1,243 +0,0 @@
import Native from './Native.js';
import CustomElementInternals from '../CustomElementInternals.js';
import CEState from '../CustomElementState.js';
import * as Utilities from '../Utilities.js';
import PatchParentNode from './Interface/ParentNode.js';
import PatchChildNode from './Interface/ChildNode.js';
/**
* @param {!CustomElementInternals} internals
*/
export default function(internals) {
if (Native.Element_attachShadow) {
Utilities.setPropertyUnchecked(Element.prototype, 'attachShadow',
/**
* @this {Element}
* @param {!{mode: string}} init
* @return {ShadowRoot}
*/
function(init) {
const shadowRoot = Native.Element_attachShadow.call(this, init);
this.__CE_shadowRoot = shadowRoot;
return shadowRoot;
});
}
function patch_innerHTML(destination, baseDescriptor) {
Object.defineProperty(destination, 'innerHTML', {
enumerable: baseDescriptor.enumerable,
configurable: true,
get: baseDescriptor.get,
set: /** @this {Element} */ function(htmlString) {
const isConnected = Utilities.isConnected(this);
// NOTE: In IE11, when using the native `innerHTML` setter, all nodes
// that were previously descendants of the context element have all of
// their children removed as part of the set - the entire subtree is
// 'disassembled'. This work around walks the subtree *before* using the
// native setter.
/** @type {!Array<!Element>|undefined} */
let removedElements = undefined;
if (isConnected) {
removedElements = [];
Utilities.walkDeepDescendantElements(this, element => {
if (element !== this) {
removedElements.push(element);
}
});
}
baseDescriptor.set.call(this, htmlString);
if (removedElements) {
for (let i = 0; i < removedElements.length; i++) {
const element = removedElements[i];
if (element.__CE_state === CEState.custom) {
internals.disconnectedCallback(element);
}
}
}
// Only create custom elements if this element's owner document is
// associated with the registry.
if (!this.ownerDocument.__CE_hasRegistry) {
internals.patchTree(this);
} else {
internals.patchAndUpgradeTree(this);
}
return htmlString;
},
});
}
if (Native.Element_innerHTML && Native.Element_innerHTML.get) {
patch_innerHTML(Element.prototype, Native.Element_innerHTML);
} else if (Native.HTMLElement_innerHTML && Native.HTMLElement_innerHTML.get) {
patch_innerHTML(HTMLElement.prototype, Native.HTMLElement_innerHTML);
} else {
internals.addPatch(function(element) {
patch_innerHTML(element, {
enumerable: true,
configurable: true,
// Implements getting `innerHTML` by performing an unpatched `cloneNode`
// of the element and returning the resulting element's `innerHTML`.
// TODO: Is this too expensive?
get: /** @this {Element} */ function() {
return Native.Node_cloneNode.call(this, true).innerHTML;
},
// Implements setting `innerHTML` by creating an unpatched element,
// setting `innerHTML` of that element and replacing the target
// element's children with those of the unpatched element.
set: /** @this {Element} */ function(assignedValue) {
// NOTE: re-route to `content` for `template` elements.
// We need to do this because `template.appendChild` does not
// route into `template.content`.
const isTemplate = (this.localName === 'template');
/** @type {!Node} */
const content = isTemplate ? (/** @type {!HTMLTemplateElement} */
(this)).content : this;
/** @type {!Node} */
const rawElement = Native.Document_createElement.call(document,
this.localName);
rawElement.innerHTML = assignedValue;
while (content.childNodes.length > 0) {
Native.Node_removeChild.call(content, content.childNodes[0]);
}
const container = isTemplate ? rawElement.content : rawElement;
while (container.childNodes.length > 0) {
Native.Node_appendChild.call(content, container.childNodes[0]);
}
},
});
});
}
Utilities.setPropertyUnchecked(Element.prototype, 'setAttribute',
/**
* @this {Element}
* @param {string} name
* @param {string} newValue
*/
function(name, newValue) {
// Fast path for non-custom elements.
if (this.__CE_state !== CEState.custom) {
return Native.Element_setAttribute.call(this, name, newValue);
}
const oldValue = Native.Element_getAttribute.call(this, name);
Native.Element_setAttribute.call(this, name, newValue);
newValue = Native.Element_getAttribute.call(this, name);
internals.attributeChangedCallback(this, name, oldValue, newValue, null);
});
Utilities.setPropertyUnchecked(Element.prototype, 'setAttributeNS',
/**
* @this {Element}
* @param {?string} namespace
* @param {string} name
* @param {string} newValue
*/
function(namespace, name, newValue) {
// Fast path for non-custom elements.
if (this.__CE_state !== CEState.custom) {
return Native.Element_setAttributeNS.call(this, namespace, name, newValue);
}
const oldValue = Native.Element_getAttributeNS.call(this, namespace, name);
Native.Element_setAttributeNS.call(this, namespace, name, newValue);
newValue = Native.Element_getAttributeNS.call(this, namespace, name);
internals.attributeChangedCallback(this, name, oldValue, newValue, namespace);
});
Utilities.setPropertyUnchecked(Element.prototype, 'removeAttribute',
/**
* @this {Element}
* @param {string} name
*/
function(name) {
// Fast path for non-custom elements.
if (this.__CE_state !== CEState.custom) {
return Native.Element_removeAttribute.call(this, name);
}
const oldValue = Native.Element_getAttribute.call(this, name);
Native.Element_removeAttribute.call(this, name);
if (oldValue !== null) {
internals.attributeChangedCallback(this, name, oldValue, null, null);
}
});
Utilities.setPropertyUnchecked(Element.prototype, 'removeAttributeNS',
/**
* @this {Element}
* @param {?string} namespace
* @param {string} name
*/
function(namespace, name) {
// Fast path for non-custom elements.
if (this.__CE_state !== CEState.custom) {
return Native.Element_removeAttributeNS.call(this, namespace, name);
}
const oldValue = Native.Element_getAttributeNS.call(this, namespace, name);
Native.Element_removeAttributeNS.call(this, namespace, name);
// In older browsers, `Element#getAttributeNS` may return the empty string
// instead of null if the attribute does not exist. For details, see;
// https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttributeNS#Notes
const newValue = Native.Element_getAttributeNS.call(this, namespace, name);
if (oldValue !== newValue) {
internals.attributeChangedCallback(this, name, oldValue, newValue, namespace);
}
});
function patch_insertAdjacentElement(destination, baseMethod) {
Utilities.setPropertyUnchecked(destination, 'insertAdjacentElement',
/**
* @this {Element}
* @param {string} where
* @param {!Element} element
* @return {?Element}
*/
function(where, element) {
const wasConnected = Utilities.isConnected(element);
const insertedElement = /** @type {!Element} */
(baseMethod.call(this, where, element));
if (wasConnected) {
internals.disconnectTree(element);
}
if (Utilities.isConnected(insertedElement)) {
internals.connectTree(element);
}
return insertedElement;
});
}
if (Native.HTMLElement_insertAdjacentElement) {
patch_insertAdjacentElement(HTMLElement.prototype, Native.HTMLElement_insertAdjacentElement);
} else if (Native.Element_insertAdjacentElement) {
patch_insertAdjacentElement(Element.prototype, Native.Element_insertAdjacentElement);
} else {
console.warn('Custom Elements: `Element#insertAdjacentElement` was not patched.');
}
PatchParentNode(internals, Element.prototype, {
prepend: Native.Element_prepend,
append: Native.Element_append,
});
PatchChildNode(internals, Element.prototype, {
before: Native.Element_before,
after: Native.Element_after,
replaceWith: Native.Element_replaceWith,
remove: Native.Element_remove,
});
};

View File

@@ -1,54 +0,0 @@
import Native from './Native.js';
import CustomElementInternals from '../CustomElementInternals.js';
import CEState from '../CustomElementState.js';
import AlreadyConstructedMarker from '../AlreadyConstructedMarker.js';
/**
* @param {!CustomElementInternals} internals
*/
export default function(internals) {
window['HTMLElement'] = (function() {
/**
* @type {function(new: HTMLElement): !HTMLElement}
*/
function HTMLElement() {
// This should really be `new.target` but `new.target` can't be emulated
// in ES5. Assuming the user keeps the default value of the constructor's
// prototype's `constructor` property, this is equivalent.
/** @type {!Function} */
const constructor = this.constructor;
const definition = internals.constructorToDefinition(constructor);
if (!definition) {
throw new Error('The custom element being constructed was not registered with `customElements`.');
}
const constructionStack = definition.constructionStack;
if (constructionStack.length === 0) {
const element = Native.Document_createElement.call(document, definition.localName);
Object.setPrototypeOf(element, constructor.prototype);
element.__CE_state = CEState.custom;
element.__CE_definition = definition;
internals.patch(element);
return element;
}
const lastIndex = constructionStack.length - 1;
const element = constructionStack[lastIndex];
if (element === AlreadyConstructedMarker) {
throw new Error('The HTMLElement constructor was either called reentrantly for this constructor or called multiple times.');
}
constructionStack[lastIndex] = AlreadyConstructedMarker;
Object.setPrototypeOf(element, constructor.prototype);
internals.patch(/** @type {!HTMLElement} */ (element));
return element;
}
HTMLElement.prototype = Native.HTMLElement.prototype;
return HTMLElement;
})();
};

View File

@@ -1,145 +0,0 @@
import CustomElementInternals from '../../CustomElementInternals.js';
import * as Utilities from '../../Utilities.js';
/**
* @typedef {{
* before: !function(...(!Node|string)),
* after: !function(...(!Node|string)),
* replaceWith: !function(...(!Node|string)),
* remove: !function(),
* }}
*/
let ChildNodeNativeMethods;
/**
* @param {!CustomElementInternals} internals
* @param {!Object} destination
* @param {!ChildNodeNativeMethods} builtIn
*/
export default function(internals, destination, builtIn) {
/**
* @param {!function(...(!Node|string))} builtInMethod
* @return {!function(...(!Node|string))}
*/
function beforeAfterPatch(builtInMethod) {
return function(...nodes) {
/**
* A copy of `nodes`, with any DocumentFragment replaced by its children.
* @type {!Array<!Node>}
*/
const flattenedNodes = [];
/**
* Elements in `nodes` that were connected before this call.
* @type {!Array<!Node>}
*/
const connectedElements = [];
for (var i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node instanceof Element && Utilities.isConnected(node)) {
connectedElements.push(node);
}
if (node instanceof DocumentFragment) {
for (let child = node.firstChild; child; child = child.nextSibling) {
flattenedNodes.push(child);
}
} else {
flattenedNodes.push(node);
}
}
builtInMethod.apply(this, nodes);
for (let i = 0; i < connectedElements.length; i++) {
internals.disconnectTree(connectedElements[i]);
}
if (Utilities.isConnected(this)) {
for (let i = 0; i < flattenedNodes.length; i++) {
const node = flattenedNodes[i];
if (node instanceof Element) {
internals.connectTree(node);
}
}
}
};
}
if (builtIn.before !== undefined) {
Utilities.setPropertyUnchecked(destination, 'before', beforeAfterPatch(builtIn.before));
}
if (builtIn.before !== undefined) {
Utilities.setPropertyUnchecked(destination, 'after', beforeAfterPatch(builtIn.after));
}
if (builtIn.replaceWith !== undefined) {
Utilities.setPropertyUnchecked(destination, 'replaceWith',
/**
* @param {...(!Node|string)} nodes
*/
function(...nodes) {
/**
* A copy of `nodes`, with any DocumentFragment replaced by its children.
* @type {!Array<!Node>}
*/
const flattenedNodes = [];
/**
* Elements in `nodes` that were connected before this call.
* @type {!Array<!Node>}
*/
const connectedElements = [];
for (var i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node instanceof Element && Utilities.isConnected(node)) {
connectedElements.push(node);
}
if (node instanceof DocumentFragment) {
for (let child = node.firstChild; child; child = child.nextSibling) {
flattenedNodes.push(child);
}
} else {
flattenedNodes.push(node);
}
}
const wasConnected = Utilities.isConnected(this);
builtIn.replaceWith.apply(this, nodes);
for (let i = 0; i < connectedElements.length; i++) {
internals.disconnectTree(connectedElements[i]);
}
if (wasConnected) {
internals.disconnectTree(this);
for (let i = 0; i < flattenedNodes.length; i++) {
const node = flattenedNodes[i];
if (node instanceof Element) {
internals.connectTree(node);
}
}
}
});
}
if (builtIn.remove !== undefined) {
Utilities.setPropertyUnchecked(destination, 'remove',
function() {
const wasConnected = Utilities.isConnected(this);
builtIn.remove.call(this);
if (wasConnected) {
internals.disconnectTree(this);
}
});
}
};

View File

@@ -1,76 +0,0 @@
import CustomElementInternals from '../../CustomElementInternals.js';
import * as Utilities from '../../Utilities.js';
/**
* @typedef {{
* prepend: !function(...(!Node|string)),
* append: !function(...(!Node|string)),
* }}
*/
let ParentNodeNativeMethods;
/**
* @param {!CustomElementInternals} internals
* @param {!Object} destination
* @param {!ParentNodeNativeMethods} builtIn
*/
export default function(internals, destination, builtIn) {
/**
* @param {!function(...(!Node|string))} builtInMethod
* @return {!function(...(!Node|string))}
*/
function appendPrependPatch(builtInMethod) {
return function(...nodes) {
/**
* A copy of `nodes`, with any DocumentFragment replaced by its children.
* @type {!Array<!Node>}
*/
const flattenedNodes = [];
/**
* Elements in `nodes` that were connected before this call.
* @type {!Array<!Node>}
*/
const connectedElements = [];
for (var i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node instanceof Element && Utilities.isConnected(node)) {
connectedElements.push(node);
}
if (node instanceof DocumentFragment) {
for (let child = node.firstChild; child; child = child.nextSibling) {
flattenedNodes.push(child);
}
} else {
flattenedNodes.push(node);
}
}
builtInMethod.apply(this, nodes);
for (let i = 0; i < connectedElements.length; i++) {
internals.disconnectTree(connectedElements[i]);
}
if (Utilities.isConnected(this)) {
for (let i = 0; i < flattenedNodes.length; i++) {
const node = flattenedNodes[i];
if (node instanceof Element) {
internals.connectTree(node);
}
}
}
};
}
if (builtIn.prepend !== undefined) {
Utilities.setPropertyUnchecked(destination, 'prepend', appendPrependPatch(builtIn.prepend));
}
if (builtIn.append !== undefined) {
Utilities.setPropertyUnchecked(destination, 'append', appendPrependPatch(builtIn.append));
}
};

View File

@@ -1,33 +0,0 @@
export default {
Document_createElement: window.Document.prototype.createElement,
Document_createElementNS: window.Document.prototype.createElementNS,
Document_importNode: window.Document.prototype.importNode,
Document_prepend: window.Document.prototype['prepend'],
Document_append: window.Document.prototype['append'],
DocumentFragment_prepend: window.DocumentFragment.prototype['prepend'],
DocumentFragment_append: window.DocumentFragment.prototype['append'],
Node_cloneNode: window.Node.prototype.cloneNode,
Node_appendChild: window.Node.prototype.appendChild,
Node_insertBefore: window.Node.prototype.insertBefore,
Node_removeChild: window.Node.prototype.removeChild,
Node_replaceChild: window.Node.prototype.replaceChild,
Node_textContent: Object.getOwnPropertyDescriptor(window.Node.prototype, 'textContent'),
Element_attachShadow: window.Element.prototype['attachShadow'],
Element_innerHTML: Object.getOwnPropertyDescriptor(window.Element.prototype, 'innerHTML'),
Element_getAttribute: window.Element.prototype.getAttribute,
Element_setAttribute: window.Element.prototype.setAttribute,
Element_removeAttribute: window.Element.prototype.removeAttribute,
Element_getAttributeNS: window.Element.prototype.getAttributeNS,
Element_setAttributeNS: window.Element.prototype.setAttributeNS,
Element_removeAttributeNS: window.Element.prototype.removeAttributeNS,
Element_insertAdjacentElement: window.Element.prototype['insertAdjacentElement'],
Element_prepend: window.Element.prototype['prepend'],
Element_append: window.Element.prototype['append'],
Element_before: window.Element.prototype['before'],
Element_after: window.Element.prototype['after'],
Element_replaceWith: window.Element.prototype['replaceWith'],
Element_remove: window.Element.prototype['remove'],
HTMLElement: window.HTMLElement,
HTMLElement_innerHTML: Object.getOwnPropertyDescriptor(window.HTMLElement.prototype, 'innerHTML'),
HTMLElement_insertAdjacentElement: window.HTMLElement.prototype['insertAdjacentElement'],
};

View File

@@ -1,235 +0,0 @@
import Native from './Native.js';
import CustomElementInternals from '../CustomElementInternals.js';
import * as Utilities from '../Utilities.js';
/**
* @param {!CustomElementInternals} internals
*/
export default function(internals) {
// `Node#nodeValue` is implemented on `Attr`.
// `Node#textContent` is implemented on `Attr`, `Element`.
Utilities.setPropertyUnchecked(Node.prototype, 'insertBefore',
/**
* @this {Node}
* @param {!Node} node
* @param {?Node} refNode
* @return {!Node}
*/
function(node, refNode) {
if (node instanceof DocumentFragment) {
const insertedNodes = Array.prototype.slice.apply(node.childNodes);
const nativeResult = Native.Node_insertBefore.call(this, node, refNode);
// DocumentFragments can't be connected, so `disconnectTree` will never
// need to be called on a DocumentFragment's children after inserting it.
if (Utilities.isConnected(this)) {
for (let i = 0; i < insertedNodes.length; i++) {
internals.connectTree(insertedNodes[i]);
}
}
return nativeResult;
}
const nodeWasConnected = Utilities.isConnected(node);
const nativeResult = Native.Node_insertBefore.call(this, node, refNode);
if (nodeWasConnected) {
internals.disconnectTree(node);
}
if (Utilities.isConnected(this)) {
internals.connectTree(node);
}
return nativeResult;
});
Utilities.setPropertyUnchecked(Node.prototype, 'appendChild',
/**
* @this {Node}
* @param {!Node} node
* @return {!Node}
*/
function(node) {
if (node instanceof DocumentFragment) {
const insertedNodes = Array.prototype.slice.apply(node.childNodes);
const nativeResult = Native.Node_appendChild.call(this, node);
// DocumentFragments can't be connected, so `disconnectTree` will never
// need to be called on a DocumentFragment's children after inserting it.
if (Utilities.isConnected(this)) {
for (let i = 0; i < insertedNodes.length; i++) {
internals.connectTree(insertedNodes[i]);
}
}
return nativeResult;
}
const nodeWasConnected = Utilities.isConnected(node);
const nativeResult = Native.Node_appendChild.call(this, node);
if (nodeWasConnected) {
internals.disconnectTree(node);
}
if (Utilities.isConnected(this)) {
internals.connectTree(node);
}
return nativeResult;
});
Utilities.setPropertyUnchecked(Node.prototype, 'cloneNode',
/**
* @this {Node}
* @param {boolean=} deep
* @return {!Node}
*/
function(deep) {
const clone = Native.Node_cloneNode.call(this, deep);
// Only create custom elements if this element's owner document is
// associated with the registry.
if (!this.ownerDocument.__CE_hasRegistry) {
internals.patchTree(clone);
} else {
internals.patchAndUpgradeTree(clone);
}
return clone;
});
Utilities.setPropertyUnchecked(Node.prototype, 'removeChild',
/**
* @this {Node}
* @param {!Node} node
* @return {!Node}
*/
function(node) {
const nodeWasConnected = Utilities.isConnected(node);
const nativeResult = Native.Node_removeChild.call(this, node);
if (nodeWasConnected) {
internals.disconnectTree(node);
}
return nativeResult;
});
Utilities.setPropertyUnchecked(Node.prototype, 'replaceChild',
/**
* @this {Node}
* @param {!Node} nodeToInsert
* @param {!Node} nodeToRemove
* @return {!Node}
*/
function(nodeToInsert, nodeToRemove) {
if (nodeToInsert instanceof DocumentFragment) {
const insertedNodes = Array.prototype.slice.apply(nodeToInsert.childNodes);
const nativeResult = Native.Node_replaceChild.call(this, nodeToInsert, nodeToRemove);
// DocumentFragments can't be connected, so `disconnectTree` will never
// need to be called on a DocumentFragment's children after inserting it.
if (Utilities.isConnected(this)) {
internals.disconnectTree(nodeToRemove);
for (let i = 0; i < insertedNodes.length; i++) {
internals.connectTree(insertedNodes[i]);
}
}
return nativeResult;
}
const nodeToInsertWasConnected = Utilities.isConnected(nodeToInsert);
const nativeResult = Native.Node_replaceChild.call(this, nodeToInsert, nodeToRemove);
const thisIsConnected = Utilities.isConnected(this);
if (thisIsConnected) {
internals.disconnectTree(nodeToRemove);
}
if (nodeToInsertWasConnected) {
internals.disconnectTree(nodeToInsert);
}
if (thisIsConnected) {
internals.connectTree(nodeToInsert);
}
return nativeResult;
});
function patch_textContent(destination, baseDescriptor) {
Object.defineProperty(destination, 'textContent', {
enumerable: baseDescriptor.enumerable,
configurable: true,
get: baseDescriptor.get,
set: /** @this {Node} */ function(assignedValue) {
// If this is a text node then there are no nodes to disconnect.
if (this.nodeType === Node.TEXT_NODE) {
baseDescriptor.set.call(this, assignedValue);
return;
}
let removedNodes = undefined;
// Checking for `firstChild` is faster than reading `childNodes.length`
// to compare with 0.
if (this.firstChild) {
// Using `childNodes` is faster than `children`, even though we only
// care about elements.
const childNodes = this.childNodes;
const childNodesLength = childNodes.length;
if (childNodesLength > 0 && Utilities.isConnected(this)) {
// Copying an array by iterating is faster than using slice.
removedNodes = new Array(childNodesLength);
for (let i = 0; i < childNodesLength; i++) {
removedNodes[i] = childNodes[i];
}
}
}
baseDescriptor.set.call(this, assignedValue);
if (removedNodes) {
for (let i = 0; i < removedNodes.length; i++) {
internals.disconnectTree(removedNodes[i]);
}
}
},
});
}
if (Native.Node_textContent && Native.Node_textContent.get) {
patch_textContent(Node.prototype, Native.Node_textContent);
} else {
internals.addPatch(function(element) {
patch_textContent(element, {
enumerable: true,
configurable: true,
// NOTE: This implementation of the `textContent` getter assumes that
// text nodes' `textContent` getter will not be patched.
get: /** @this {Node} */ function() {
/** @type {!Array<string>} */
const parts = [];
for (let i = 0; i < this.childNodes.length; i++) {
parts.push(this.childNodes[i].textContent);
}
return parts.join('');
},
set: /** @this {Node} */ function(assignedValue) {
while (this.firstChild) {
Native.Node_removeChild.call(this, this.firstChild);
}
Native.Node_appendChild.call(this, document.createTextNode(assignedValue));
},
});
});
}
};

View File

@@ -1,129 +0,0 @@
const reservedTagList = new Set([
'annotation-xml',
'color-profile',
'font-face',
'font-face-src',
'font-face-uri',
'font-face-format',
'font-face-name',
'missing-glyph',
]);
/**
* @param {string} localName
* @returns {boolean}
*/
export function isValidCustomElementName(localName) {
const reserved = reservedTagList.has(localName);
const validForm = /^[a-z][.0-9_a-z]*-[\-.0-9_a-z]*$/.test(localName);
return !reserved && validForm;
}
/**
* @private
* @param {!Node} node
* @return {boolean}
*/
export function isConnected(node) {
// Use `Node#isConnected`, if defined.
const nativeValue = node.isConnected;
if (nativeValue !== undefined) {
return nativeValue;
}
/** @type {?Node|undefined} */
let current = node;
while (current && !(current.__CE_isImportDocument || current instanceof Document)) {
current = current.parentNode || (window.ShadowRoot && current instanceof ShadowRoot ? current.host : undefined);
}
return !!(current && (current.__CE_isImportDocument || current instanceof Document));
}
/**
* @param {!Node} root
* @param {!Node} start
* @return {?Node}
*/
function nextSiblingOrAncestorSibling(root, start) {
let node = start;
while (node && node !== root && !node.nextSibling) {
node = node.parentNode;
}
return (!node || node === root) ? null : node.nextSibling;
}
/**
* @param {!Node} root
* @param {!Node} start
* @return {?Node}
*/
function nextNode(root, start) {
return start.firstChild ? start.firstChild : nextSiblingOrAncestorSibling(root, start);
}
/**
* @param {!Node} root
* @param {!function(!Element)} callback
* @param {!Set<Node>=} visitedImports
*/
export function walkDeepDescendantElements(root, callback, visitedImports = new Set()) {
let node = root;
while (node) {
if (node.nodeType === Node.ELEMENT_NODE) {
const element = /** @type {!Element} */(node);
callback(element);
const localName = element.localName;
if (localName === 'link' && element.getAttribute('rel') === 'import') {
// If this import (polyfilled or not) has it's root node available,
// walk it.
const importNode = /** @type {!Node} */ (element.import);
if (importNode instanceof Node && !visitedImports.has(importNode)) {
// Prevent multiple walks of the same import root.
visitedImports.add(importNode);
for (let child = importNode.firstChild; child; child = child.nextSibling) {
walkDeepDescendantElements(child, callback, visitedImports);
}
}
// Ignore descendants of import links to prevent attempting to walk the
// elements created by the HTML Imports polyfill that we just walked
// above.
node = nextSiblingOrAncestorSibling(root, element);
continue;
} else if (localName === 'template') {
// Ignore descendants of templates. There shouldn't be any descendants
// because they will be moved into `.content` during construction in
// browsers that support template but, in case they exist and are still
// waiting to be moved by a polyfill, they will be ignored.
node = nextSiblingOrAncestorSibling(root, element);
continue;
}
// Walk shadow roots.
const shadowRoot = element.__CE_shadowRoot;
if (shadowRoot) {
for (let child = shadowRoot.firstChild; child; child = child.nextSibling) {
walkDeepDescendantElements(child, callback, visitedImports);
}
}
}
node = nextNode(root, node);
}
}
/**
* Used to suppress Closure's "Modifying the prototype is only allowed if the
* constructor is in the same scope" warning without using
* `@suppress {newCheckTypes, duplicate}` because `newCheckTypes` is too broad.
*
* @param {!Object} destination
* @param {string} name
* @param {*} value
*/
export function setPropertyUnchecked(destination, name, value) {
destination[name] = value;
}

View File

@@ -1,46 +0,0 @@
/**
* @license
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
import CustomElementInternals from './CustomElementInternals.js';
import CustomElementRegistry from './CustomElementRegistry.js';
import PatchHTMLElement from './Patch/HTMLElement.js';
import PatchDocument from './Patch/Document.js';
import PatchDocumentFragment from './Patch/DocumentFragment.js';
import PatchNode from './Patch/Node.js';
import PatchElement from './Patch/Element.js';
const priorCustomElements = window['customElements'];
if (!priorCustomElements ||
priorCustomElements['forcePolyfill'] ||
(typeof priorCustomElements['define'] != 'function') ||
(typeof priorCustomElements['get'] != 'function')) {
/** @type {!CustomElementInternals} */
const internals = new CustomElementInternals();
PatchHTMLElement(internals);
PatchDocument(internals);
PatchDocumentFragment(internals);
PatchNode(internals);
PatchElement(internals);
// The main document is always associated with the registry.
document.__CE_hasRegistry = true;
/** @type {!CustomElementRegistry} */
const customElements = new CustomElementRegistry(internals);
Object.defineProperty(window, 'customElements', {
configurable: true,
enumerable: true,
value: customElements,
});
}

View File

@@ -1,164 +0,0 @@
/**
* @license
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
/**
* This shim allows elements written in, or compiled to, ES5 to work on native
* implementations of Custom Elements.
*
* ES5-style classes don't work with native Custom Elements because the
* HTMLElement constructor uses the value of `new.target` to look up the custom
* element definition for the currently called constructor. `new.target` is only
* set when `new` is called and is only propagated via super() calls. super()
* is not emulatable in ES5. The pattern of `SuperClass.call(this)`` only works
* when extending other ES5-style classes, and does not propagate `new.target`.
*
* This shim allows the native HTMLElement constructor to work by generating and
* registering a stand-in class instead of the users custom element class. This
* stand-in class's constructor has an actual call to super().
* `customElements.define()` and `customElements.get()` are both overridden to
* hide this stand-in class from users.
*
* In order to create instance of the user-defined class, rather than the stand
* in, the stand-in's constructor swizzles its instances prototype and invokes
* the user-defined constructor. When the user-defined constructor is called
* directly it creates an instance of the stand-in class to get a real extension
* of HTMLElement and returns that.
*
* There are two important constructors: A patched HTMLElement constructor, and
* the StandInElement constructor. They both will be called to create an element
* but which is called first depends on whether the browser creates the element
* or the user-defined constructor is called directly. The variables
* `browserConstruction` and `userConstruction` control the flow between the
* two constructors.
*
* This shim should be better than forcing the polyfill because:
* 1. It's smaller
* 2. All reaction timings are the same as native (mostly synchronous)
* 3. All reaction triggering DOM operations are automatically supported
*
* There are some restrictions and requirements on ES5 constructors:
* 1. All constructors in a inheritance hierarchy must be ES5-style, so that
* they can be called with Function.call(). This effectively means that the
* whole application must be compiled to ES5.
* 2. Constructors must return the value of the emulated super() call. Like
* `return SuperClass.call(this)`
* 3. The `this` reference should not be used before the emulated super() call
* just like `this` is illegal to use before super() in ES6.
* 4. Constructors should not create other custom elements before the emulated
* super() call. This is the same restriction as with native custom
* elements.
*
* Compiling valid class-based custom elements to ES5 will satisfy these
* requirements with the latest version of popular transpilers.
*/
(() => {
'use strict';
// Do nothing if `customElements` does not exist.
if (!window.customElements) return;
const NativeHTMLElement = window.HTMLElement;
const nativeDefine = window.customElements.define;
const nativeGet = window.customElements.get;
/**
* Map of user-provided constructors to tag names.
*
* @type {Map<Function, string>}
*/
const tagnameByConstructor = new Map();
/**
* Map of tag names to user-provided constructors.
*
* @type {Map<string, Function>}
*/
const constructorByTagname = new Map();
/**
* Whether the constructors are being called by a browser process, ie parsing
* or createElement.
*/
let browserConstruction = false;
/**
* Whether the constructors are being called by a user-space process, ie
* calling an element constructor.
*/
let userConstruction = false;
window.HTMLElement = function() {
if (!browserConstruction) {
const tagname = tagnameByConstructor.get(this.constructor);
const fakeClass = nativeGet.call(window.customElements, tagname);
// Make sure that the fake constructor doesn't call back to this constructor
userConstruction = true;
const instance = new (fakeClass)();
return instance;
}
// Else do nothing. This will be reached by ES5-style classes doing
// HTMLElement.call() during initialization
browserConstruction = false;
};
// By setting the patched HTMLElement's prototype property to the native
// HTMLElement's prototype we make sure that:
// document.createElement('a') instanceof HTMLElement
// works because instanceof uses HTMLElement.prototype, which is on the
// ptototype chain of built-in elements.
window.HTMLElement.prototype = NativeHTMLElement.prototype;
const define = (tagname, elementClass) => {
const elementProto = elementClass.prototype;
const StandInElement = class extends NativeHTMLElement {
constructor() {
// Call the native HTMLElement constructor, this gives us the
// under-construction instance as `this`:
super();
// The prototype will be wrong up because the browser used our fake
// class, so fix it:
Object.setPrototypeOf(this, elementProto);
if (!userConstruction) {
// Make sure that user-defined constructor bottom's out to a do-nothing
// HTMLElement() call
browserConstruction = true;
// Call the user-defined constructor on our instance:
elementClass.call(this);
}
userConstruction = false;
}
};
const standInProto = StandInElement.prototype;
StandInElement.observedAttributes = elementClass.observedAttributes;
standInProto.connectedCallback = elementProto.connectedCallback;
standInProto.disconnectedCallback = elementProto.disconnectedCallback;
standInProto.attributeChangedCallback = elementProto.attributeChangedCallback;
standInProto.adoptedCallback = elementProto.adoptedCallback;
tagnameByConstructor.set(elementClass, tagname);
constructorByTagname.set(tagname, elementClass);
nativeDefine.call(window.customElements, tagname, StandInElement);
};
const get = (tagname) => constructorByTagname.get(tagname);
// Workaround for Safari bug where patching customElements can be lost, likely
// due to native wrapper garbage collection issue
Object.defineProperty(window, 'customElements',
{value: window.customElements, configurable: true, writable: true});
Object.defineProperty(window.customElements, 'define',
{value: define, configurable: true, writable: true});
Object.defineProperty(window.customElements, 'get',
{value: get, configurable: true, writable: true});
})();

View File

@@ -1,34 +0,0 @@
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<div id=editor contenteditable><x-x>aa</x-x></div>
<script>
'use strict';
promise_test(function () {
let constructCount = 0;
customElements.define('x-x', class extends HTMLElement {
constructor() {
super();
constructCount++;
}
});
assert_equals(constructCount, 1, 'define() should run constructor');
let element = editor.firstElementChild;
element.focus();
let selection = getSelection();
selection.collapse(element.firstChild, 1);
eventSender.keyDown('\r');
// Backup queue runs in a microtask, so schedule the assertions after that.
return new Promise(resolve => {
resolve();
}).then(() => {
assert_equals(document.getElementsByTagName('x-x').length, 2, 'Enter key should clone the element');
assert_equals(constructCount, 2, 'clone should run constructor');
});
}, 'clone-a-node in contenteditable');
</script>

View File

@@ -1,44 +0,0 @@
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="../fast/dom/custom/testutils.js"></script>
<body>
<script>
'use strict';
// TODO(dominicc): Port fork() etc. to work with testharness, then
// remove these.
function debug() {}
function finishJSTest() {}
(() => {
if (fork()) {
// The controlling parent frame.
let t = async_test('constructor destroys the context before super');
let watcher = new EventWatcher(t, window, 'message');
watcher.wait_for('message').then(t.step_func((event) => {
assert_equals(event.data, 'PASS destroyed context');
return watcher.wait_for('message');
})).then(t.step_func((event) => {
assert_equals(event.data, 'PASS child done');
t.done();
}));
} else {
// The child frame.
class BadConstructor extends HTMLElement {
constructor() {
destroyContext();
super();
}
}
window.customElements.define('x-x', BadConstructor);
try {
new BadConstructor();
} finally {
done();
}
}
})();
</script>

View File

@@ -1,39 +0,0 @@
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="../fast/dom/custom/testutils.js"></script>
<body>
<script>
'use strict';
// TODO(dominicc): Port fork() etc. to work with testharness, then
// remove these.
function debug() {}
function finishJSTest() {}
(() => {
if (fork()) {
// The controlling parent frame.
let t = async_test('context is destroyed; call across context');
let watcher = new EventWatcher(t, window, 'message');
watcher.wait_for('message').then(t.step_func((event) => {
assert_equals(event.data, 'PASS destroyed context');
return watcher.wait_for('message');
})).then(t.step_func((event) => {
assert_equals(event.data, 'PASS child done');
let e = new window.C();
assert_true(e instanceof C, 'should have created an element');
t.done();
}));
} else {
// The child frame.
class C extends HTMLElement {}
window.customElements.define('a-a', C);
window.parent.C = C;
destroyContext();
done();
}
})();
</script>

View File

@@ -1,65 +0,0 @@
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="spec/resources/custom-elements-helpers.js"></script>
<body>
<script>
'use strict';
test_with_window((w) => {
let doc = w.document;
let e = doc.createElement('a-a');
doc.body.appendChild(e);
var misbehave = true;
var invocations = [];
class X extends w.HTMLElement {
constructor() {
if (misbehave) {
misbehave = false;
invocations.push('misbehaving');
return new X();
}
super();
invocations.push(this);
}
}
w.customElements.define('a-a', X);
assert_array_equals(invocations, ['misbehaving', e],
'returning the existing element should have succeeded');
}, 'HTMLElement constructor: poach but return upgrade candidate');
test_with_window((w) => {
let doc = w.document;
let e = doc.createElement('a-a');
doc.body.appendChild(e);
var misbehave = true;
var invocations = [];
var poacher;
class X extends w.HTMLElement {
constructor() {
if (misbehave) {
misbehave = false;
poacher = new X();
}
try {
super();
invocations.push(this);
} catch (e) {
invocations.push(e);
}
}
}
w.customElements.define('a-a', X);
assert_equals(invocations.length, 2,
'the constructor should have been invoked once for upgrade ' +
'and once for the recursive call to "new"');
assert_equals(poacher, e,
'the recursive "new" should steal the upgrade candidate');
assert_equals(poacher, invocations[0],
'the recursize "new" should happen first');
assert_true(invocations[1] instanceof w.DOMException,
'the super call should have thrown a DOMException');
assert_equals(invocations[1].name, 'InvalidStateError',
'the exception should be an InvalidStateError');
}, 'HTMLElement constructor: poach upgrade candidate');
</script>

View File

@@ -1,42 +0,0 @@
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="../fast/dom/custom/testutils.js"></script>
<body>
<script>
'use strict';
// TODO(dominicc): Port fork() etc. to work with testharness, then
// remove these.
function debug() {}
function finishJSTest() {}
(() => {
if (fork()) {
// The controlling parent frame.
let t = async_test('retrieving the prototype destroys the context');
let watcher = new EventWatcher(t, window, 'message');
watcher.wait_for('message').then(t.step_func((event) => {
assert_equals(event.data, 'PASS destroyed context');
return watcher.wait_for('message');
})).then(t.step_func((event) => {
assert_equals(event.data, 'PASS child done');
t.done();
}));
} else {
// The child frame.
let BadConstructor = (function () {}).bind({});
Object.defineProperty(BadConstructor, 'prototype', {
get() {
destroyContext();
return new Object();
}
});
window.customElements.define('x-x', BadConstructor);
done();
}
})();
</script>

View File

@@ -1,65 +0,0 @@
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<body>
<script>
'use strict';
(() => {
if (!window.internals) {
// Requires observeGC
return;
}
promise_test((t) => {
var observation = null;
(() => {
let f = document.createElement('iframe');
f.srcdoc = 'content';
document.body.appendChild(f);
let w = f.contentWindow;
var X = new Object;
X.a = [f, w, w.customElements, w.document];
X.a.forEach((o) => o.x = X);
observation = internals.observeGC(X);
f.remove();
})();
return Promise.resolve()
.then(() => {
gc(); gc();
assert_true(observation.wasCollected,
'cyclic references between objects and DOM wrappers should ' +
'not leak');
});
}, 'Sanity check ordinary objects are not leaking');
promise_test((t) => {
var observation = null;
(() => {
let f = document.createElement('iframe');
f.srcdoc = 'content';
document.body.appendChild(f);
let w = f.contentWindow;
class X extends HTMLElement {}
X.a = [f, w, w.customElements, w.document];
X.a.forEach((o) => o.x = X);
w.customElements.define('a-a', X);
observation = internals.observeGC(X);
f.remove();
})();
return Promise.resolve()
.then(() => {
gc(); gc();
assert_true(observation.wasCollected,
'defining a custom element should not leak its constructor');
});
}, 'Defining custom elements does not leak');
})();
</script>

View File

@@ -1,82 +0,0 @@
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<link id="import1" rel="import" href="resources/adopted-nodes.html">
<body>
<b-b id="x"></b-b>
<b-b id="y"></b-b>
<b-b id="z"></b-b>
</body>
<script>
'use strict';
test(() => {
let reactions = [];
customElements.define('a-a', class extends HTMLElement {
adoptedCallback() {
reactions.push(this);
}
});
let importDoc = import1.import;
let a = importDoc.querySelector('#a');
assert_equals(a.ownerDocument, importDoc);
document.body.appendChild(a);
assert_equals(a.ownerDocument, document, 'After appendChild(), node document should change.');
let b = importDoc.querySelector('#b');
assert_equals(b.ownerDocument, importDoc);
let b2 = document.importNode(b);
assert_equals(b.ownerDocument, importDoc, 'importNode() should not change node document.');
assert_equals(b2.ownerDocument, document,
'importNode() should copy node from one document to another.');
let c = importDoc.querySelector('#c');
assert_equals(c.ownerDocument, importDoc);
let c2 = document.adoptNode(c);
assert_equals(c.ownerDocument, document, 'adoptNode should have changed node document.');
assert_equals(c, c2, 'adoptNode() should move node from another document.');
assert_array_equals(importDoc.querySelectorAll('a-a'), [b],
'Only <a-a#b> should remain in the imported document.');
assert_array_equals(reactions, [a, c],
'appendChild() and adoptNode() should cause adoptedCallback.');
}, 'adoptedCallback should be invoked when moving custom element from import to master document');
test(() => {
let reactions = [];
customElements.define('b-b', class extends HTMLElement {
adoptedCallback() {
reactions.push(this);
}
});
let importDoc = import1.import;
let x = document.querySelector('#x');
assert_equals(x.ownerDocument, document);
importDoc.body.appendChild(x);
assert_equals(x.ownerDocument, importDoc, 'After appendChild(), node document should change.');
let y = document.querySelector('#y');
assert_equals(y.ownerDocument, document);
let y2 = importDoc.importNode(y);
assert_equals(y.ownerDocument, document, 'importNode() should not change node document.');
assert_equals(y2.ownerDocument, importDoc,
'importNode() should copy node from one document to another.');
let z = document.querySelector('#z');
assert_equals(z.ownerDocument, document);
let z2 = importDoc.adoptNode(z);
assert_equals(z.ownerDocument, importDoc, 'adoptNode should have changed node document.');
assert_equals(z, z2, 'adoptNode() should move node from another document.');
assert_array_equals(document.querySelectorAll('b-b'), [y],
'Only <b-b#y> should remain in the imported document.');
assert_array_equals(reactions, [x, z],
'appendChild() and adoptNode() should cause adoptedCallback.');
}, 'adoptedCallback should be invoked when moving custom element from master document to import');
</script>

View File

@@ -1,21 +0,0 @@
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../spec/resources/custom-elements-helpers.js"></script>
<link id="import1" rel="import" href="resources/async-component.html" async>
<a-a></a-a>
<script>
'use strict';
promise_test(() => {
return customElements.whenDefined('a-a').then(() => {
let a = document.querySelector('a-a');
assert_is_upgraded(a, AsyncComponent,
'<async-component> in this document should have been upgraded.');
let b = import1.import.querySelector('a-a');
assert_is_upgraded(b, AsyncComponent,
'<async-component> in imported document should have been upgraded.');
});
}, 'custom elements definition in async import should work.');
</script>

View File

@@ -1,30 +0,0 @@
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../spec/resources/custom-elements-helpers.js"></script>
<script>
'use strict';
let constructors = [];
</script>
<link id="import1" rel="import" href="resources/async-nested-component.html" async>
<script>
'use strict';
async_test((test) => {
import1.onload = test.step_func_done(() => {
let n1 = import1.import.querySelector('a-a');
let n2 = import1.import.querySelector('b-b');
let n3 = import1.import.querySelector('c-c');
assert_is_upgraded(n1, NestedLevel1, 'nested-level1 in import should be custom');
assert_is_upgraded(n2, NestedLevel2, 'nested-level2 in import should be custom');
assert_is_upgraded(n3, NestedLevel3, 'nested-level3 in import should be custom');
// As subimports are sync, the upgrade order should be the order of script execution.
let types = constructors.map(e => e.type);
assert_array_equals(types, ['nested-level3', 'nested-level2', 'nested-level1'],
'upgrade order should be the order of script execution in imports');
}, 'top-level async import should properly run dependent subimports and get elements defined.');
}, 'custom elements defined in nested imports from an async import should work.');
</script>

View File

@@ -1,53 +0,0 @@
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<link id="import1" rel="import" href="resources/attribute-upgrade.html">
<script>
'use strict';
let reactions = [];
customElements.define('a-a', class extends HTMLElement {
static get observedAttributes() { return ['attr']; }
attributeChangedCallback() {
reactions.push(this);
}
});
</script>
<link id="import2" rel="import" href="resources/attribute-parsercreate.html">
<script>
'use strict';
test(() => {
let importDoc1 = import1.import;
let importDoc2 = import2.import;
let a = importDoc1.querySelector('#a');
let b = importDoc1.querySelector('#b');
let c = importDoc2.querySelector('#c');
let d = importDoc2.querySelector('#d');
assert_array_equals(reactions, [b, d],
'attributeChangedCallback should be called for both upgrade and create.');
reactions = [];
a.setAttribute('attr', 'a');
b.setAttribute('attr', 'b');
c.setAttribute('attr', 'c');
d.setAttribute('attr', 'd');
assert_array_equals(reactions, [a, b, c, d],
'attributeChangedCallback should be called for setAttribute().');
reactions = [];
d.removeAttribute('attr', 'd');
c.removeAttribute('attr', 'c');
b.removeAttribute('attr', 'b');
a.removeAttribute('attr', 'a');
assert_array_equals(reactions, [d, c, b, a],
'attributeChangedCallback should be called for removeAttribute().');
}, 'attributeChangedCallback should be invoked for elements in import');
</script>

View File

@@ -1,21 +0,0 @@
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script>
'use strict';
let constructors = [];
</script>
<a-a></a-a>
<b-b></b-b>
<link rel="import" href="resources/circular-level1.html">
<script>
'use strict';
test(() => {
// At the point of imports, <a-a> and <b-b> are already parsed and will be
// upgraded when the definitions are ready.
assert_array_equals(constructors, ['circular-level2', 'circular-level1'],
'The constructor order should be script execution order of definition.');
}, 'custom element definitions in circularly-dependent imports should work.');
</script>

View File

@@ -1,4 +0,0 @@
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<link rel="import" href="resources/create-element.html">

View File

@@ -1,21 +0,0 @@
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<link id="import1" rel="import" href="resources/import-custom.html">
<script>
'use strict';
test(() => {
let a = import1.import.querySelector('x-x');
assert_equals(Object.getPrototypeOf(a), HTMLElement.prototype,
'<x-x> should not have been upgreaded yet.');
import1.remove();
assert_equals(document.querySelector('link[rel=import]'), null,
'imported document should be detached');
customElements.define('x-x', class extends HTMLElement{});
assert_equals(Object.getPrototypeOf(a), HTMLElement.prototype,
'detached <x-x> should not have been upgreaded.');
}, 'custom elements in detached imports should not upgrade.');
</script>

View File

@@ -1,4 +0,0 @@
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<link rel="import" href="resources/inner-html.html">

View File

@@ -1,20 +0,0 @@
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script>
'use strict';
let constructors = [];
</script>
<a-a></a-a>
<b-b></b-b>
<c-c></c-c>
<link rel="import" href="resources/nested-level1.html">
<script>
'use strict';
test(() => {
let types = constructors.map(e => e.type);
assert_array_equals(types, ['nested-level3', 'nested-level2', 'nested-level1'],
'The constructor order should be script execution order of definition.');
}, 'custom element definitions in nested imports should work.');
</script>

View File

@@ -1,4 +0,0 @@
<!DOCTYPE html>
<a-a id="a"></a-a>
<a-a id="b"></a-a>
<a-a id="c"></a-a>

View File

@@ -1,8 +0,0 @@
<!DOCTYPE html>
<a-a></a-a>
<script>
'use strict';
class AsyncComponent extends HTMLElement {}
customElements.define('a-a', AsyncComponent);
</script>

View File

@@ -1,5 +0,0 @@
<!DOCTYPE html>
<link rel="import" href="nested-level1.html">
<a-a></a-a>
<b-b></b-b>
<c-c></c-c>

View File

@@ -1,3 +0,0 @@
<!DOCTYPE html>
<a-a id="c"></a-a>
<a-a id="d" attr="attr"></a-a>

View File

@@ -1,3 +0,0 @@
<!DOCTYPE html>
<a-a id="a"></a-a>
<a-a id="b" attr="attr"></a-a>

View File

@@ -1,12 +0,0 @@
<!DOCTYPE html>
<link rel="import" href="circular-level2.html">
<script>
'use strict';
customElements.define('a-a', class extends HTMLElement {
constructor() {
super();
constructors.push('circular-level1');
}
});
</script>

View File

@@ -1,12 +0,0 @@
<!DOCTYPE html>
<link rel="import" href="circular-level1.html">
<script>
'use strict';
customElements.define('b-b', class extends HTMLElement {
constructor() {
super();
constructors.push('circular-level2');
}
});
</script>

View File

@@ -1,38 +0,0 @@
<script>
'use strict';
let constructors = [];
test(() => {
assert_equals(constructors.length, 0);
class MyElement extends HTMLElement {
constructor() {
super();
constructors.push(this);
}
}
customElements.define('a-a', MyElement);
// createElement should synchronously call constructor.
let a = document.createElement('a-a');
assert_equals(constructors.length, 1);
assert_equals(a.ownerDocument, document);
let importDoc = document.currentScript.ownerDocument;
// TODO(kochi): crbug.com/640465 createElement returns wrong ownerDocument
// createElement should work in imported document.
let b = importDoc.createElement('a-a')
assert_equals(b.ownerDocument, importDoc);
assert_equals(constructors.length, 2);
// new MyElement() should synchronously call constructor.
let c = new MyElement();
assert_equals(c.ownerDocument, document);
assert_equals(constructors.length, 3);
assert_array_equals(constructors, [a, b, c]);
}, 'createElement() and new MyElement should work in imported document.');
</script>

View File

@@ -1,2 +0,0 @@
<!DOCTYPE html>
<x-x></x-x>

View File

@@ -1,28 +0,0 @@
<!DOCTYPE html>
<script src="../../spec/resources/custom-elements-helpers.js"></script>
<div id="sandbox"></div>
<script>
'use strict';
test(() => {
let constructors = [];
assert_equals(constructors.length, 0);
class MyElement extends HTMLElement {
constructor() {
super();
constructors.push(this);
}
}
customElements.define('a-a', MyElement);
let importDoc = document.currentScript.ownerDocument;
let sandbox = importDoc.querySelector('#sandbox');
sandbox.innerHTML = '<a-a></a-a>';
let a = importDoc.querySelector('a-a');
assert_is_upgraded(a, MyElement, '<a-a> should be upgraded.');
assert_array_equals(constructors, [a]);
}, 'innerHTML with custom elements should work in imported document.');
</script>

View File

@@ -1,14 +0,0 @@
<!DOCTYPE html>
<link rel="import" href="nested-level2.html">
<script>
'use strict';
class NestedLevel1 extends HTMLElement {
constructor() {
super();
constructors.push({element: this, type: 'nested-level1'});
}
}
customElements.define('a-a', NestedLevel1);
</script>

View File

@@ -1,13 +0,0 @@
<!DOCTYPE html>
<link rel="import" href="nested-level3.html">
<script>
'use strict';
class NestedLevel2 extends HTMLElement {
constructor() {
super();
constructors.push({element: this, type: 'nested-level2'});
}
}
customElements.define('b-b', NestedLevel2);
</script>

View File

@@ -1,12 +0,0 @@
<!DOCTYPE html>
<script>
'use strict';
class NestedLevel3 extends HTMLElement {
constructor() {
super();
constructors.push({element: this, type: 'nested-level3'});
}
}
customElements.define('c-c', NestedLevel3);
</script>

View File

@@ -1,13 +0,0 @@
<!DOCTYPE html>
<x-x id="aa"></x-x>
<y-y id="bb"></y-y>
<script>
'use strict';
customElements.define('x-x', class extends HTMLElement {
constructor() {
super();
reactions.push({ type: 'constructor', element: this });
}
});
</script>

View File

@@ -1,45 +0,0 @@
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script>
'use strict';
let reactions = [];
let test = async_test('Constructor should run in the order elements are created');
test.step(() => {
customElements.define('x-x', class extends HTMLElement {
constructor() {
super();
reactions.push(this);
}
});
assert_array_equals(reactions, [], 'Should not have parsed <x-x> yet');
});
</script>
<x-x></x-x>
<script>
'use strict';
test.step(() => {
assert_equals(reactions.length, 1, 'Parser should invoke the custom element constructor');
});
let import1 = document.createElement('link');
import1.rel = 'import';
import1.href = 'resources/import-custom.html';
// TODO(kochi): crbug.com/640465 synchronous creation fails in imports.
import1.onload = test.step_func_done(() => {
let elementInMaster = document.querySelector('x-x');
let elementInImport = import1.import.querySelector('x-x');
assert_array_equals(reactions, [elementInMaster, elementInImport],
'Constructor should run in the order elements are created');
});
document.head.appendChild(import1);
</script>

View File

@@ -1,30 +0,0 @@
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script>
'use strict';
let reactions = [];
</script>
<x-x id="a"></x-x>
<y-y id="x"></y-y>
<link id="import1" rel="import" href="resources/upgrade.html">
<x-x id="b"></x-x>
<y-y id="y"></y-y>
<script>
'use strict'
async_test((test) => {
window.onload = test.step_func_done(() => {
customElements.define('y-y', class extends HTMLElement {
constructor() {
super();
reactions.push({ type: 'constructor', element: this });
}
});
let elements = reactions.map(e => e.element.id);
assert_array_equals(elements, ['a', 'aa', 'b', 'x', 'bb', 'y']);
}, 'Upgrade of custom elements should happen in document order.');
});
</script>

View File

@@ -1,80 +0,0 @@
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="spec/resources/custom-elements-helpers.js"></script>
<body>
<script>
'use strict';
(() => {
if (!window.testRunner) {
// Requires evaluateScriptInIsolatedWorld
return;
}
promise_test((t) => {
return create_window_in_test(t)
.then((w) => {
function in_isolated_world() {
let f = document.querySelector('iframe');
let w = f.contentWindow;
let e = w.document.body.lastChild;
w.postMessage(
e.localName == 'a-a' &&
Object.getPrototypeOf(e) === w.HTMLElement.prototype,
'*');
}
var p = new Promise((resolve) => {
w.addEventListener('message', t.step_func((event) => {
assert_true(event.data,
'the custom element prototype should not appear in ' +
'isolated worlds');
resolve();
}));
});
class X extends w.HTMLElement {}
w.customElements.define('a-a', X);
w.document.body.appendChild(new X);
testRunner.evaluateScriptInIsolatedWorld(
1,
`(${in_isolated_world.toString()})();`);
return p;
});
}, 'Retrieve a custom element in an isolated world');
// TODO(dominicc): Test retrieving a custom element that has not been wrapped
// before in any world, when upgrades are implemented.
promise_test((t) => {
return create_window_in_test(t)
.then((w) => {
function in_isolated_world() {
let f = document.querySelector('iframe');
let w = f.contentWindow;
w.postMessage(`customElements=${w.customElements}`, '*');
}
var p = new Promise((resolve) => {
w.addEventListener('message', t.step_func((event) => {
assert_equals(event.data,
'customElements=null',
'the customElements property should be null ' +
'in isolated worlds');
resolve();
}));
});
testRunner.evaluateScriptInIsolatedWorld(
1,
`(${in_isolated_world.toString()})();`);
return p;
});
}, 'No custom elements registry in isolated worlds');
})();
</script>

View File

@@ -1,57 +0,0 @@
<!DOCTYPE html>
<title>Custom Elements: adopt node</title>
<link rel="help" href="https://dom.spec.whatwg.org/#concept-node-adopt">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
<body>
<script>
'use strict'
// 3.2 For each inclusiveDescendant in nodes shadow-including inclusive descendants that is a custom
// element, enqueue a custom element callback reaction with inclusiveDescendant,
// callback name "adoptedCallback", and an empty argument list.
promise_test((t) => {
return Promise.all([
create_window_in_test(t),
create_window_in_test(t)])
.then(([w1, w2]) => {
let invocations = [];
class X extends w1.HTMLElement {
adoptedCallback() { invocations.push(['adopted', this, arguments]); }
}
w1.customElements.define('a-a', X);
let a = w1.document.createElement('a-a');
w2.document.adoptNode(a);
assert_array_equals_callback_invocations(invocations, [ ['adopted', a, [w1.document, w2.document]] ]);
});
}, 'adopting a custom element to the different document should enqueue an adoptedCallback reaction');
// 3.3 For each inclusiveDescendant in nodes shadow-including inclusive descendants, in
// shadow-including tree order, run the adopting steps with inclusiveDescendant and oldDocument.
promise_test((t) => {
return Promise.all([
create_window_in_test(t),
create_window_in_test(t)])
.then(([w1, w2]) => {
w1.document.body.innerHTML = `
<a-a id="a">
<p>
<a-a id="b"></a-a>
<a-a id="c"></a-a>
</p>
<a-a id="d"></a-a>
</a-a>`;
let invocations = [];
class X extends w1.HTMLElement {
adoptedCallback() { invocations.push(this); }
}
w1.customElements.define('a-a', X);
let a = w1.document.getElementById("a");
w2.document.adoptNode(a);
assert_array_equals(['a', 'b', 'c', 'd'], invocations.map((e) => e.id),
'four elements should have been removed in doc order');
});
}, 'shadow-including inclusive descendants should be adopted in shadow-including tree order');
</script>
</body>

View File

@@ -1,212 +0,0 @@
<!DOCTYPE html>
<title>Custom Elements: Create an element when definition is non-null and synchronous flag not set</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
<body>
<script>
'use strict';
(() => {
// "Upgrade an element"
// https://html.spec.whatwg.org/multipage/scripting.html#upgrades
// 1. For each attribute in element's attribute list, in order, enqueue a
// custom element callback reaction with element, callback name
// "attributeChangedCallback", and an argument list containing attribute's
// local name, null, attribute's value, and attribute's namespace.
// 2. If element is currently in a shadow-including document, then enqueue a
// custom element callback reaction with element, callback name
// "connectedCallback", and an empty argument list.
const constructor = 'constructor';
const connected = 'connected';
const disconnected = 'disconnected';
const attributeChanged = 'attributeChanged';
function define_logger(w, observedAttributes) {
let logs = [];
w.customElements.define('a-a', class extends w.HTMLElement {
constructor() { super(); logs.push([constructor, this, arguments]); }
connectedCallback() { logs.push([connected, this, arguments]); }
disconnectedCallback() { logs.push([disconnected, this, arguments]); }
static get observedAttributes() { return observedAttributes; }
attributeChangedCallback() { logs.push([attributeChanged, this, arguments]); }
});
return logs;
}
function assert_log_is_type(logs, i, type, element, argv) {
assert_equals(logs[i][0], type, `[${i}] should be ${type}`);
assert_equals(logs[i][1], element, `this in ${type} should be the element`);
if (argv) {
assert_array_equals(logs[i][2], argv, `${type} should have arguments ${argv}`);
} else {
assert_equals(logs[i][2].length, 0, `${type} should have no arguments`);
}
}
// test_with_window(w => {
// let document = w.document;
// let element = document.createElement('a-a');
// document.body.appendChild(element);
// let logs = define_logger(w);
// assert_log_is_type(logs, 0, constructor, element);
// assert_log_is_type(logs, 1, connected, element);
// assert_equals(logs.length, 2);
// }, 'upgrade should enqueue connectedCallback');
//
// test_with_window(w => {
// let document = w.document;
// let element = document.createElement('a-a');
// element.setAttribute('x', '1');
// element.setAttribute('y', '2');
// element.setAttribute('z', '3');
// document.body.appendChild(element);
// let logs = define_logger(w, ['x', 'y']);
// assert_log_is_type(logs, 0, constructor, element);
// assert_log_is_type(logs, 1, attributeChanged, element, ['x', null, '1', null]);
// assert_log_is_type(logs, 2, attributeChanged, element, ['y', null, '2', null]);
// assert_log_is_type(logs, 3, connected, element);
// assert_equals(logs.length, 4);
// }, 'upgrade should enqueue attributeChangedCallback and connectedCallback');
//
// test_with_window(w => {
// let document = w.document;
// let element = document.createElement('a-a');
// element.setAttribute('x', '1');
// document.body.appendChild(element);
// w.customElements.flush();
// let logs = define_logger(w, ['x', 'y']);
// logs.length = 0;
// element.setAttribute('z', '0');
// element.setAttribute('y', '2');
// element.setAttribute('x', '9');
// assert_log_is_type(logs, 0, attributeChanged, element, ['y', null, '2', null]);
// assert_log_is_type(logs, 1, attributeChanged, element, ['x', '1', '9', null]);
// assert_equals(logs.length, 2);
// }, 'setAttribute should enqueue attributeChangedCallback');
test_with_window(w => {
let document = w.document;
let element = document.createElement('a-a');
document.body.appendChild(element);
w.customElements.flush();
let logs = define_logger(w, ['style']);
logs.length = 0;
w.customElements.flush();
element.style.color = 'red';
w.customElements.flush();
assert_equals(logs.length, 1, 'A');
assert_log_is_type(logs, 0, attributeChanged, element, ['style', null, 'color: red;', null]);
element.style.color = 'green';
w.customElements.flush();
assert_equals(logs.length, 2, 'B');
assert_log_is_type(logs, 1, attributeChanged, element, ['style', 'color: red;', 'color: green;', null]);
element.style.color = '';
w.customElements.flush();
assert_equals(logs.length, 3, 'C');
assert_log_is_type(logs, 2, attributeChanged, element, ['style', 'color: green;', null, null]);
}, 'style.color should enqueue attributeChangedCallback for style attribute');
// test_with_window(w => {
// let document = w.document;
// let element = document.createElement('a-a');
// document.body.appendChild(element);
// let logs = define_logger(w, ['style']);
//
// logs.length = 0;
// element.style.cssText = 'color: red';
// assert_equals(logs.length, 1);
// assert_log_is_type(logs, 0, attributeChanged, element, ['style', null, 'color: red;', null]);
// }, 'style.cssText should enqueue attributeChangedCallback for style attribute');
//
// test_with_window(w => {
// let document = w.document;
// let element = document.createElement('a-a');
// document.body.appendChild(element);
// let logs = define_logger(w, ['style']);
//
// logs.length = 0;
// element.style.setProperty('color', 'red');
// assert_equals(logs.length, 1);
// assert_log_is_type(logs, 0, attributeChanged, element, ['style', null, 'color: red;', null]);
//
// element.style.removeProperty('color', 'red');
// assert_equals(logs.length, 2);
// assert_log_is_type(logs, 1, attributeChanged, element, ['style', 'color: red;', null, null]);
// }, 'style.setProperty/removeProperty should enqueue attributeChangedCallback for style attribute');
//
// test_with_window(w => {
// let document = w.document;
// let element = document.createElement('a-a');
// document.body.appendChild(element);
// let logs = define_logger(w, ['style']);
//
// logs.length = 0;
// element.style.cssFloat = 'left';
// assert_equals(logs.length, 1);
// assert_log_is_type(logs, 0, attributeChanged, element, ['style', null, 'float: left;', null]);
// }, 'style.cssFloat should enqueue attributeChangedCallback for style attribute');
//
// test_with_window(w => {
// let document = w.document;
// let element = document.createElement('a-a');
// document.body.appendChild(element);
// let logs = define_logger(w, ['lang']);
//
// logs.length = 0;
// element.lang = 'ja-jp';
// assert_equals(logs.length, 1);
// assert_log_is_type(logs, 0, attributeChanged, element, ['lang', null, 'ja-jp', null]);
// }, 'lang property setter should enqueue attributeChangedCallback for lang attribute');
//
// test_with_window(w => {
// let document = w.document;
// let element = document.createElement('a-a');
// element.setAttribute('x', '1');
// document.body.appendChild(element);
// let logs = define_logger(w, ['x']);
//
// logs.length = 0;
// element.removeAttribute('x');
// w.customElements.flush();
// assert_log_is_type(logs, 0, attributeChanged, element, ['x', '1', null, null]);
// assert_equals(logs.length, 1);
// }, 'removeAttribute should enqueue attributeChangedCallback');
//
// test_with_window(w => {
// let document = w.document;
// let element = document.createElement('a-a');
// document.body.appendChild(element);
// let logs = define_logger(w, ['x']);
// logs.length = 0;
//
// element.setAttributeNS('urn:foo', 'x', '1');
// assert_log_is_type(logs, 0, attributeChanged, element, ['x', null, '1', 'urn:foo']);
// assert_equals(logs.length, 1);
//
// element.removeAttributeNS('urn:foo', 'x');
// assert_log_is_type(logs, 1, attributeChanged, element, ['x', '1', null, 'urn:foo']);
// assert_equals(logs.length, 2);
// }, 'attributeChangedCallback should transmit namespaces');
//
// test_with_window(w => {
// let document = w.document;
// let element = document.createElement('a-a');
// document.body.appendChild(element);
// let logs = define_logger(w);
//
// logs.length = 0;
// element.remove();
// w.customElements.flush();
// assert_log_is_type(logs, 0, disconnected, element);
// assert_equals(logs.length, 1);
// }, 'remove should enqueue disconnectedCallback');
})();
</script>
</body>

View File

@@ -1,77 +0,0 @@
<!DOCTYPE html>
<title>Custom Elements: Constructor Tests</title>
<link rel="help" href="https://html.spec.whatwg.org/multipage/dom.html#elements-in-the-dom">
<meta name="author" title="Dominic Cooney" href="mailto:dominicc@chromium.org">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
<body>
<script>
'use strict';
test_with_window((w) => {
assert_throws(TypeError.prototype, () => {
w.HTMLElement();
}, 'calling the HTMLElement constructor should throw a TypeError');
}, 'HTMLElement constructor, invoke');
test_with_window((w) => {
assert_throws(TypeError.prototype, () => {
new w.HTMLElement();
}, 'invoking the HTMLElement constructor with a construct call should ' +
'throw a TypeError');
}, 'HTMLElement constructor, construct');
test_with_window((w) => {
class X extends w.HTMLElement {}
w.customElements.define('a-a', X);
assert_throws(TypeError.prototype, () => {
X();
}, 'calling a custom element constructor should throw a TypeError');
}, 'Custom element constructor, invoke');
test_with_window((w) => {
var num_constructor_invocations = 0;
class C extends w.HTMLElement {
constructor() {
super();
num_constructor_invocations++;
}
}
w.customElements.define('a-a', C);
let e = new C();
assert_true(e instanceof w.HTMLElement,
'the element should be an HTMLElement');
assert_equals(e.localName,
'a-a',
'the element tag name should be "a-a"');
assert_equals(e.namespaceURI,
'http://www.w3.org/1999/xhtml',
'the element should be in the HTML namespace');
assert_equals(e.prefix,
null,
'the element name should not have a prefix');
assert_equals(e.ownerDocument,
w.document,
'the element should be owned by the registry\'s associated ' +
'document');
assert_equals(num_constructor_invocations,
1,
'the constructor should have been invoked once');
}, 'Custom element constructor, construct');
test_with_window((w) => {
class C extends w.HTMLElement { }
class D extends C {}
w.customElements.define('a-a', C); // Note: Not D.
assert_throws(TypeError.prototype, () => {
new D();
}, 'constructing a custom element with a new.target with no registry ' +
'entry should throw a TypeError');
assert_throws(TypeError.prototype, () => {
Reflect.construct(C, [], 42);
}, 'constructing a custom element with a non-object new.target should ' +
'throw a TypeError');
}, 'Custom element constructor, construct invalid new.target');
</script>

View File

@@ -1,79 +0,0 @@
<!DOCTYPE html>
<title>Custom Elements: Create an element when definition is non-null and synchronous flag not set</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
<body>
<script>
'use strict';
// Create an element
// https://dom.spec.whatwg.org/#concept-create-element
// 6. If definition is non-null, then:
// 6.2. If the synchronous custom elements flag is not set:
(() => {
// customElements.define() upgrades existing elements
// with synchronous flag unset.
test_with_window(w => {
create_element_and_upgrade(w);
}, 'define() should upgrade existing elements');
function create_element_and_upgrade(w) {
let document = w.document;
let element = document.createElement('a-a');
document.body.appendChild(element);
assert_false('is_custom_constructed' in element, 'Constructor should not run before define()');
define(w);
assert_true(element.is_custom_constructed, 'Constructor should run after define()');
return element;
}
function define(w) {
w.customElements.define('a-a', class extends w.HTMLElement {
constructor() { super(); this.is_custom_constructed = true; }
});
}
// The "clone a node" concept is async.
// https://dom.spec.whatwg.org/#concept-node-clone
test_with_window(w => {
let element = create_element_and_upgrade(w);
let clone = element.cloneNode();
assert_true(clone.is_custom_constructed);
}, 'cloneNode() should run custom constructor');
// importNode() uses the same "clone a node" conecpt to clone the node.
test_with_window(w => {
define(w);
let document = w.document;
let another_document = document.implementation.createHTMLDocument();
let element_in_another_document = another_document.createElement('a-a');
let imported = document.importNode(element_in_another_document);
assert_true(imported.is_custom_constructed);
}, 'importNode() should run custom constructor');
// innerHTML/outerHTML setters use the fragment parser.
// https://w3c.github.io/DOM-Parsing/#dom-element-innerhtml
// Synchronous flag is unset if HTML fragment parsing algorithm.
// https://html.spec.whatwg.org/multipage/syntax.html#create-an-element-for-the-token
test_with_window(w => {
define(w);
let document = w.document;
let fragment = document.createElement('div');
document.body.appendChild(fragment);
fragment.innerHTML = '<a-a></a-a>';
assert_true(fragment.children[0].is_custom_constructed);
}, 'innerHTML setter should run custom constructor');
test_with_window(w => {
define(w);
let document = w.document;
let fragment = document.createElement('div');
document.body.appendChild(fragment);
fragment.outerHTML = '<a-a></a-a>';
assert_true(document.body.children[0].is_custom_constructed);
}, 'outerHTML setter should run custom constructor');
})();
</script>
</body>

View File

@@ -1,120 +0,0 @@
<!DOCTYPE html>
<title>Custom Elements: Create an element when definition is non-null and synchronous flag set</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
<body>
<script>
'use strict';
function assert_rethrown(func, description) {
assert_throws({ name: 'rethrown' }, () => {
const err = new Error('check this is rethrown');
err.name = 'rethrown';
func(err);
}, description);
}
const expectTypeError = TypeError.prototype;
const expectNotSupportedError = 'NOT_SUPPORTED_ERR';
// https://dom.spec.whatwg.org/#concept-create-element
// 6. If definition is non-null, then:
// 6.1. If the synchronous custom elements flag is set:
test_create_element_synchronous(
'createElement(): ',
(w, constructor, options) => {
w.customElements.define('a-a', constructor, options);
return w.document.createElement('a-a');
});
test_create_element_synchronous(
'createElementNS(): ',
(w, constructor, options) => {
w.customElements.define('a-a', constructor, options);
return w.document.createElementNS('http://www.w3.org/1999/xhtml', 'a-a');
});
function test_create_element_synchronous(description, define_and_create_element) {
test_with_window((w) => {
let is_constructed = false;
define_and_create_element(w, class extends w.HTMLElement {
constructor() { super(); is_constructed = true; }
});
assert_true(is_constructed, 'custom constructor ran');
}, `${description}Pre-flight check should succeed`);
test_with_window((w) => {
assert_rethrown(err => {
define_and_create_element(w, class extends w.HTMLElement {
constructor() { super(); throw err; }
});
});
}, `${description}6.1.2. Errors in Construct(C) should be rethrown`);
test_with_window((w) => {
assert_throws(expectTypeError, () => {
define_and_create_element(w, class {
constructor() {}
});
});
}, `${description}6.1.3. If result does not implement the HTMLElement interface, throw a TypeError`);
test_with_window((w) => {
assert_throws(expectNotSupportedError, () => {
define_and_create_element(w, class extends w.HTMLElement {
constructor() { super(); this.setAttribute('a', 'a'); }
});
});
}, `${description}6.1.4. If result\'s attribute list is not empty, then throw a NotSupportedError`);
test_with_window((w) => {
assert_throws(expectNotSupportedError, () => {
define_and_create_element(w, class extends w.HTMLElement {
constructor() { super(); this.appendChild(w.document.createElement('a')); }
});
}, 'should throw if it has a child element');
assert_throws(expectNotSupportedError, () => {
define_and_create_element(w, class extends w.HTMLElement {
constructor() { super(); this.appendChild(w.document.createTextNode('a')); }
});
}, 'should throw if it has a child text node');
}, `${description}6.1.5. If result has children, then throw a NotSupportedError`);
test_with_window((w) => {
assert_throws(expectNotSupportedError, () => {
define_and_create_element(w, class extends w.HTMLElement {
constructor() { super(); w.document.createElement('div').appendChild(this); }
});
});
}, `${description}6.1.6. If result\'s parent is not null, then throw a NotSupportedError`);
test_with_window((w) => {
assert_throws(expectNotSupportedError, () => {
define_and_create_element(w, class extends w.HTMLElement {
constructor() { super(); return w.document.implementation.createHTMLDocument().createElement('div'); }
});
});
}, `${description}6.1.7. If result\'s node document is not document, then throw a NotSupportedError`);
/* This is not testsable today, see https://github.com/whatwg/html/issues/1402
test_with_window((w) => {
assert_throws(expectNotSupportedError, () => {
define_and_create_element(w, class extends w.HTMLElement {
constructor() { super(); return w.document.createElementNS('http://www.w3.org/2000/svg', 'g'); }
});
});
}, `${description}6.1.8. If result\'s namespace is not the HTML namespace, then throw a NotSupportedError`);
*/
test_with_window((w) => {
assert_throws(expectNotSupportedError, () => {
define_and_create_element(w, class extends w.HTMLElement {
constructor() { super(); return document.createElement('div'); }
});
});
}, `${description}6.1.9. If result\'s local name is not equal to localName, then throw a NotSupportedError`);
}
</script>
</body>

View File

@@ -1,25 +0,0 @@
<!DOCTYPE html>
<title>Custom Elements: create an element inside a template </title>
<link rel="help" href="https://dom.spec.whatwg.org/#concept-create-element">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
<iframe id="iframe"></iframe>
<script>
'use strict';
iframe.srcdoc = `<template id="test"><a-a></a-a></template>`;
setup({ explicit_done: true });
iframe.onload = () => {
let doc = iframe.contentDocument;
let w = doc.defaultView;
let tmpl = doc.querySelector('#test');
let element = tmpl.content.querySelector('a-a');
w.customElements.define('a-a', class extends w.HTMLElement {});
test(function () {
assert_true(element.matches(':not(:defined)'));
assert_true(element instanceof w.HTMLElement);
}, 'Custom element state in template content should be "not defined"');
done();
};
</script>

View File

@@ -1,44 +0,0 @@
<!DOCTYPE html>
<!--
TODO(yosin): We should upstream to wpt test.
This file is taken from https://github.com/kojiishi/web-platform-tests/blob/53908d773012edf931047674f7afe3975bc1820f/custom-elements/custom-elements-registry/get.html
-->
<title>Custom Elements: CustomElementRegistry.get</title>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<body>
<div id="log"></div>
<script>
'use strict';
(() => {
// https://html.spec.whatwg.org/multipage/scripting.html#dom-customelementregistry-whendefined
// Use window from iframe to isolate the test.
function setup() {
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const testWindow = iframe.contentWindow;
const customElements = testWindow.customElements;
if (!customElements)
return Promise.reject('This test requires window.customElements');
if (!('get' in customElements))
return Promise.reject('This test requires window.customElements.get');
return Promise.resolve(customElements);
}
promise_test(() => setup()
.then(customElements => {
// 1. If this CustomElementRegistry contains an entry with name name,
// then return that entry's constructor.
const name = 'test-get-existing';
class C extends HTMLElement {};
customElements.define(name, C);
assert_equals(customElements.get(name), C, 'get() returns the constructor')
return Promise.resolve(customElements);
}).then(customElements => {
// 2. Otherwise, return undefined.
assert_equals(customElements.get('test-get-not-defined'), undefined,
'get() returns undefined for not-defined name');
}).catch(reason => { throw reason }));
})();
</script>
</body>

View File

@@ -1,101 +0,0 @@
<!DOCTYPE html>
<title>Custom Elements: CustomElementRegistry.whenDefined</title>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../resources/custom-elements-helpers.js"></script>
<body>
<div id="log"></div>
<script>
'use strict';
(() => {
// https://html.spec.whatwg.org/multipage/scripting.html#dom-customelementregistry-whendefined
// Use window from iframe to isolate the test.
function setup() {
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const testWindow = iframe.contentWindow;
const customElements = testWindow.customElements;
if (!customElements)
return Promise.reject('This test requires window.customElements');
if (!('whenDefined' in customElements))
return Promise.reject('This test requires window.customElements.whenDefined');
return Promise.resolve(customElements);
}
const kNameToBeDefined = 'x-foo';
const kNameNotDefined = 'x-bar';
let beforeDefined = false;
let notDefined = true;
let afterDefined = false;
promise_test(() => setup()
.then(customElements => {
// 4. If map does not contain an entry with key name, create an entry in
// map with key name and whose value is a new promise.
const promiseBeforeDefined = customElements.whenDefined(kNameToBeDefined);
promiseBeforeDefined.then(() => beforeDefined = true);
// 5. Let promise be the value of the entry in map with key name.
// 6. Return promise
assert_equals(promiseBeforeDefined,
customElements.whenDefined(kNameToBeDefined),
'There is only one promise defined before.');
const promiseNotDefined = customElements.whenDefined(kNameNotDefined);
promiseNotDefined.then(() => notDefined = false);
assert_not_equals(promiseBeforeDefined, promiseNotDefined,
'whenDefined() returns different promises for different names.');
customElements.define(kNameToBeDefined, class {});
// 3. If this CustomElementRegistry contains an entry with name name,
// then return a new promise resolved with undefined and abort these
// steps.
const promiseAfterDefined = customElements.whenDefined(kNameToBeDefined);
promiseAfterDefined.then(() => afterDefined = true);
assert_not_equals(promiseBeforeDefined, promiseAfterDefined,
'When name is defined, we should have a new promise.');
assert_not_equals(promiseAfterDefined,
customElements.whenDefined(kNameToBeDefined),
'Once name is defined, whenDefined() always returns a new promise.');
return customElements;
}).then(customElements => {
assert_true(beforeDefined, 'promise before defined should be resolved.');
assert_true(afterDefined, 'promise after defined should be resolved.');
assert_true(notDefined, 'promise for not defined name should not be resolved.');
}).catch(reason => { throw reason }));
// Calling whenDefined() with invalid names should throw "SyntaxError"DOMException
// https://html.spec.whatwg.org/multipage/scripting.html#valid-custom-element-name
let invalid_names = [
'annotation-xml',
'color-profile',
'font-face',
'font-face-src',
'font-face-uri',
'font-face-format',
'font-face-name',
'missing-glyph',
'div', 'p',
'nothtmlbutnohyphen',
'-not-initial-a-z', '0not-initial-a-z', 'Not-initial-a-z',
'intermediate-UPPERCASE-letters',
'bad-\u00b6', 'bad-\u00b8', 'bad-\u00bf', 'bad-\u00d7', 'bad-\u00f7',
'bad-\u037e', 'bad-\u037e', 'bad-\u2000', 'bad-\u200e', 'bad-\u203e',
'bad-\u2041', 'bad-\u206f', 'bad-\u2190', 'bad-\u2bff', 'bad-\u2ff0',
'bad-\u3000', 'bad-\ud800', 'bad-\uf8ff', 'bad-\ufdd0', 'bad-\ufdef',
'bad-\ufffe', 'bad-\uffff', 'bad-' + String.fromCodePoint(0xf0000)
];
invalid_names.forEach((name) => {
promise_test((t) => {
return create_window_in_test(t)
.then((w) => {
return promise_rejects_with_dom_exception_syntax_error(w, t, w.customElements.whenDefined(name));
});
}, 'whenDefined() called with invalid name ' + name + ' should throw "SyntaxError"DOMException');
});
function promise_rejects_with_dom_exception_syntax_error(global_context, test, promise, description) {
return promise.then(test.unreached_func("Should have rejected: " + description)).catch(function(e) {
assert_throws_dom_exception(global_context, 'SYNTAX_ERR', function () { throw e; })
});
}
})();
</script>
</body>

View File

@@ -1,412 +0,0 @@
<!DOCTYPE html>
<title>Custom Elements: defineElement</title>
<link rel="help" href="https://html.spec.whatwg.org/multipage/scripting.html#customelementsregistry">
<meta name="author" title="Dominic Cooney" href="mailto:dominicc@chromium.org">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
<body>
<script>
// TODO(dominicc): Merge these tests with
// https://github.com/w3c/web-platform-tests/pull/2940
'use strict';
test_with_window((w) => {
assert_throws(TypeError.prototype, () => {
w.customElements.define('a-a', 42);
}, 'defining a number "constructor" should throw a TypeError');
assert_throws(TypeError.prototype, () => {
w.customElements.define('a-a', () => {});
}, 'defining an arrow function "constructor" should throw a TypeError');
assert_throws(TypeError.prototype, () => {
w.customElements.define('a-a', { m() {} }.m);
}, 'defining a concise method "constructor" should throw a TypeError');
}, 'A "constructor" that is not a constructor');
test_with_window((w) => {
// https://html.spec.whatwg.org/multipage/scripting.html#valid-custom-element-name
let invalid_names = [
'annotation-xml',
'color-profile',
'font-face',
'font-face-src',
'font-face-uri',
'font-face-format',
'font-face-name',
'missing-glyph',
'div', 'p',
'nothtmlbutnohyphen',
'-not-initial-a-z', '0not-initial-a-z', 'Not-initial-a-z',
'intermediate-UPPERCASE-letters',
'bad-\u00b6', 'bad-\u00b8', 'bad-\u00bf', 'bad-\u00d7', 'bad-\u00f7',
'bad-\u037e', 'bad-\u037e', 'bad-\u2000', 'bad-\u200e', 'bad-\u203e',
'bad-\u2041', 'bad-\u206f', 'bad-\u2190', 'bad-\u2bff', 'bad-\u2ff0',
'bad-\u3000', 'bad-\ud800', 'bad-\uf8ff', 'bad-\ufdd0', 'bad-\ufdef',
'bad-\ufffe', 'bad-\uffff', 'bad-' + String.fromCodePoint(0xf0000)
];
class X extends w.HTMLElement {}
invalid_names.forEach((name) => {
assert_throws_dom_exception(w, 'SYNTAX_ERR', () => {
w.customElements.define(name, X);
})
});
}, 'Invalid names');
test_with_window((w) => {
class X extends w.HTMLElement {}
class Y extends w.HTMLElement {}
w.customElements.define('a-a', X);
assert_throws_dom_exception(w, 'NotSupportedError', () => {
w.customElements.define('a-a', Y);
}, 'defining an element with a name that is already defined should throw ' +
'a NotSupportedError');
}, 'Duplicate name');
test_with_window((w) => {
class Y extends w.HTMLElement {}
let X = (function () {}).bind({});
Object.defineProperty(X, 'prototype', {
get() {
assert_throws_dom_exception(w, 'NotSupportedError', () => {
w.customElements.define('a-a', Y);
}, 'defining an element with a name that is being defined should ' +
'throw a NotSupportedError');
return {};
}
});
w.customElements.define('a-a', X);
assert_equals(w.customElements.get('a-a'), X, 'the first definition should have worked');
}, 'Duplicate name defined recursively');
test_with_window((w) => {
class X extends w.HTMLElement {}
w.customElements.define('a-a', X);
assert_throws_dom_exception(w, 'NotSupportedError', () => {
w.customElements.define('a-b', X);
}, 'defining an element with a constructor that is already in the ' +
'registry should throw a NotSupportedError');
}, 'Reused constructor');
promise_test((t) => {
return Promise.all([create_window_in_test(t), create_window_in_test(t)])
.then(([w1, w2]) => {
class X extends w2.HTMLElement { };
w1.customElements.define('first-name', X);
w2.customElements.define('second-name', X);
assert_equals(
new X().localName, 'second-name',
'the current global object should determine which definition is ' +
'operative; because X extends w2.HTMLElement, w2 is operative');
});
}, 'HTMLElement constructor looks up definitions in the current global-' +
'reused constructor');
promise_test((t) => {
return Promise.all([create_window_in_test(t), create_window_in_test(t)])
.then(([w1, w2]) => {
class X extends w2.HTMLElement { };
w1.customElements.define('x-x', X);
assert_throws(
TypeError.prototype, () => new X(),
'the current global object (w2) should not find the definition in w1');
});
}, 'HTMLElement constructor looks up definitions in the current global');
test_with_window((w) => {
let X = (function () {}).bind({});
Object.defineProperty(X, 'prototype', {
get() {
assert_throws_dom_exception(w, 'NotSupportedError', () => {
w.customElements.define('second-name', X);
}, 'defining an element with a constructor that is being defined ' +
'should throw a NotSupportedError');
return {};
}
});
w.customElements.define('first-name', X);
assert_equals(w.customElements.get('first-name'), X, 'the first definition should have worked');
}, 'Reused constructor recursively');
test_with_window((w) => {
let X = (function () {}).bind({});
Object.defineProperty(X, 'prototype', {
get() {
assert_throws_dom_exception(w, 'NotSupportedError', () => {
w.customElements.define('second-name', class extends HTMLElement { });
}, 'defining an element while element definition is running should ' +
'throw a NotSupportedError');
return {};
}
});
w.customElements.define('first-name', X);
assert_equals(w.customElements.get('first-name'), X,
'the first definition should have worked');
}, 'Define while element definition is running');
promise_test((t) => {
return Promise.all([create_window_in_test(t), create_window_in_test(t)])
.then(([w1, w2]) => {
let X = (function () {}).bind({});
class Y extends w2.HTMLElement { };
Object.defineProperty(X, 'prototype', {
get() {
w2.customElements.define('second-name', Y);
return {};
}
});
w1.customElements.define('first-name', X);
assert_equals(w1.customElements.get('first-name'), X,
'the first definition should have worked');
assert_equals(w2.customElements.get('second-name'), Y,
'the second definition should have worked, too');
});
}, 'Define while element definition is running in a separate registry');
test_with_window((w) => {
class Y extends w.HTMLElement { };
class X extends w.HTMLElement {
constructor() {
super();
w.customElements.define('second-name', Y);
}
};
// the element definition flag while first-name is processed should
// be reset before doing upgrades
w.customElements.define('first-name', X);
assert_equals(w.customElements.get('second-name'), Y,
'the second definition should have worked');
}, 'Element definition flag resets before upgrades',
'<first-name></first-name>');
test_with_window((w) => {
assert_throws(TypeError.prototype, () => {
let not_a_constructor = () => {};
let invalid_name = 'annotation-xml';
w.customElements.define(invalid_name, not_a_constructor);
}, 'defining an element with an invalid name and invalid constructor ' +
'should throw a TypeError for the constructor and not a SyntaxError');
class C extends w.HTMLElement {}
w.customElements.define('a-a', C);
assert_throws_dom_exception(w, 'SYNTAX_ERR', () => {
let invalid_name = 'annotation-xml';
let reused_constructor = C;
w.customElements.define(invalid_name, reused_constructor);
}, 'defining an element with an invalid name and a reused constructor ' +
'should throw a SyntaxError for the name and not a NotSupportedError');
}, 'Order of checks');
test_with_window((w) => {
let doc = w.document;
doc.body.innerHTML = `
<a-a id="a">
<p>
<a-a id="b"></a-a>
<a-a id="c"></a-a>
</p>
<a-a id="d"></a-a>
</a-a>`;
let invocations = [];
class C extends w.HTMLElement {
constructor() {
super();
invocations.push(this);
}
}
w.customElements.define('a-a', C);
assert_array_equals(['a', 'b', 'c', 'd'], invocations.map((e) => e.id),
'four elements should have been upgraded in doc order');
}, 'Upgrade: existing elements');
test_with_window((w) => {
let doc = w.document;
let a = doc.createElement('a-a');
doc.body.appendChild(a);
assert_equals(w.HTMLElement.prototype, Object.getPrototypeOf(a),
'the undefined autonomous element should be a HTMLElement');
let invocations = [];
class C extends w.HTMLElement {
constructor() {
super();
assert_equals(C.prototype, Object.getPrototypeOf(a),
'the HTMLElement constructor should set the prototype ' +
'to the defined prototype');
invocations.push(this);
}
}
w.customElements.define('a-a', C);
assert_array_equals([a], invocations,
'the constructor should have been invoked for the in-' +
'document element');
}, 'Upgrade: sets prototype of existing elements');
test_with_window((w) => {
let doc = w.document;
var shadow = doc.body.attachShadow({mode: 'open'});
let a = doc.createElement('a-a');
shadow.appendChild(a);
let invocations = [];
class C extends w.HTMLElement {
constructor() {
super();
invocations.push(this);
}
}
w.customElements.define('a-a', C);
assert_array_equals([a], invocations,
'the constructor should have been invoked once for the ' +
'elements in the shadow tree');
}, 'Upgrade: shadow tree');
// Final step in Step 14
// 14. Finally, if the first set of steps threw an exception, then rethrow that exception,
// and terminate this algorithm.
test_with_window((w) => {
class Y extends w.HTMLElement {}
let X = (function () {}).bind({});
Object.defineProperty(X, 'prototype', {
get() { throw { name: 42 }; }
});
assert_throws({ name: 42 }, () => {
w.customElements.define('a-a', X);
}, 'should rethrow constructor exception');
w.customElements.define('a-a', Y);
assert_equals(w.customElements.get('a-a'), Y, 'the same name can be registered after failure');
}, 'If an exception is thrown, rethrow that exception and terminate the algorithm');
// 14.1 Let prototype be Get(constructor, "prototype"). Rethrow any exceptions.
test_with_window((w) => {
let X = (function () {}).bind({});
Object.defineProperty(X, 'prototype', {
get() { throw { name: 'prototype throws' }; }
});
assert_throws({ name: 'prototype throws' }, () => {
w.customElements.define('a-a', X);
}, 'Exception from Get(constructor, prototype) should be rethrown');
}, 'Rethrow any exceptions thrown while getting prototype');
// 14.2 If Type(prototype) is not Object, then throw a TypeError exception.
test_with_window((w) => {
function F() {}
F.prototype = 42;
assert_throws(TypeError.prototype, () => {
w.customElements.define('a-a', F);
}, 'defining an element with a constructor with a prototype that is not an ' +
'object should throw a TypeError');
}, 'Retrieved prototype is a non-object');
// 14.3 Let connectedCallback be Get(prototype, "connectedCallback"). Rethrow any exceptions.
// 14.5 Let disconnectedCallback be Get(prototype, "disconnectedCallback"). Rethrow any exceptions.
// 14.7 Let attributeChangedCallback be Get(prototype, "attributeChangedCallback"). Rethrow any exceptions.
// Note that this test implicitly tests order of callback retrievals.
// Callbacks are defined in reverse order.
let callbacks_in_reverse = ['attributeChangedCallback', 'disconnectedCallback', 'connectedCallback'];
function F_for_callbacks_in_reverse() {};
callbacks_in_reverse.forEach((callback) => {
test_with_window((w) => {
Object.defineProperty(F_for_callbacks_in_reverse.prototype, callback, {
get() { throw { name: callback }; }
});
assert_throws({ name: callback }, () => {
w.customElements.define('a-a', F_for_callbacks_in_reverse);
}, 'Exception from Get(prototype, callback) should be rethrown');
}, 'Rethrow any exceptions thrown while retrieving ' + callback);
});
// 14.4 If connectedCallback is not undefined, and IsCallable(connectedCallback) is false,
// then throw a TypeError exception.
// 14.6 If disconnectedCallback is not undefined, and IsCallable(disconnectedCallback) is false,
// then throw a TypeError exception.
// 14.9. If attributeChangedCallback is not undefined, then
// 1. If IsCallable(attributeChangedCallback) is false, then throw a TypeError exception.
callbacks_in_reverse.forEach((callback) => {
test_with_window((w) => {
function F() {}
Object.defineProperty(F.prototype, callback, {
get() { return {}; }
});
assert_throws(TypeError.prototype, () => {
w.customElements.define('a-a', F);
}, 'defining an element with a constructor with a callback that is ' +
'not undefined and not callable should throw a TypeError');
}, 'If retrieved callback '+ callback + ' is not undefined and not callable, throw TypeError');
});
// 14.9.2 Let observedAttributesIterable be Get(constructor, "observedAttributes").
// Rethrow any exceptions.
test_with_window((w) => {
class X extends w.HTMLElement{
constructor() { super(); }
attributeChangedCallback() {}
static get observedAttributes() { throw { name: 'observedAttributes throws' }; }
}
assert_throws({ name: 'observedAttributes throws' }, () => {
w.customElements.define('a-a', X);
}, 'Exception from Get(constructor, observedAttributes) should be rethrown');
}, 'Rethrow any exceptions thrown while getting observedAttributes');
// 14.9.3 If observedAttributesIterable is not undefined, then set observedAttributes
// to the result of converting observedAttributesIterable to a sequence<DOMString>.
// Rethrow any exceptions.
test_with_window((w) => {
class X extends w.HTMLElement{
constructor() { super(); }
attributeChangedCallback() {}
static get observedAttributes() { return new RegExp(); }
}
assert_throws(TypeError.prototype, () => {
w.customElements.define('a-a', X);
}, 'converting RegExp to sequence<DOMString> should throw TypeError');
}, 'exception thrown while converting observedAttributes to ' +
'sequence<DOMString> should be rethrown');
// 14.9.2 test Get(constructor, observedAttributes) does not throw if
// attributeChangedCallback is undefined.
test_with_window((w) => {
let observedAttributes_invoked = false;
let X = (function () {}).bind({});
Object.defineProperty(X, 'observedAttributes', {
get() { observedAttributes_invoked = true; }
});
assert_false( observedAttributes_invoked, 'Get(constructor, observedAttributes) should not be invoked');
}, 'Get(constructor, observedAttributes) should not execute if ' +
'attributeChangedCallback is undefined');
// TODO(dominicc): I think the order and timing of checks the tests
// below exercise has changed. Update these tests. crbug.com/643052
// step 2
// 2. If constructor is an interface object whose corresponding interface either is
// HTMLElement or has HTMLElement in its set of inherited interfaces, throw
// a TypeError and abort these steps.
// 3. If name is not a valid custom element name, then throw a "SyntaxError" DOMException
// and abort these steps.
test_with_window((w) => {
let invalid_name = 'annotation-xml';
// TODO(davaajav): change this to TypeError, when we add a failure expectation to this file
assert_throws_dom_exception(w, 'SYNTAX_ERR', () => {
w.customElements.define(invalid_name, HTMLElement);
}, 'defining a constructor that is an interface object whose interface is HTMLElement' +
'should throw TypeError not SyntaxError');
}, 'Invalid constructor');
// step 2
test_with_window((w) => {
let invalid_name = 'annotation-xml';
assert_throws_dom_exception(w, 'SYNTAX_ERR', () => {
w.customElements.define(invalid_name, HTMLButtonElement);
}, 'defining a constructor that is an interface object who has HTMLElement' +
'in its set of inhertied interfaces should throw TypeError not SyntaxError');
}, 'Invalid constructor');
// step 2
test_with_window((w) => {
let invalid_name = 'annotation-xml';
assert_throws_dom_exception(w, 'SYNTAX_ERR', () => {
w.customElements.define(invalid_name, class extends HTMLElement {});
}, 'defining author-defined custom element constructor should pass this ' +
'step without throwing TypeError');
}, 'Invalid constructor');
</script>
</body>

View File

@@ -1,53 +0,0 @@
<!DOCTYPE html>
<title>Custom Elements: create an element inside a template </title>
<link rel="help" href="https://dom.spec.whatwg.org/#concept-create-element">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
<iframe id="iframe"></iframe>
<script>
'use strict';
// A document`s browsing context only afffects the look up result of custom element definition
// https://html.spec.whatwg.org/#look-up-a-custom-element-definition
// There are two places where we need to look up a custom element definition:
// 1. creating an element
// https://dom.spec.whatwg.org/#concept-create-element
// 2. trying to upgrade an element
// https://html.spec.whatwg.org/#concept-try-upgrade
// The previous observation leads to the following behaviours:
// 1. an element in a document without browsing context should not be upgraded
// 2. a custom element cannot be created in a document without browsing context
// 3. an already "defined" custom element should remain "defined" after being inserted
// into a document without browsing context
test_with_window(w => {
let doc_without_browsing_context = w.document.implementation.createHTMLDocument();
let element = doc_without_browsing_context.createElement('a-a');
w.customElements.define('a-a', class extends w.HTMLElement {});
doc_without_browsing_context.body.appendChild(element);
assert_true(element.matches(':not(:defined)'));
w.document.body.appendChild(element);
assert_true(element.matches(':defined'));
}, 'An element should not upgraded in a document without browsing context');
test_with_window(w => {
let doc_without_browsing_context = w.document.implementation.createHTMLDocument();
w.customElements.define('b-b', class extends w.HTMLElement {});
let e1 = doc_without_browsing_context.createElement('b-b');
assert_true(e1.matches(':not(:defined)'));
let e2 = w.document.createElement('b-b');
assert_true(e2.matches(':defined'));
}, 'Custom element should not be created in a document without browsing context');
test_with_window(w => {
w.customElements.define('a-a', class extends w.HTMLElement {});
let element = w.document.createElement('a-a');
assert_true(element.matches(':defined'));
let doc_without_browsing_context = w.document.implementation.createHTMLDocument();
doc_without_browsing_context.body.appendChild(element);
let inserted_element = doc_without_browsing_context.body.querySelector('a-a');
assert_true(inserted_element.matches(':defined'));
}, 'Inserting an already defined custom element into a document without browsing context ' +
'should not change its state');
</script>

View File

@@ -1,52 +0,0 @@
<!DOCTYPE html>
<title>Custom Elements: Insert a node should try to upgrade</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
<body>
<script>
'use strict';
function assert_invocations(invocations, element, length, i) {
assert_equals(invocations.length, length, length == 2 ? 'inserting into different node should enqueue connectedCallback again' : 'inserting an element should enqueue connectedCallback reaction');
assert_array_equals(invocations[i].slice(0, 2), ['connected', element], 'inserting "custom" element should enqueue a connectedCallback reaction');
assert_array_equals(invocations[i][2], [], 'connectedCallback should be invoked with empty argument list');
}
// Insert a node
// https://dom.spec.whatwg.org/#concept-node-insert
// 6.5.2.1 If inclusiveDescendant is custom, then enqueue a custom element callback reaction
// with inclusiveDescendant, callback name "connectedCallback", and an empty argument list.
test_with_window(w => {
let element = w.document.createElement('a-a');
let invocations = [];
w.customElements.define('a-a', class extends w.HTMLElement {
connectedCallback() { invocations.push(['connected', this, arguments]); }
});
w.document.body.appendChild(element);
assert_invocations(invocations, element, 1, 0);
w.document.head.appendChild(element);
assert_invocations(invocations, element, 2, 1);
}, 'Insert a node that is "custom" should enqueue connectedCallback');
// 6.5.2.2. try to upgrade inclusiveDescendant.
// Try to upgrade an element
// https://html.spec.whatwg.org/multipage/scripting.html#concept-try-upgrade
test_with_window(w => {
let element = w.document.createElement('a-a');
w.customElements.define('a-a', class extends w.HTMLElement {
constructor() {
super();
this.is_upgraded = true;
}
});
assert_false('is_upgraded' in element);
assert_false(element.matches(':defined'));
w.document.body.appendChild(element);
assert_true(element.is_upgraded);
assert_true(element.matches(':defined'));
}, 'Insert a node should try to upgrade');
</script>
</body>

View File

@@ -1,285 +0,0 @@
<!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>

View File

@@ -1,34 +0,0 @@
<!DOCTYPE html>
<title>Custom Elements: Remove an element</title>
<link rel="help" href="https://dom.spec.whatwg.org/#concept-node-remove">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
<body>
<script>
'use strict';
// 15. For each shadow-including descendant descendant of node, in shadow-including tree order,
// run these substeps:
// 2. If descendant is custom, then enqueue a custom element callback reaction with descendant,
// callback name "disconnectedCallback", and an empty argument list.
test_with_window((w) => {
w.document.body.innerHTML = `
<a-a id="a">
<p>
<a-a id="b"></a-a>
<a-a id="c"></a-a>
</p>
<a-a id="d"></a-a>
</a-a>`;
let invocations = [];
class X extends w.HTMLElement {
disconnectedCallback() { invocations.push(this); }
}
w.customElements.define('a-a', X);
w.document.getElementById("a").remove();
assert_array_equals(['a', 'b', 'c', 'd'], invocations.map((e) => e.id),
'four elements should have been removed in doc order');
},'Remove an element');
</script>
</body>

View File

@@ -1,156 +0,0 @@
<!DOCTYPE html>
<title>Custom Elements: report the exception</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
<body>
<!--
The spec has two places where it says to [report the exception]:
1. In [create an element for a token], step 7.
This can occur only when [create an element] is invoked from
[create an element for a token] with the synchronous custom elements flag
set. The document parser uses this algorithm.
2. In [invoke custom element reactions], step 2.1.
There are different code paths when:
1. Author script throws an exception that is rethrown.
2. The user agent throws an exception.
This test contains 4 tests for the combination of the above 2x2.
[create an element]: https://dom.spec.whatwg.org/#concept-create-element
[create an element for a token]: https://html.spec.whatwg.org/multipage/syntax.html#create-an-element-for-the-token
[invoke custom element reactions]: https://html.spec.whatwg.org/multipage/scripting.html#invoke-custom-element-reactions
[report the exception]: https://html.spec.whatwg.org/multipage/webappapis.html#report-the-exception
-->
<template id="constructor-throws">
<script>
'use strict';
window.errors = [];
// https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-attributes:handler-onerror
window.onerror = function (event, source, lineno, colno, error) {
errors.push({
event: event,
source: source,
lineno: lineno,
colno: colno,
error: error
});
return true; // Cancel the error event.
};
const rethrowErrorName = 'rethrown';
const rethrowErrorMessage = 'check this is rethrown';
customElements.define('a-a', class extends HTMLElement {
constructor() {
const error = new Error(rethrowErrorMessage);
error.name = rethrowErrorName;
throw error;
}
});
</script>
</template>
<template id="instantiate">
<a-a></a-a>
</template>
<script>
'use strict';
const rethrowErrorName = 'rethrown';
const rethrowErrorMessage = 'check this is rethrown';
function assert_not_muted_error_event(error) {
assert_not_equals(error, undefined, 'should fire error event');
// Report an error, 6. If script has muted errors, ...
// https://html.spec.whatwg.org/multipage/webappapis.html#report-the-error
assert_false(error.event === 'Script error.'
&& error.source === '' && error.lineno === 0 && error.colno === 0
&& error.error === null,
'the error should not be muted.');
assert_false(!error.event, 'event (1st arg) should not be null');
assert_false(!error.source, 'source (2nd arg) should not be null');
// The spec doesn't define valid values for lineno/colno.
assert_false(!error.error, 'error (5th arg) should not be null');
}
function assert_rethrown_error_event(error) {
assert_not_muted_error_event(error);
assert_equals(error.error.name, rethrowErrorName);
assert_equals(error.error.message, rethrowErrorMessage);
}
const constructor_throws =
document.getElementById('constructor-throws').innerHTML;
const instantiate = document.getElementById('instantiate').innerHTML;
test_with_window((w) => {
assert_rethrown_error_event(w.errors[0]);
}, 'Document parser invokes the constructor that throws',
constructor_throws + instantiate);
test_with_window((w) => {
w.document.body.innerHTML = instantiate;
assert_rethrown_error_event(w.errors[0]);
}, 'Upgrade reaction invokes the constructor that throws',
constructor_throws);
</script>
<!--
Test when JavaScript returns without throwing errors, but the result is invalid
and thus UA should [report the exception].
-->
<template id="constructor-returns-bad-object">
<script>
'use strict';
window.errors = [];
// https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-attributes:handler-onerror
window.onerror = function (event, source, lineno, colno, error) {
errors.push({
event: event,
source: source,
lineno: lineno,
colno: colno,
error: error
});
return true; // Cancel the error event.
};
customElements.define('a-a', class extends HTMLElement {
constructor() {
super();
return []; // returning objects other than "this" is invalid.
}
});
</script>
</template>
<script>
const constructor_returns_bad_object =
document.getElementById('constructor-returns-bad-object').innerHTML;
function assert_type_error_event(error) {
assert_not_muted_error_event(error);
assert_equals(error.error.name, 'TypeError');
}
function assert_invalid_state_dom_error_event(error) {
assert_not_muted_error_event(error);
assert_equals(error.error.name, 'InvalidStateError');
}
test_with_window((w) => {
// "create an element" 6.1.3, throw a TypeError.
// https://dom.spec.whatwg.org/#concept-create-element
assert_type_error_event(w.errors[0]);
}, 'Document parser invokes the constructor that returns a bad object',
constructor_returns_bad_object + instantiate);
test_with_window((w) => {
// "upgrade an element" 10, throw an InvalidStateError DOMException.
// https://html.spec.whatwg.org/multipage/scripting.html#upgrades
w.document.body.innerHTML = instantiate;
assert_invalid_state_dom_error_event(w.errors[0]);
}, 'Upgrade reaction invokes the constructor that returns a bad object',
constructor_returns_bad_object);
</script>
</body>

View File

@@ -1,54 +0,0 @@
function create_window_in_test(t, srcdoc) {
let p = new Promise((resolve) => {
let f = document.createElement('iframe');
srcdoc = srcdoc || '';
// srcdoc = `<script src="../../../../../../dist/CustomElementsV1.min.js"></script>\n` +
srcdoc = `<script src="../../../../../../src/CustomElements/v1/CustomElements.js"></script>\n` +
`<script>customElements.enableFlush = true;</script>\n` + srcdoc;
f.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 assert_throws_dom_exception(global_context, code, func, description) {
let exception;
assert_throws(code, () => {
try {
func.call(this);
} catch(e) {
exception = e;
throw e;
}
}, description);
assert_true(exception instanceof global_context.DOMException, 'DOMException on the appropriate window');
}
function assert_array_equals_callback_invocations(actual, expected, description) {
assert_equals(actual.length, expected.length);
for (let len=actual.length, i=0; i<len; ++i) {
let callback = expected[i][0];
assert_equals(actual[i][0], expected[i][0], callback + ' callback should be invoked');
assert_equals(actual[i][1], expected[i][1], callback + ' should be invoked on the element ' + expected[i][1]);
assert_array_equals(actual[i][2], expected[i][2], callback + ' should be invoked with the arguments ' + expected[i][2]);
}
}
function assert_is_upgraded(element, className, description) {
assert_true(element.matches(':defined'), description);
assert_equals(Object.getPrototypeOf(element), className.prototype, description);
}

View File

@@ -1,80 +0,0 @@
<!DOCTYPE html>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<iframe id="iframe"></iframe>
<script>
const testList = [
{ tag_name: 'div', defined: true },
{ tag_name: 'a-a', defined: false },
{ tag_name: 'font-face', defined: true },
];
// Setup iframe to test the parser.
const neither = 'rgb(255, 0, 0)';
const defined = 'rgb(255, 165, 0)';
const not_defined = 'rgb(0, 0, 255)';
iframe.srcdoc = `<style>
* { color:${neither}; }
:defined { color:${defined}; }
:not(:defined) { color:${not_defined}; }
</style>`
+ testList.map(d => `<${d.tag_name}></${d.tag_name}>`).join('');
setup({ explicit_done: true });
iframe.onload = () => {
const doc = iframe.contentDocument;
const doc_without_browsing_context = doc.implementation.createHTMLDocument();
for (const data of testList) {
// Test elements inserted by parser.
test_defined(data.defined, doc.getElementsByTagName(data.tag_name)[0], `<${data.tag_name}>`);
// Test DOM createElement() methods.
test_defined_for_createElement(data.defined, !data.defined, doc, data.tag_name);
// Documents without browsing context should behave the same.
test_defined_for_createElement(data.defined, false, doc_without_browsing_context, data.tag_name, 'Without browsing context: ');
}
done();
};
function test_defined_for_createElement(defined, should_test_change, doc, tag_name, description = '') {
// Test document.createElement().
let element = doc.createElement(tag_name);
doc.body.appendChild(element);
test_defined(defined, element, `${description}createElement("${tag_name}")`);
// Test document.createElementNS().
let html_element = doc.createElementNS('http://www.w3.org/1999/xhtml', tag_name);
doc.body.appendChild(html_element);
test_defined(defined, html_element, `${description}createElementNS("http://www.w3.org/1999/xhtml", "${tag_name}")`);
// If the element namespace is not HTML, it should be "uncustomized"; i.e., "defined".
let svg_element = doc.createElementNS('http://www.w3.org/2000/svg', tag_name);
doc.body.appendChild(svg_element);
test_defined(true, svg_element, `${description}createElementNS("http://www.w3.org/2000/svg", "${tag_name}")`);
// Test ":defined" changes when the custom element was defined.
if (should_test_change) {
let w = doc.defaultView;
assert_false(!w, 'defaultView required to test change');
w.customElements.define(tag_name, class extends w.HTMLElement {
constructor() { super(); }
});
test_defined(true, element, `Upgraded ${description}createElement("${tag_name}")`);
test_defined(true, html_element, `Upgraded ${description}createElementNS("http://www.w3.org/1999/xhtml", "${tag_name}")`);
}
}
function test_defined(expected, element, description) {
test(() => {
assert_equals(element.matches(':defined'), expected, 'matches(":defined")');
assert_equals(element.matches(':not(:defined)'), !expected, 'matches(":not(:defined")');
const view = element.ownerDocument.defaultView;
if (!view)
return;
const style = view.getComputedStyle(element);
assert_equals(style.color, expected ? defined : not_defined, 'getComputedStyle');
}, `${description} should ${expected ? 'be' : 'not be'} :defined`);
}
</script>

View File

@@ -1,48 +0,0 @@
<!DOCTYPE html>
<title>Custom Elements: Custom Element State "Failed" in document parser</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
<body>
<template id="test-content">
<script>
'use strict';
window.logs = [];
customElements.define('a-a', class extends HTMLElement {
constructor() {
super();
logs.push('constructor');
throw new Error();
}
connectedCallback() {
logs.push('connected');
}
});
</script>
<a-a></a-a>
</template>
<script>
'use strict';
// Custom Element State
// https://dom.spec.whatwg.org/#concept-element-custom-element-state
// Set to "failed" in step 7 of "create an element for a token"
// https://html.spec.whatwg.org/multipage/syntax.html#create-an-element-for-the-token
// If this step throws an exception, then report the exception, and let element be
// instead a new element that implements HTMLUnknownElement, with no attributes,
// namespace set to given namespace, namespace prefix set to null, custom element state
// set to "failed", custom element definition set to null, and node document set to document.
// This test loads the template content into iframe.srcdoc because "create an
// element for a token" with synchronous custom elements flag set to true is
// used only in document parser.
test_with_window(w => {
let logs = w.logs;
assert_equals(logs.length, 1, 'Only constructor should be invoked');
assert_equals(logs[0], 'constructor', 'The 1st action should be constructor');
let e = w.document.querySelector('a-a');
assert_equals(Object.getPrototypeOf(e), w.HTMLUnknownElement.prototype);
}, undefined, document.getElementById('test-content').innerHTML);
</script>
</body>

View File

@@ -1,6 +0,0 @@
CONSOLE ERROR: line 21: Uncaught Error
CONSOLE ERROR: line 2445: Uncaught Error: assert_unreached: connectedCallback should not be invoked if constructor threw Reached unreachable code
This is a testharness.js-based test.
PASS Custom Elements: Custom Element State "Failed" in Upgrades
Harness: the test ran to completion.

View File

@@ -1,52 +0,0 @@
<!DOCTYPE html>
<title>Custom Elements: Custom Element State "Failed" in Upgrades</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
<body>
<script>
'use strict';
// Custom Element State
// https://dom.spec.whatwg.org/#concept-element-custom-element-state
test_with_window(w => {
let document = w.document;
let element = document.createElement('a-a');
let constructorCount = 0;
w.customElements.define('a-a', class extends w.HTMLElement {
constructor() {
constructorCount++;
throw new Error();
}
connectedCallback() {
// TODO(davaajav): remove the failure expectation when this issue is closed
// https://github.com/w3c/webcomponents/issues/563
assert_unreached('connectedCallback should not be invoked if constructor threw');
}
});
// Upgrade calls the constructor.
// 9. If constructResult is an abrupt completion, then
// Set element's custom element state to "failed".
// https://html.spec.whatwg.org/multipage/scripting.html#upgrades
let container = document.body;
container.appendChild(element);
assert_equals(constructorCount, 1, 'constructor should be invoked once');
// "failed" is not "defined"
// https://dom.spec.whatwg.org/#concept-element-defined
assert_false(element.matches(':defined'));
// "failed" element should implement HTMLUnknownElement only in "creating an element for a token"
// https://html.spec.whatwg.org/#create-an-element-for-the-token
assert_equals(Object.getPrototypeOf(element), w.HTMLElement.prototype);
// 2. If element's custom element state is "failed", then abort these steps.
// https://html.spec.whatwg.org/multipage/scripting.html#upgrades
container.appendChild(element);
assert_equals(constructorCount, 1, 'constructor should be invoked once');
});
</script>
</body>

View File

@@ -1,117 +0,0 @@
<!DOCTYPE html>
<title>Custom Elements: upgrade element</title>
<link rel="help" href="https://html.spec.whatwg.org/multipage/scripting.html#concept-upgrade-an-element">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
<body>
<script>
'use strict'
// 6. If C non-conformantly uses an API decorated with the [CEReactions] extended attribute,
// then the reactions enqueued at the beginning of upgrade will execute during this step,
// before C finishes and control returns to this algorithm.
test_with_window((w) => {
let invocations = [];
let changedCallbackArgs = [];
let a = w.document.createElement('a-a');
w.document.body.appendChild(a);
a.setAttribute('x', '1');
class X extends w.HTMLElement {
constructor() {
super();
this.setAttribute('y', '2');
invocations.push(['constructor', this]);
}
connectedCallback() { invocations.push(['connected', this]); }
static get observedAttributes() { return ['x', 'y']; }
attributeChangedCallback() {
invocations.push(['attributeChanged', this]);
changedCallbackArgs.push(arguments);
}
}
w.customElements.define('a-a', X);
// Unlike calling new, upgrading element with createElement does not set element's state
// to "custom" during HTMLConstructor. Thus, appending attribute after super()
// does not enqueue a callback reaction.
assert_equals(invocations.length, 3);
assert_equals(changedCallbackArgs.length, 1, 'attributeChangedCallback should only be invoked once');
assert_array_equals(invocations[0], ['constructor', a], 'constructor should execute first');
assert_array_equals(invocations[1], ['attributeChanged', a], 'attributeChangedCallback should execute after constructor');
assert_array_equals(changedCallbackArgs[0], ['x', null, '1', null], 'attributeChangedCallback should execute for setAttribute outside of the constructor');
assert_array_equals(invocations[2], ['connected', a], 'connectedCallback should execute after the constrcutor');
}, 'The constructor non-conformatly uses API decorated with the [CEReactions] when constuctor is invoked during upgrade');
// Step 6 case when constructor is invoked with new
test_with_window((w) => {
let invocations = [];
let changedCallbackArgs = [];
class X extends w.HTMLElement {
constructor() {
super();
this.setAttribute('y', '2');
invocations.push(['constructor', this]);
}
connectedCallback() { invocations.push(['connected', this]); }
static get observedAttributes() { return ['x', 'y']; }
attributeChangedCallback() {
invocations.push(['attributeChanged', this]);
changedCallbackArgs.push(arguments);
}
}
w.customElements.define('a-a', X);
let a = new X();
a.setAttribute('x','1');
assert_equals(invocations.length, 3);
assert_equals(changedCallbackArgs.length, 2, 'attributeChangedCallback should be invoked twice');
assert_array_equals(invocations[0], ['attributeChanged', a], 'attributeChangedCallback for "a" should execute before the constructor is finished');
assert_array_equals(invocations[1], ['constructor', a], 'constructor executes second');
assert_array_equals(invocations[2], ['attributeChanged', a], 'setAttribute outside of the constructorshould be invoked');
assert_array_equals(changedCallbackArgs[0], ['y', null, '2', null]);
assert_array_equals(changedCallbackArgs[1], ['x', null, '1', null]);
}, 'The constructor non-conformatly uses API decorated with the [CEReactions] when constructor is invoked with new');
// 8. If constructResult is an abrupt completion, then return constructResult
// (i.e., rethrow the exception).
test_with_window((w) => {
let error_log = [];
let doc = w.document;
doc.body.appendChild(doc.createElement('a-a'));
w.onerror = function (msg, url, lineNo, columnNo, error) {
error_log.push(error.name);
return true;
};
class X extends w.HTMLElement {
constructor() {
super();
assert_false(this.matches(':defined'), 'calling super() with non-empty construction stack should not define the element');
throw { name: 'constructor throws' };
}
}
w.customElements.define('a-a', X);
assert_array_equals(error_log, ['constructor throws'], 'rethrow any exception thrown from constructor');
}, 'Upgrading an element with a throwing constructor should rethrow that exception');
// 9. If SameValue(constructResult.[[value]], element) is false, then throw an
// "InvalidStateError" DOMException and terminate these steps.
test_with_window((w) => {
let error_log = [];
w.onerror = function (msg, url, lineNo, columnNo, error) {
error_log.push(error.name);
return true;
};
let a = w.document.createElement('a-a');
w.document.body.appendChild(a);
class X extends w.HTMLElement {
constructor() {
super();
return ['aaaa'];
}
}
w.customElements.define('a-a', X);
assert_array_equals(error_log, ['InvalidStateError'], 'returning object that is different from element should throw "InvalidStateError"');
}, 'Upgrading an element with constructor that returns different object');
</script>
</body>

View File

@@ -1,25 +0,0 @@
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="spec/resources/custom-elements-helpers.js"></script>
<body>
<script>
'use strict';
test_with_window((w) => {
class X extends w.HTMLElement {}
w.customElements.define('new-old', X);
assert_throws(null, () => {
w.document.registerElement('new-old', {prototype: X.prototype});
}, '"registering" (v0) a name already "defined" should throw');
w.document.registerElement('old-new', {
prototype: Object.create(w.HTMLElement.prototype)
});
class Y extends w.HTMLElement {}
assert_throws(null, () => {
w.customElements.define('old-new', Y);
}, '"defining" (v1) a name already "registered" (v0) should throw');
}, 'Overlapping old and new-style custom elements are not allowed');
</script>

View File

@@ -1,26 +0,0 @@
The following files are used to run W3C testharness.js-style tests,
either authored for Blink or imported from W3C's web-platform-tests.
These files should not be modified locally, as they are imported.
* testharness.js from testharness.js
* testharness.css from testharness.js
* idlharness.js from testharness.js
* WebIDLParser.js from webidl2.js
* vendor-prefix.js from web-platform-tests
NOTE: The 'WebIDLParser.js' file is developed as 'webidl2.js' but
web-platform-tests's wpt-tools server is configured to serve the
resource under a different name, which is matched here.
The following files are native to Blink and can be modified:
* testharnessreport.js integration with Blink's test runner
See also:
* https://www.chromium.org/blink/importing-the-w3c-tests
* LayoutTests/imported/README
References:
* web-platform-tests https://github.com/w3c/web-platform-tests
* testharness.js https://github.com/w3c/testharness.js
* webidl2.js https://github.com/darobin/webidl2.js

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More