<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[204540] trunk</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/204540">204540</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2016-08-16 16:55:49 -0700 (Tue, 16 Aug 2016)</dd>
</dl>
<h3>Log Message</h3>
<pre>customElements.define should retrieve lifecycle callbacks
https://bugs.webkit.org/show_bug.cgi?id=160797
Reviewed by Chris Dumez.
Source/WebCore:
Updated JSCustomElementInterface to invoke Get(constructor, "prototype") and Get(prototype, callbackName)
for each lifecycle callback as required by the latest specification:
https://html.spec.whatwg.org/#dom-customelementsregistry-define
Also added the support for "observedAttributes" property on the custom elements constructor which defines
the list of attributes for which attributeChangedCallback is invoked.
Test: fast/custom-elements/CustomElementsRegistry.html
* bindings/js/JSCustomElementInterface.cpp:
(WebCore::JSCustomElementInterface::setAttributeChangedCallback): Added.
(WebCore::JSCustomElementInterface::attributeChanged): Invoke m_attributeChangedCallback instead of on the
result of Get(element, "attributeChangedCallback").
* bindings/js/JSCustomElementInterface.h:
(WebCore::JSCustomElementInterface::observesAttribute): Added.
* bindings/js/JSCustomElementsRegistryCustom.cpp:
(WebCore::getLifecycleCallback): Added.
(WebCore::JSCustomElementsRegistry::define): Invoke Get(prototype, callbackName) for each callback. Also
store attributedChangedCallback and observedAttributes to JSCustomElementInterface. Other callbacks will
be stored in the future when the support for those callbacks are added.
* dom/Element.cpp:
(WebCore::Element::attributeChanged): Moved more code into enqueueAttributeChangedCallbackIfNeeded.
* dom/LifecycleCallbackQueue.cpp:
(WebCore::LifecycleCallbackQueue::enqueueAttributeChangedCallbackIfNeeded): Added an early exit for when
the given attribute is not observed by the custom element. Also moved the logic to retrieve
JSCustomElementInterface from Element::attributeChanged and renamed it from enqueueAttributeChangedCallback.
* bindings/js/JSDOMBinding.h:
(WebCore::toNativeArray): Throw a TypeError when the argument is not an object.
* bindings/js/JSDOMConvert.h:
(WebCore::Converter<Vector<T>>::convert): Removed a FIXME comment.
LayoutTests:
Added test cases for CustomElementsRegistry.define to make sure it invokes Get(constructor, "prototype")
and Get(prototype, callbackName) for each lifecycle callback.
Also updated the tests to reflect the support for observedAttributes which specifies the list of attributes
for which attributeChangedCallback is invoked.
* fast/custom-elements/CustomElementsRegistry-expected.txt: Renamed from Document-defineElement-expected.txt.
* fast/custom-elements/CustomElementsRegistry.html: Renamed from Document-defineElement.html.
* fast/custom-elements/Document-defineElement-expected.txt: Removed.
* fast/custom-elements/Document-defineElement.html: Removed.
* fast/custom-elements/attribute-changed-callback-expected.txt:
* fast/custom-elements/attribute-changed-callback.html: Added test cases for "observedAttributes".
* fast/custom-elements/lifecycle-callback-timing.html:</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkLayoutTestsfastcustomelementsattributechangedcallbackexpectedtxt">trunk/LayoutTests/fast/custom-elements/attribute-changed-callback-expected.txt</a></li>
<li><a href="#trunkLayoutTestsfastcustomelementsattributechangedcallbackhtml">trunk/LayoutTests/fast/custom-elements/attribute-changed-callback.html</a></li>
<li><a href="#trunkLayoutTestsfastcustomelementslifecyclecallbacktiminghtml">trunk/LayoutTests/fast/custom-elements/lifecycle-callback-timing.html</a></li>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCorebindingsjsJSCustomElementInterfacecpp">trunk/Source/WebCore/bindings/js/JSCustomElementInterface.cpp</a></li>
<li><a href="#trunkSourceWebCorebindingsjsJSCustomElementInterfaceh">trunk/Source/WebCore/bindings/js/JSCustomElementInterface.h</a></li>
<li><a href="#trunkSourceWebCorebindingsjsJSCustomElementsRegistryCustomcpp">trunk/Source/WebCore/bindings/js/JSCustomElementsRegistryCustom.cpp</a></li>
<li><a href="#trunkSourceWebCoredomElementcpp">trunk/Source/WebCore/dom/Element.cpp</a></li>
<li><a href="#trunkSourceWebCoredomLifecycleCallbackQueuecpp">trunk/Source/WebCore/dom/LifecycleCallbackQueue.cpp</a></li>
<li><a href="#trunkSourceWebCoredomLifecycleCallbackQueueh">trunk/Source/WebCore/dom/LifecycleCallbackQueue.h</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsfastcustomelementsCustomElementsRegistryexpectedtxt">trunk/LayoutTests/fast/custom-elements/CustomElementsRegistry-expected.txt</a></li>
<li><a href="#trunkLayoutTestsfastcustomelementsCustomElementsRegistryhtml">trunk/LayoutTests/fast/custom-elements/CustomElementsRegistry.html</a></li>
</ul>
<h3>Removed Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsfastcustomelementsDocumentdefineElementexpectedtxt">trunk/LayoutTests/fast/custom-elements/Document-defineElement-expected.txt</a></li>
<li><a href="#trunkLayoutTestsfastcustomelementsDocumentdefineElementhtml">trunk/LayoutTests/fast/custom-elements/Document-defineElement.html</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (204539 => 204540)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2016-08-16 23:31:38 UTC (rev 204539)
+++ trunk/LayoutTests/ChangeLog        2016-08-16 23:55:49 UTC (rev 204540)
</span><span class="lines">@@ -1,3 +1,24 @@
</span><ins>+2016-08-16 Ryosuke Niwa <rniwa@webkit.org>
+
+ customElements.define should retrieve lifecycle callbacks
+ https://bugs.webkit.org/show_bug.cgi?id=160797
+
+ Reviewed by Chris Dumez.
+
+ Added test cases for CustomElementsRegistry.define to make sure it invokes Get(constructor, "prototype")
+ and Get(prototype, callbackName) for each lifecycle callback.
+
+ Also updated the tests to reflect the support for observedAttributes which specifies the list of attributes
+ for which attributeChangedCallback is invoked.
+
+ * fast/custom-elements/CustomElementsRegistry-expected.txt: Renamed from Document-defineElement-expected.txt.
+ * fast/custom-elements/CustomElementsRegistry.html: Renamed from Document-defineElement.html.
+ * fast/custom-elements/Document-defineElement-expected.txt: Removed.
+ * fast/custom-elements/Document-defineElement.html: Removed.
+ * fast/custom-elements/attribute-changed-callback-expected.txt:
+ * fast/custom-elements/attribute-changed-callback.html: Added test cases for "observedAttributes".
+ * fast/custom-elements/lifecycle-callback-timing.html:
+
</ins><span class="cx"> 2016-08-16 Chris Dumez <cdumez@apple.com>
</span><span class="cx">
</span><span class="cx"> Align isDefaultNamespace() / lookupPrefix() / lookupNamespaceURI() with the specification
</span></span></pre></div>
<a id="trunkLayoutTestsfastcustomelementsCustomElementsRegistryexpectedtxtfromrev204539trunkLayoutTestsfastcustomelementsDocumentdefineElementexpectedtxt"></a>
<div class="copfile"><h4>Copied: trunk/LayoutTests/fast/custom-elements/CustomElementsRegistry-expected.txt (from rev 204539, trunk/LayoutTests/fast/custom-elements/Document-defineElement-expected.txt) (0 => 204540)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/custom-elements/CustomElementsRegistry-expected.txt         (rev 0)
+++ trunk/LayoutTests/fast/custom-elements/CustomElementsRegistry-expected.txt        2016-08-16 23:55:49 UTC (rev 204540)
</span><span class="lines">@@ -0,0 +1,20 @@
</span><ins>+
+PASS CustomElementsRegistry interface must have define as a method
+PASS customElements.define must throw with an invalid name
+PASS customElements.define must throw when there is already a custom element of the same name
+PASS customElements.define must throw when there is already a custom element with the same class
+PASS customElements.define must throw when the element interface is not a constructor
+PASS customElements.define must get "prototype" property of the constructor
+PASS customElements.define must rethrow an exception thrown while getting "prototype" property of the constructor
+PASS customElements.define must throw when "prototype" property of the constructor is not an object
+PASS customElements.define must get callbacks of the constructor prototype
+PASS customElements.define must rethrow an exception thrown while getting callbacks on the constructor prototype
+PASS customElements.define must rethrow an exception thrown while converting a callback value to Function callback type
+PASS customElements.define must get "observedAttributes" property on the constructor prototype when "attributeChangedCallback" is present
+PASS customElements.define must rethrow an exception thrown while getting observedAttributes on the constructor prototype
+PASS customElements.define must rethrow an exception thrown while converting the value of observedAttributes to sequence<DOMString>
+PASS customElements.define must rethrow an exception thrown while iterating over observedAttributes to sequence<DOMString>
+PASS customElements.define must rethrow an exception thrown while retrieving Symbol.iterator on observedAttributes
+PASS customElements.define must not throw even if "observedAttributes" fails to convert if "attributeChangedCallback" is not defined
+PASS customElements.define must define an instantiatable custom element
+
</ins></span></pre></div>
<a id="trunkLayoutTestsfastcustomelementsCustomElementsRegistryhtmlfromrev204539trunkLayoutTestsfastcustomelementsDocumentdefineElementhtml"></a>
<div class="copfile"><h4>Copied: trunk/LayoutTests/fast/custom-elements/CustomElementsRegistry.html (from rev 204539, trunk/LayoutTests/fast/custom-elements/Document-defineElement.html) (0 => 204540)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/custom-elements/CustomElementsRegistry.html         (rev 0)
+++ trunk/LayoutTests/fast/custom-elements/CustomElementsRegistry.html        2016-08-16 23:55:49 UTC (rev 204540)
</span><span class="lines">@@ -0,0 +1,272 @@
</span><ins>+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: CustomElementsRegistry interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="CustomElementsRegistry interface must exist">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<link rel='stylesheet' href='../../resources/testharness.css'>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+test(function () {
+ assert_true('define' in CustomElementsRegistry.prototype, '"define" exists on CustomElementsRegistry.prototype');
+ assert_true('define' in customElements, '"define" exists on window.customElements');
+}, 'CustomElementsRegistry interface must have define as a method');
+
+test(function () {
+ class MyCustomElement extends HTMLElement {};
+
+ assert_throws({'name': 'SyntaxError'}, function () { customElements.define(null, MyCustomElement); },
+ 'customElements.define must throw a SyntaxError if the tag name is null');
+ assert_throws({'name': 'SyntaxError'}, function () { customElements.define('', MyCustomElement); },
+ 'customElements.define must throw a SyntaxError if the tag name is empty');
+ assert_throws({'name': 'SyntaxError'}, function () { customElements.define('abc', MyCustomElement); },
+ 'customElements.define must throw a SyntaxError if the tag name does not contain "-"');
+ assert_throws({'name': 'SyntaxError'}, function () { customElements.define('a-Bc', MyCustomElement); },
+ 'customElements.define must throw a SyntaxError if the tag name contains an upper case letter');
+
+ var builtinTagNames = [
+ 'annotation-xml',
+ 'color-profile',
+ 'font-face',
+ 'font-face-src',
+ 'font-face-uri',
+ 'font-face-format',
+ 'font-face-name',
+ 'missing-glyph'
+ ];
+
+ for (var tagName of builtinTagNames) {
+ assert_throws({'name': 'SyntaxError'}, function () { customElements.define(tagName, MyCustomElement); },
+ 'customElements.define must throw a SyntaxError if the tag name is "' + tagName + '"');
+ }
+
+}, 'customElements.define must throw with an invalid name');
+
+test(function () {
+ class SomeCustomElement extends HTMLElement {};
+ class OtherCustomElement extends HTMLElement {};
+
+ customElements.define('some-custom-element', SomeCustomElement);
+ assert_throws({'name': 'NotSupportedError'}, function () { customElements.define('some-custom-element', OtherCustomElement); },
+ 'customElements.define must throw a NotSupportedError if the specified tag name is already used');
+
+}, 'customElements.define must throw when there is already a custom element of the same name');
+
+test(function () {
+ class AnotherCustomElement extends HTMLElement {};
+
+ customElements.define('another-custom-element', AnotherCustomElement);
+ assert_throws({'name': 'NotSupportedError'}, function () { customElements.define('some-other-element', AnotherCustomElement); },
+ 'customElements.define must throw a NotSupportedError if the specified class already defines an element');
+
+}, 'customElements.define must throw when there is already a custom element with the same class');
+
+test(function () {
+ assert_throws({'name': 'TypeError'}, function () { customElements.define('invalid-element', 1); },
+ 'customElements.define must throw a TypeError when the element interface is a number');
+ assert_throws({'name': 'TypeError'}, function () { customElements.define('invalid-element', '123'); },
+ 'customElements.define must throw a TypeError when the element interface is a string');
+ assert_throws({'name': 'TypeError'}, function () { customElements.define('invalid-element', {}); },
+ 'customElements.define must throw a TypeError when the element interface is an object');
+ assert_throws({'name': 'TypeError'}, function () { customElements.define('invalid-element', []); },
+ 'customElements.define must throw a TypeError when the element interface is an array');
+}, 'customElements.define must throw when the element interface is not a constructor');
+
+test(function () {
+ var calls = [];
+ var proxy = new Proxy(class extends HTMLElement { }, {
+ get: function (target, name) {
+ calls.push(name);
+ return target[name];
+ }
+ });
+ customElements.define('proxy-element', proxy);
+ assert_array_equals(calls, ['prototype']);
+}, 'customElements.define must get "prototype" property of the constructor');
+
+test(function () {
+ var proxy = new Proxy(class extends HTMLElement { }, {
+ get: function (target, name) {
+ throw {name: 'expectedError'};
+ }
+ });
+ assert_throws({'name': 'expectedError'}, function () { customElements.define('element-with-string-prototype', proxy); });
+}, 'customElements.define must rethrow an exception thrown while getting "prototype" property of the constructor');
+
+test(function () {
+ var returnedValue;
+ var proxy = new Proxy(class extends HTMLElement { }, {
+ get: function (target, name) { return returnedValue; }
+ });
+
+ returnedValue = null;
+ assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-string-prototype', proxy); },
+ 'customElements.define must throw when "prototype" property of the constructor is null');
+ returnedValue = undefined;
+ assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-string-prototype', proxy); },
+ 'customElements.define must throw when "prototype" property of the constructor is undefined');
+ returnedValue = 'hello';
+ assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-string-prototype', proxy); },
+ 'customElements.define must throw when "prototype" property of the constructor is a string');
+ returnedValue = 1;
+ assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-string-prototype', proxy); },
+ 'customElements.define must throw when "prototype" property of the constructor is a number');
+
+}, 'customElements.define must throw when "prototype" property of the constructor is not an object');
+
+test(function () {
+ var constructor = function () {}
+ var calls = [];
+ constructor.prototype = new Proxy(constructor.prototype, {
+ get: function (target, name) {
+ calls.push(name);
+ return target[name];
+ }
+ });
+ customElements.define('element-with-proxy-prototype', constructor);
+ assert_array_equals(calls, ['connectedCallback', 'disconnectedCallback', 'adoptedCallback', 'attributeChangedCallback']);
+}, 'customElements.define must get callbacks of the constructor prototype');
+
+test(function () {
+ var constructor = function () {}
+ var calls = [];
+ constructor.prototype = new Proxy(constructor.prototype, {
+ get: function (target, name) {
+ calls.push(name);
+ if (name == 'disconnectedCallback')
+ throw {name: 'expectedError'};
+ return target[name];
+ }
+ });
+ assert_throws({'name': 'expectedError'}, function () { customElements.define('element-with-throwing-callback', constructor); });
+ assert_array_equals(calls, ['connectedCallback', 'disconnectedCallback'],
+ 'customElements.define must not get callbacks after one of the get throws');
+}, 'customElements.define must rethrow an exception thrown while getting callbacks on the constructor prototype');
+
+test(function () {
+ var constructor = function () {}
+ var calls = [];
+ constructor.prototype = new Proxy(constructor.prototype, {
+ get: function (target, name) {
+ calls.push(name);
+ if (name == 'adoptedCallback')
+ return 1;
+ return target[name];
+ }
+ });
+ assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-throwing-callback', constructor); });
+ assert_array_equals(calls, ['connectedCallback', 'disconnectedCallback', 'adoptedCallback'],
+ 'customElements.define must not get callbacks after one of the conversion throws');
+}, 'customElements.define must rethrow an exception thrown while converting a callback value to Function callback type');
+
+test(function () {
+ var constructor = function () {}
+ constructor.prototype.attributeChangedCallback = function () { };
+ var prototypeCalls = [];
+ var callOrder = 0;
+ constructor.prototype = new Proxy(constructor.prototype, {
+ get: function (target, name) {
+ if (name == 'prototype' || name == 'observedAttributes')
+ throw 'Unexpected access to observedAttributes';
+ prototypeCalls.push(callOrder++);
+ prototypeCalls.push(name);
+ return target[name];
+ }
+ });
+ var constructorCalls = [];
+ var proxy = new Proxy(constructor, {
+ get: function (target, name) {
+ constructorCalls.push(callOrder++);
+ constructorCalls.push(name);
+ return target[name];
+ }
+ });
+ customElements.define('element-with-attribute-changed-callback', proxy);
+ assert_array_equals(prototypeCalls, [1, 'connectedCallback', 2, 'disconnectedCallback', 3, 'adoptedCallback', 4, 'attributeChangedCallback']);
+ assert_array_equals(constructorCalls, [0, 'prototype', 5, 'observedAttributes']);
+}, 'customElements.define must get "observedAttributes" property on the constructor prototype when "attributeChangedCallback" is present');
+
+test(function () {
+ var constructor = function () {}
+ constructor.prototype.attributeChangedCallback = function () { };
+ var calls = [];
+ var proxy = new Proxy(constructor, {
+ get: function (target, name) {
+ calls.push(name);
+ if (name == 'observedAttributes')
+ throw {name: 'expectedError'};
+ return target[name];
+ }
+ });
+ assert_throws({'name': 'expectedError'}, function () { customElements.define('element-with-throwing-observed-attributes', proxy); });
+ assert_array_equals(calls, ['prototype', 'observedAttributes'],
+ 'customElements.define must get "prototype" and "observedAttributes" on the constructor');
+}, 'customElements.define must rethrow an exception thrown while getting observedAttributes on the constructor prototype');
+
+test(function () {
+ var constructor = function () {}
+ constructor.prototype.attributeChangedCallback = function () { };
+ var calls = [];
+ var proxy = new Proxy(constructor, {
+ get: function (target, name) {
+ calls.push(name);
+ if (name == 'observedAttributes')
+ return 1;
+ return target[name];
+ }
+ });
+ assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-invalid-observed-attributes', proxy); });
+ assert_array_equals(calls, ['prototype', 'observedAttributes'],
+ 'customElements.define must get "prototype" and "observedAttributes" on the constructor');
+}, 'customElements.define must rethrow an exception thrown while converting the value of observedAttributes to sequence<DOMString>');
+
+test(function () {
+ var constructor = function () {}
+ constructor.prototype.attributeChangedCallback = function () { };
+ constructor.observedAttributes = {[Symbol.iterator]: function *() {
+ yield 'foo';
+ throw {name: 'SomeError'};
+ }};
+ assert_throws({'name': 'SomeError'}, function () { customElements.define('element-with-generator-observed-attributes', constructor); });
+}, 'customElements.define must rethrow an exception thrown while iterating over observedAttributes to sequence<DOMString>');
+
+test(function () {
+ var constructor = function () {}
+ constructor.prototype.attributeChangedCallback = function () { };
+ constructor.observedAttributes = {[Symbol.iterator]: 1};
+ assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-observed-attributes-with-uncallable-iterator', constructor); });
+}, 'customElements.define must rethrow an exception thrown while retrieving Symbol.iterator on observedAttributes');
+
+test(function () {
+ var constructor = function () {}
+ constructor.observedAttributes = 1;
+ customElements.define('element-without-callback-with-invalid-observed-attributes', constructor);
+}, 'customElements.define must not throw even if "observedAttributes" fails to convert if "attributeChangedCallback" is not defined');
+
+test(function () {
+ class MyCustomElement extends HTMLElement {};
+ customElements.define('my-custom-element', MyCustomElement);
+
+ var instance = new MyCustomElement;
+ assert_true(instance instanceof MyCustomElement,
+ 'An instance of a custom HTML element be an instance of the associated interface');
+
+ assert_true(instance instanceof HTMLElement,
+ 'An instance of a custom HTML element must inherit from HTMLElement');
+
+ assert_equals(instance.localName, 'my-custom-element',
+ 'An instance of a custom element must use the associated tag name');
+
+ assert_equals(instance.namespaceURI, 'http://www.w3.org/1999/xhtml',
+ 'A custom element HTML must use HTML namespace');
+
+}, 'customElements.define must define an instantiatable custom element');
+
+</script>
+</body>
+</html>
</ins></span></pre></div>
<a id="trunkLayoutTestsfastcustomelementsDocumentdefineElementexpectedtxt"></a>
<div class="delfile"><h4>Deleted: trunk/LayoutTests/fast/custom-elements/Document-defineElement-expected.txt (204539 => 204540)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/custom-elements/Document-defineElement-expected.txt        2016-08-16 23:31:38 UTC (rev 204539)
+++ trunk/LayoutTests/fast/custom-elements/Document-defineElement-expected.txt        2016-08-16 23:55:49 UTC (rev 204540)
</span><span class="lines">@@ -1,8 +0,0 @@
</span><del>-
-PASS Check the existence of CustomElementsRegistry.prototype.define on CustomElementsRegistry interface
-PASS customElements.define should throw with an invalid name
-PASS customElements.define should throw when there is already a custom element of the same name
-PASS customElements.define should throw when there is already a custom element with the same class
-PASS customElements.define should throw when the element interface is not a constructor
-PASS customElements.define should define an instantiatable custom element
-
</del></span></pre></div>
<a id="trunkLayoutTestsfastcustomelementsDocumentdefineElementhtml"></a>
<div class="delfile"><h4>Deleted: trunk/LayoutTests/fast/custom-elements/Document-defineElement.html (204539 => 204540)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/custom-elements/Document-defineElement.html        2016-08-16 23:31:38 UTC (rev 204539)
+++ trunk/LayoutTests/fast/custom-elements/Document-defineElement.html        2016-08-16 23:55:49 UTC (rev 204540)
</span><span class="lines">@@ -1,101 +0,0 @@
</span><del>-<!DOCTYPE html>
-<html>
-<head>
-<title>Custom Elements: Extensions to Document interface</title>
-<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
-<meta name="assert" content="customElements.define should define a custom element">
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<link rel='stylesheet' href='../../resources/testharness.css'>
-</head>
-<body>
-<div id="log"></div>
-<script>
-
-test(function () {
- assert_true('define' in CustomElementsRegistry.prototype, '"define" exists on CustomElementsRegistry.prototype');
- assert_true('define' in customElements, '"define" exists on window.customElements');
-}, 'Check the existence of CustomElementsRegistry.prototype.define on CustomElementsRegistry interface');
-
-test(function () {
- class MyCustomElement extends HTMLElement {};
-
- assert_throws({'name': 'SyntaxError'}, function () { customElements.define(null, MyCustomElement); },
- 'customElements.define must throw a SyntaxError if the tag name is null');
- assert_throws({'name': 'SyntaxError'}, function () { customElements.define('', MyCustomElement); },
- 'customElements.define must throw a SyntaxError if the tag name is empty');
- assert_throws({'name': 'SyntaxError'}, function () { customElements.define('abc', MyCustomElement); },
- 'customElements.define must throw a SyntaxError if the tag name does not contain "-"');
- assert_throws({'name': 'SyntaxError'}, function () { customElements.define('a-Bc', MyCustomElement); },
- 'customElements.define must throw a SyntaxError if the tag name contains an upper case letter');
-
- var builtinTagNames = [
- 'annotation-xml',
- 'color-profile',
- 'font-face',
- 'font-face-src',
- 'font-face-uri',
- 'font-face-format',
- 'font-face-name',
- 'missing-glyph'
- ];
-
- for (var tagName of builtinTagNames) {
- assert_throws({'name': 'SyntaxError'}, function () { customElements.define(tagName, MyCustomElement); },
- 'customElements.define must throw a SyntaxError if the tag name is "' + tagName + '"');
- }
-
-}, 'customElements.define should throw with an invalid name');
-
-test(function () {
- class SomeCustomElement extends HTMLElement {};
- class OtherCustomElement extends HTMLElement {};
-
- customElements.define('some-custom-element', SomeCustomElement);
- assert_throws({'name': 'NotSupportedError'}, function () { customElements.define('some-custom-element', OtherCustomElement); },
- 'customElements.define must throw a NotSupportedError if the specified tag name is already used');
-
-}, 'customElements.define should throw when there is already a custom element of the same name');
-
-test(function () {
- class AnotherCustomElement extends HTMLElement {};
-
- customElements.define('another-custom-element', AnotherCustomElement);
- assert_throws({'name': 'NotSupportedError'}, function () { customElements.define('some-other-element', AnotherCustomElement); },
- 'customElements.define must throw a NotSupportedError if the specified class already defines an element');
-
-}, 'customElements.define should throw when there is already a custom element with the same class');
-
-test(function () {
- assert_throws({'name': 'TypeError'}, function () { customElements.define('invalid-element', 1); },
- 'customElements.define must throw a TypeError when the element interface is a number');
- assert_throws({'name': 'TypeError'}, function () { customElements.define('invalid-element', '123'); },
- 'customElements.define must throw a TypeError when the element interface is a string');
- assert_throws({'name': 'TypeError'}, function () { customElements.define('invalid-element', {}); },
- 'customElements.define must throw a TypeError when the element interface is an object');
- assert_throws({'name': 'TypeError'}, function () { customElements.define('invalid-element', []); },
- 'customElements.define must throw a TypeError when the element interface is an array');
-}, 'customElements.define should throw when the element interface is not a constructor');
-
-test(function () {
- class MyCustomElement extends HTMLElement {};
- customElements.define('my-custom-element', MyCustomElement);
-
- var instance = new MyCustomElement;
- assert_true(instance instanceof MyCustomElement,
- 'An instance of a custom HTML element be an instance of the associated interface');
-
- assert_true(instance instanceof HTMLElement,
- 'An instance of a custom HTML element must inherit from HTMLElement');
-
- assert_equals(instance.localName, 'my-custom-element',
- 'An instance of a custom element must use the associated tag name');
-
- assert_equals(instance.namespaceURI, 'http://www.w3.org/1999/xhtml',
- 'A custom element HTML must use HTML namespace');
-
-}, 'customElements.define should define an instantiatable custom element');
-
-</script>
-</body>
-</html>
</del></span></pre></div>
<a id="trunkLayoutTestsfastcustomelementsattributechangedcallbackexpectedtxt"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/fast/custom-elements/attribute-changed-callback-expected.txt (204539 => 204540)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/custom-elements/attribute-changed-callback-expected.txt        2016-08-16 23:31:38 UTC (rev 204539)
+++ trunk/LayoutTests/fast/custom-elements/attribute-changed-callback-expected.txt        2016-08-16 23:55:49 UTC (rev 204540)
</span><span class="lines">@@ -3,4 +3,8 @@
</span><span class="cx"> PASS setAttributeNS and removeAttributeNS must enqueue and invoke attributeChangedCallback
</span><span class="cx"> PASS setAttributeNode and removeAttributeNS must enqueue and invoke attributeChangedCallback
</span><span class="cx"> PASS setAttributeNode and removeAttributeNS must enqueue and invoke attributeChangedCallback
</span><ins>+PASS Mutating attributeChangedCallback after calling customElements.define must not affect the callback being invoked
+PASS attributedChangedCallback must not be invoked when the observed attributes does not contain the attribute.
+PASS Mutating observedAttributes after calling customElements.define must not affect the set of attributes for which attributedChangedCallback is invoked
+PASS attributedChangedCallback must be enqueued for attributes specified in a non-Array iterable observedAttributes
</ins><span class="cx">
</span></span></pre></div>
<a id="trunkLayoutTestsfastcustomelementsattributechangedcallbackhtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/fast/custom-elements/attribute-changed-callback.html (204539 => 204540)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/custom-elements/attribute-changed-callback.html        2016-08-16 23:31:38 UTC (rev 204539)
+++ trunk/LayoutTests/fast/custom-elements/attribute-changed-callback.html        2016-08-16 23:55:49 UTC (rev 204540)
</span><span class="lines">@@ -19,6 +19,7 @@
</span><span class="cx"> argumentList.push({arguments: arguments, value: this.getAttributeNS(namespace, name)});
</span><span class="cx"> }
</span><span class="cx"> }
</span><ins>+MyCustomElement.observedAttributes = ['title', 'id', 'r'];
</ins><span class="cx"> customElements.define('my-custom-element', MyCustomElement);
</span><span class="cx">
</span><span class="cx"> test(function () {
</span><span class="lines">@@ -96,9 +97,97 @@
</span><span class="cx"> assert_equals(argumentList.length, 2);
</span><span class="cx"> assert_equals(argumentList[1].value, null);
</span><span class="cx"> assert_array_equals(argumentList[1].arguments, ['r', '100', null, 'http://www.w3.org/2000/svg']);
</span><del>-
</del><span class="cx"> }, 'setAttributeNode and removeAttributeNS must enqueue and invoke attributeChangedCallback');
</span><span class="cx">
</span><ins>+test(function () {
+ var callsToOld = [];
+ var callsToNew = [];
+ class CustomElement extends HTMLElement { }
+ CustomElement.prototype.attributeChangedCallback = function () {
+ callsToOld.push(Array.from(arguments));
+ }
+ CustomElement.observedAttributes = ['title'];
+ customElements.define('element-with-mutated-attribute-changed-callback', CustomElement);
+ CustomElement.prototype.attributeChangedCallback = function () {
+ callsToNew.push(Array.from(arguments));
+ }
+
+ var instance = document.createElement('element-with-mutated-attribute-changed-callback');
+ instance.setAttribute('title', 'hi');
+ assert_equals(instance.getAttribute('title'), 'hi');
+ assert_array_equals(callsToNew, []);
+ assert_equals(callsToOld.length, 1);
+ assert_array_equals(callsToOld[0], ['title', null, 'hi', null]);
+}, 'Mutating attributeChangedCallback after calling customElements.define must not affect the callback being invoked');
+
+test(function () {
+ var calls = [];
+ class CustomElement extends HTMLElement {
+ attributeChangedCallback() {
+ calls.push(Array.from(arguments));
+ }
+ }
+ CustomElement.observedAttributes = ['title'];
+ customElements.define('element-not-observing-id-attribute', CustomElement);
+
+ var instance = document.createElement('element-not-observing-id-attribute');
+ instance.setAttribute('title', 'hi');
+ assert_equals(calls.length, 1);
+ assert_array_equals(calls[0], ['title', null, 'hi', null]);
+ instance.setAttribute('id', 'some');
+ assert_equals(calls.length, 1);
+}, 'attributedChangedCallback must not be invoked when the observed attributes does not contain the attribute.');
+
+test(function () {
+ var calls = [];
+ class CustomElement extends HTMLElement { }
+ CustomElement.prototype.attributeChangedCallback = function () {
+ calls.push(Array.from(arguments));
+ }
+ CustomElement.observedAttributes = ['title', 'lang'];
+ customElements.define('element-with-mutated-observed-attributes', CustomElement);
+ CustomElement.observedAttributes = ['title', 'id'];
+
+ var instance = document.createElement('element-with-mutated-observed-attributes');
+ instance.setAttribute('title', 'hi');
+ assert_equals(calls.length, 1);
+ assert_array_equals(calls[0], ['title', null, 'hi', null]);
+
+ instance.setAttribute('id', 'some');
+ assert_equals(calls.length, 1);
+
+ instance.setAttribute('lang', 'en');
+ assert_equals(calls.length, 2);
+ assert_array_equals(calls[0], ['title', null, 'hi', null]);
+ assert_array_equals(calls[1], ['lang', null, 'en', null]);
+}, 'Mutating observedAttributes after calling customElements.define must not affect the set of attributes for which attributedChangedCallback is invoked');
+
+test(function () {
+ var calls = [];
+ class CustomElement extends HTMLElement { }
+ CustomElement.prototype.attributeChangedCallback = function () {
+ calls.push(Array.from(arguments));
+ }
+ CustomElement.observedAttributes = { [Symbol.iterator]: function *() { yield 'lang'; yield 'style'; } };
+ customElements.define('element-with-generator-observed-attributes', CustomElement);
+
+ var instance = document.createElement('element-with-generator-observed-attributes');
+ instance.setAttribute('lang', 'en');
+ assert_equals(calls.length, 1);
+ assert_array_equals(calls[0], ['lang', null, 'en', null]);
+
+ instance.setAttribute('lang', 'ja');
+ assert_equals(calls.length, 2);
+ assert_array_equals(calls[1], ['lang', 'en', 'ja', null]);
+
+ instance.setAttribute('title', 'hello');
+ assert_equals(calls.length, 2);
+
+ instance.setAttribute('style', 'font-size: 2rem');
+ assert_equals(calls.length, 3);
+ assert_array_equals(calls[2], ['style', null, 'font-size: 2rem', null]);
+}, 'attributedChangedCallback must be enqueued for attributes specified in a non-Array iterable observedAttributes');
+
</ins><span class="cx"> </script>
</span><span class="cx"> </body>
</span><span class="cx"> </html>
</span></span></pre></div>
<a id="trunkLayoutTestsfastcustomelementslifecyclecallbacktiminghtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/fast/custom-elements/lifecycle-callback-timing.html (204539 => 204540)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/custom-elements/lifecycle-callback-timing.html        2016-08-16 23:31:38 UTC (rev 204539)
+++ trunk/LayoutTests/fast/custom-elements/lifecycle-callback-timing.html        2016-08-16 23:55:49 UTC (rev 204540)
</span><span class="lines">@@ -20,6 +20,7 @@
</span><span class="cx">
</span><span class="cx"> handler() { }
</span><span class="cx"> }
</span><ins>+MyCustomElement.observedAttributes = ['data-title', 'title'];
</ins><span class="cx"> customElements.define('my-custom-element', MyCustomElement);
</span><span class="cx">
</span><span class="cx"> test(function () {
</span></span></pre></div>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (204539 => 204540)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog        2016-08-16 23:31:38 UTC (rev 204539)
+++ trunk/Source/WebCore/ChangeLog        2016-08-16 23:55:49 UTC (rev 204540)
</span><span class="lines">@@ -1,3 +1,45 @@
</span><ins>+2016-08-16 Ryosuke Niwa <rniwa@webkit.org>
+
+ customElements.define should retrieve lifecycle callbacks
+ https://bugs.webkit.org/show_bug.cgi?id=160797
+
+ Reviewed by Chris Dumez.
+
+ Updated JSCustomElementInterface to invoke Get(constructor, "prototype") and Get(prototype, callbackName)
+ for each lifecycle callback as required by the latest specification:
+ https://html.spec.whatwg.org/#dom-customelementsregistry-define
+
+ Also added the support for "observedAttributes" property on the custom elements constructor which defines
+ the list of attributes for which attributeChangedCallback is invoked.
+
+ Test: fast/custom-elements/CustomElementsRegistry.html
+
+ * bindings/js/JSCustomElementInterface.cpp:
+ (WebCore::JSCustomElementInterface::setAttributeChangedCallback): Added.
+ (WebCore::JSCustomElementInterface::attributeChanged): Invoke m_attributeChangedCallback instead of on the
+ result of Get(element, "attributeChangedCallback").
+ * bindings/js/JSCustomElementInterface.h:
+ (WebCore::JSCustomElementInterface::observesAttribute): Added.
+
+ * bindings/js/JSCustomElementsRegistryCustom.cpp:
+ (WebCore::getLifecycleCallback): Added.
+ (WebCore::JSCustomElementsRegistry::define): Invoke Get(prototype, callbackName) for each callback. Also
+ store attributedChangedCallback and observedAttributes to JSCustomElementInterface. Other callbacks will
+ be stored in the future when the support for those callbacks are added.
+
+ * dom/Element.cpp:
+ (WebCore::Element::attributeChanged): Moved more code into enqueueAttributeChangedCallbackIfNeeded.
+
+ * dom/LifecycleCallbackQueue.cpp:
+ (WebCore::LifecycleCallbackQueue::enqueueAttributeChangedCallbackIfNeeded): Added an early exit for when
+ the given attribute is not observed by the custom element. Also moved the logic to retrieve
+ JSCustomElementInterface from Element::attributeChanged and renamed it from enqueueAttributeChangedCallback.
+
+ * bindings/js/JSDOMBinding.h:
+ (WebCore::toNativeArray): Throw a TypeError when the argument is not an object.
+ * bindings/js/JSDOMConvert.h:
+ (WebCore::Converter<Vector<T>>::convert): Removed a FIXME comment.
+
</ins><span class="cx"> 2016-08-16 Anders Carlsson <andersca@apple.com>
</span><span class="cx">
</span><span class="cx"> Fix build.
</span></span></pre></div>
<a id="trunkSourceWebCorebindingsjsJSCustomElementInterfacecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/bindings/js/JSCustomElementInterface.cpp (204539 => 204540)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/bindings/js/JSCustomElementInterface.cpp        2016-08-16 23:31:38 UTC (rev 204539)
+++ trunk/Source/WebCore/bindings/js/JSCustomElementInterface.cpp        2016-08-16 23:55:49 UTC (rev 204540)
</span><span class="lines">@@ -151,6 +151,14 @@
</span><span class="cx"> ASSERT(wrappedElement->isCustomElement());
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+void JSCustomElementInterface::setAttributeChangedCallback(JSC::JSObject* callback, const Vector<String>& observedAttributes)
+{
+ m_attributeChangedCallback = callback;
+ m_observedAttributes.clear();
+ for (auto& name : observedAttributes)
+ m_observedAttributes.add(name);
+}
+
</ins><span class="cx"> void JSCustomElementInterface::attributeChanged(Element& element, const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue)
</span><span class="cx"> {
</span><span class="cx"> if (!canInvokeCallback())
</span><span class="lines">@@ -170,12 +178,9 @@
</span><span class="cx">
</span><span class="cx"> JSObject* jsElement = asObject(toJS(state, globalObject, element));
</span><span class="cx">
</span><del>- PropertyName attributeChanged(Identifier::fromString(state, "attributeChangedCallback"));
- JSValue callback = jsElement->get(state, attributeChanged);
</del><span class="cx"> CallData callData;
</span><del>- CallType callType = getCallData(callback, callData);
- if (callType == CallType::None)
- return;
</del><ins>+ CallType callType = m_attributeChangedCallback->methodTable()->getCallData(m_attributeChangedCallback.get(), callData);
+ ASSERT(callType != CallType::None);
</ins><span class="cx">
</span><span class="cx"> const AtomicString& namespaceURI = attributeName.namespaceURI();
</span><span class="cx"> MarkedArgumentBuffer args;
</span><span class="lines">@@ -187,7 +192,7 @@
</span><span class="cx"> InspectorInstrumentationCookie cookie = JSMainThreadExecState::instrumentFunctionCall(context, callType, callData);
</span><span class="cx">
</span><span class="cx"> NakedPtr<Exception> exception;
</span><del>- JSMainThreadExecState::call(state, callback, callType, callData, jsElement, args, exception);
</del><ins>+ JSMainThreadExecState::call(state, m_attributeChangedCallback.get(), callType, callData, jsElement, args, exception);
</ins><span class="cx">
</span><span class="cx"> InspectorInstrumentation::didCallFunction(cookie, context);
</span><span class="cx">
</span></span></pre></div>
<a id="trunkSourceWebCorebindingsjsJSCustomElementInterfaceh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/bindings/js/JSCustomElementInterface.h (204539 => 204540)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/bindings/js/JSCustomElementInterface.h        2016-08-16 23:31:38 UTC (rev 204539)
+++ trunk/Source/WebCore/bindings/js/JSCustomElementInterface.h        2016-08-16 23:55:49 UTC (rev 204540)
</span><span class="lines">@@ -37,6 +37,7 @@
</span><span class="cx"> #include <wtf/Forward.h>
</span><span class="cx"> #include <wtf/RefCounted.h>
</span><span class="cx"> #include <wtf/RefPtr.h>
</span><ins>+#include <wtf/text/AtomicStringHash.h>
</ins><span class="cx">
</span><span class="cx"> namespace JSC {
</span><span class="cx">
</span><span class="lines">@@ -65,6 +66,8 @@
</span><span class="cx">
</span><span class="cx"> void upgradeElement(Element&);
</span><span class="cx">
</span><ins>+ void setAttributeChangedCallback(JSC::JSObject* callback, const Vector<String>& observedAttributes);
+ bool observesAttribute(const AtomicString& name) const { return m_observedAttributes.contains(name); }
</ins><span class="cx"> void attributeChanged(Element&, const QualifiedName&, const AtomicString& oldValue, const AtomicString& newValue);
</span><span class="cx">
</span><span class="cx"> ScriptExecutionContext* scriptExecutionContext() const { return ContextDestructionObserver::scriptExecutionContext(); }
</span><span class="lines">@@ -83,8 +86,10 @@
</span><span class="cx">
</span><span class="cx"> QualifiedName m_name;
</span><span class="cx"> mutable JSC::Weak<JSC::JSObject> m_constructor;
</span><ins>+ mutable JSC::Weak<JSC::JSObject> m_attributeChangedCallback;
</ins><span class="cx"> RefPtr<DOMWrapperWorld> m_isolatedWorld;
</span><span class="cx"> Vector<RefPtr<Element>, 1> m_constructionStack;
</span><ins>+ HashSet<AtomicString> m_observedAttributes;
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> } // namespace WebCore
</span></span></pre></div>
<a id="trunkSourceWebCorebindingsjsJSCustomElementsRegistryCustomcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/bindings/js/JSCustomElementsRegistryCustom.cpp (204539 => 204540)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/bindings/js/JSCustomElementsRegistryCustom.cpp        2016-08-16 23:31:38 UTC (rev 204539)
+++ trunk/Source/WebCore/bindings/js/JSCustomElementsRegistryCustom.cpp        2016-08-16 23:55:49 UTC (rev 204540)
</span><span class="lines">@@ -31,13 +31,29 @@
</span><span class="cx"> #include "HTMLNames.h"
</span><span class="cx"> #include "JSCustomElementInterface.h"
</span><span class="cx"> #include "JSDOMBinding.h"
</span><ins>+#include "JSDOMConvert.h"
</ins><span class="cx">
</span><span class="cx"> using namespace JSC;
</span><span class="cx">
</span><span class="cx"> namespace WebCore {
</span><span class="cx">
</span><del>-
</del><span class="cx"> #if ENABLE(CUSTOM_ELEMENTS)
</span><ins>+
+static JSObject* getLifecycleCallback(ExecState& state, JSObject& prototype, const Identifier& id)
+{
+ JSValue callback = prototype.get(&state, id);
+ if (state.hadException())
+ return nullptr;
+ if (callback.isUndefined())
+ return nullptr;
+ if (!callback.isFunction()) {
+ throwTypeError(&state, ASCIILiteral("A lifecycle callback must be a function"));
+ return nullptr;
+ }
+ return callback.getObject();
+}
+
+// https://html.spec.whatwg.org/#dom-customelementsregistry-define
</ins><span class="cx"> JSValue JSCustomElementsRegistry::define(ExecState& state)
</span><span class="cx"> {
</span><span class="cx"> if (UNLIKELY(state.argumentCount() < 2))
</span><span class="lines">@@ -53,6 +69,7 @@
</span><span class="cx"> JSObject* constructor = constructorValue.getObject();
</span><span class="cx">
</span><span class="cx"> // FIXME: Throw a TypeError if constructor doesn't inherit from HTMLElement.
</span><ins>+ // https://github.com/w3c/webcomponents/issues/541
</ins><span class="cx">
</span><span class="cx"> switch (Document::validateCustomElementName(localName)) {
</span><span class="cx"> case CustomElementNameValidationStatus::Valid:
</span><span class="lines">@@ -65,6 +82,9 @@
</span><span class="cx"> return throwSyntaxError(&state, ASCIILiteral("Custom element name cannot contain an upper case letter"));
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ // FIXME: Check re-entrancy here.
+ // https://github.com/w3c/webcomponents/issues/545
+
</ins><span class="cx"> CustomElementsRegistry& registry = wrapped();
</span><span class="cx"> if (registry.findInterface(localName)) {
</span><span class="cx"> throwNotSupportedError(state, ASCIILiteral("Cannot define multiple custom elements with the same tag name"));
</span><span class="lines">@@ -76,20 +96,51 @@
</span><span class="cx"> return jsUndefined();
</span><span class="cx"> }
</span><span class="cx">
</span><del>- // FIXME: 10. Let prototype be Get(constructor, "prototype"). Rethrow any exceptions.
- // FIXME: 11. If Type(prototype) is not Object, throw a TypeError exception.
- // FIXME: 12. Let attachedCallback be Get(prototype, "attachedCallback"). Rethrow any exceptions.
- // FIXME: 13. Let detachedCallback be Get(prototype, "detachedCallback"). Rethrow any exceptions.
- // FIXME: 14. Let attributeChangedCallback be Get(prototype, "attributeChangedCallback"). Rethrow any exceptions.
</del><ins>+ auto& vm = globalObject()->vm();
+ JSValue prototypeValue = constructor->get(&state, vm.propertyNames->prototype);
+ if (state.hadException())
+ return jsUndefined();
+ if (!prototypeValue.isObject())
+ return throwTypeError(&state, ASCIILiteral("Custom element constructor's prototype must be an object"));
+ JSObject& prototypeObject = *asObject(prototypeValue);
</ins><span class="cx">
</span><del>- PrivateName uniquePrivateName;
- globalObject()->putDirect(globalObject()->vm(), uniquePrivateName, constructor);
</del><ins>+ // FIXME: Add the support for connectedCallback.
+ getLifecycleCallback(state, prototypeObject, Identifier::fromString(&vm, "connectedCallback"));
+ if (state.hadException())
+ return jsUndefined();
</ins><span class="cx">
</span><ins>+ // FIXME: Add the support for disconnectedCallback.
+ getLifecycleCallback(state, prototypeObject, Identifier::fromString(&vm, "disconnectedCallback"));
+ if (state.hadException())
+ return jsUndefined();
+
+ // FIXME: Add the support for adoptedCallback.
+ getLifecycleCallback(state, prototypeObject, Identifier::fromString(&vm, "adoptedCallback"));
+ if (state.hadException())
+ return jsUndefined();
+
</ins><span class="cx"> QualifiedName name(nullAtom, localName, HTMLNames::xhtmlNamespaceURI);
</span><del>- registry.addElementDefinition(JSCustomElementInterface::create(name, constructor, globalObject()));
</del><ins>+ auto interface = JSCustomElementInterface::create(name, constructor, globalObject());
</ins><span class="cx">
</span><ins>+ auto* attributeChangedCallback = getLifecycleCallback(state, prototypeObject, Identifier::fromString(&vm, "attributeChangedCallback"));
+ if (state.hadException())
+ return jsUndefined();
+ if (attributeChangedCallback) {
+ auto value = convertOptional<Vector<String>>(state, constructor->get(&state, Identifier::fromString(&state, "observedAttributes")));
+ if (state.hadException())
+ return jsUndefined();
+ if (value)
+ interface->setAttributeChangedCallback(attributeChangedCallback, *value);
+ }
+
+ PrivateName uniquePrivateName;
+ globalObject()->putDirect(vm, uniquePrivateName, constructor);
+
+ registry.addElementDefinition(WTFMove(interface));
+
</ins><span class="cx"> // FIXME: 17. Let map be registry's upgrade candidates map.
</span><span class="cx"> // FIXME: 18. Upgrade a newly-defined element given map and definition.
</span><ins>+ // FIXME: 19. Resolve whenDefined promise.
</ins><span class="cx">
</span><span class="cx"> return jsUndefined();
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceWebCoredomElementcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/Element.cpp (204539 => 204540)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/Element.cpp        2016-08-16 23:31:38 UTC (rev 204539)
+++ trunk/Source/WebCore/dom/Element.cpp        2016-08-16 23:55:49 UTC (rev 204540)
</span><span class="lines">@@ -1290,15 +1290,8 @@
</span><span class="cx"> document().incDOMTreeVersion();
</span><span class="cx">
</span><span class="cx"> #if ENABLE(CUSTOM_ELEMENTS)
</span><del>- if (UNLIKELY(isCustomElement())) {
- if (auto* window = document().domWindow()) {
- if (auto* registry = window->customElementsRegistry()) {
- auto* elementInterface = registry->findInterface(tagQName());
- RELEASE_ASSERT(elementInterface);
- LifecycleCallbackQueue::enqueueAttributeChangedCallback(*this, *elementInterface, name, oldValue, newValue);
- }
- }
- }
</del><ins>+ if (UNLIKELY(isCustomElement()))
+ LifecycleCallbackQueue::enqueueAttributeChangedCallbackIfNeeded(*this, name, oldValue, newValue);
</ins><span class="cx"> #endif
</span><span class="cx">
</span><span class="cx"> if (valueIsSameAsBefore)
</span></span></pre></div>
<a id="trunkSourceWebCoredomLifecycleCallbackQueuecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/LifecycleCallbackQueue.cpp (204539 => 204540)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/LifecycleCallbackQueue.cpp        2016-08-16 23:31:38 UTC (rev 204539)
+++ trunk/Source/WebCore/dom/LifecycleCallbackQueue.cpp        2016-08-16 23:55:49 UTC (rev 204540)
</span><span class="lines">@@ -28,6 +28,8 @@
</span><span class="cx">
</span><span class="cx"> #if ENABLE(CUSTOM_ELEMENTS)
</span><span class="cx">
</span><ins>+#include "CustomElementsRegistry.h"
+#include "DOMWindow.h"
</ins><span class="cx"> #include "Document.h"
</span><span class="cx"> #include "Element.h"
</span><span class="cx"> #include "JSCustomElementInterface.h"
</span><span class="lines">@@ -96,11 +98,23 @@
</span><span class="cx"> queue->m_items.append(LifecycleQueueItem(LifecycleQueueItem::Type::ElementUpgrade, element, elementInterface));
</span><span class="cx"> }
</span><span class="cx">
</span><del>-void LifecycleCallbackQueue::enqueueAttributeChangedCallback(Element& element, JSCustomElementInterface& elementInterface,
- const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue)
</del><ins>+void LifecycleCallbackQueue::enqueueAttributeChangedCallbackIfNeeded(Element& element, const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue)
</ins><span class="cx"> {
</span><ins>+ ASSERT(element.isCustomElement());
+ auto* window = element.document().domWindow();
+ if (!window)
+ return;
+
+ auto* registry = window->customElementsRegistry();
+ if (!registry)
+ return;
+
+ auto* elementInterface = registry->findInterface(element.tagQName());
+ if (!elementInterface->observesAttribute(attributeName.localName()))
+ return;
+
</ins><span class="cx"> if (auto* queue = CustomElementLifecycleProcessingStack::ensureCurrentQueue())
</span><del>- queue->m_items.append(LifecycleQueueItem(element, elementInterface, attributeName, oldValue, newValue));
</del><ins>+ queue->m_items.append(LifecycleQueueItem(element, *elementInterface, attributeName, oldValue, newValue));
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> void LifecycleCallbackQueue::invokeAll()
</span></span></pre></div>
<a id="trunkSourceWebCoredomLifecycleCallbackQueueh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/LifecycleCallbackQueue.h (204539 => 204540)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/LifecycleCallbackQueue.h        2016-08-16 23:31:38 UTC (rev 204539)
+++ trunk/Source/WebCore/dom/LifecycleCallbackQueue.h        2016-08-16 23:55:49 UTC (rev 204540)
</span><span class="lines">@@ -23,8 +23,7 @@
</span><span class="cx"> * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</span><span class="cx"> */
</span><span class="cx">
</span><del>-#ifndef LifecycleCallbackQueue_h
-#define LifecycleCallbackQueue_h
</del><ins>+#pragma once
</ins><span class="cx">
</span><span class="cx"> #if ENABLE(CUSTOM_ELEMENTS)
</span><span class="cx">
</span><span class="lines">@@ -47,10 +46,8 @@
</span><span class="cx"> ~LifecycleCallbackQueue();
</span><span class="cx">
</span><span class="cx"> static void enqueueElementUpgrade(Element&, JSCustomElementInterface&);
</span><ins>+ static void enqueueAttributeChangedCallbackIfNeeded(Element&, const QualifiedName&, const AtomicString& oldValue, const AtomicString& newValue);
</ins><span class="cx">
</span><del>- static void enqueueAttributeChangedCallback(Element&, JSCustomElementInterface&,
- const QualifiedName&, const AtomicString& oldValue, const AtomicString& newValue);
-
</del><span class="cx"> void invokeAll();
</span><span class="cx">
</span><span class="cx"> private:
</span><span class="lines">@@ -89,5 +86,3 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> #endif
</span><del>-
-#endif // LifecycleCallbackQueue_h
</del></span></pre>
</div>
</div>
</body>
</html>