Changeset 204540 in webkit


Ignore:
Timestamp:
Aug 16, 2016 4:55:49 PM (8 years ago)
Author:
rniwa@webkit.org
Message:

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:
Location:
trunk
Files:
11 edited
2 moved

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r204536 r204540  
     12016-08-16  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        customElements.define should retrieve lifecycle callbacks
     4        https://bugs.webkit.org/show_bug.cgi?id=160797
     5
     6        Reviewed by Chris Dumez.
     7
     8        Added test cases for CustomElementsRegistry.define to make sure it invokes Get(constructor, "prototype")
     9        and Get(prototype, callbackName) for each lifecycle callback.
     10
     11        Also updated the tests to reflect the support for observedAttributes which specifies the list of attributes
     12        for which attributeChangedCallback is invoked.
     13
     14        * fast/custom-elements/CustomElementsRegistry-expected.txt: Renamed from Document-defineElement-expected.txt.
     15        * fast/custom-elements/CustomElementsRegistry.html: Renamed from Document-defineElement.html.
     16        * fast/custom-elements/Document-defineElement-expected.txt: Removed.
     17        * fast/custom-elements/Document-defineElement.html: Removed.
     18        * fast/custom-elements/attribute-changed-callback-expected.txt:
     19        * fast/custom-elements/attribute-changed-callback.html: Added test cases for "observedAttributes".
     20        * fast/custom-elements/lifecycle-callback-timing.html:
     21
    1222016-08-16  Chris Dumez  <cdumez@apple.com>
    223
  • trunk/LayoutTests/fast/custom-elements/CustomElementsRegistry-expected.txt

    r204539 r204540  
    11
    2 PASS Check the existence of CustomElementsRegistry.prototype.define on CustomElementsRegistry interface
    3 PASS customElements.define should throw with an invalid name
    4 PASS customElements.define should throw when there is already a custom element of the same name
    5 PASS customElements.define should throw when there is already a custom element with the same class
    6 PASS customElements.define should throw when the element interface is not a constructor
    7 PASS customElements.define should define an instantiatable custom element
     2PASS CustomElementsRegistry interface must have define as a method
     3PASS customElements.define must throw with an invalid name
     4PASS customElements.define must throw when there is already a custom element of the same name
     5PASS customElements.define must throw when there is already a custom element with the same class
     6PASS customElements.define must throw when the element interface is not a constructor
     7PASS customElements.define must get "prototype" property of the constructor
     8PASS customElements.define must rethrow an exception thrown while getting "prototype" property of the constructor
     9PASS customElements.define must throw when "prototype" property of the constructor is not an object
     10PASS customElements.define must get callbacks of the constructor prototype
     11PASS customElements.define must rethrow an exception thrown while getting callbacks on the constructor prototype
     12PASS customElements.define must rethrow an exception thrown while converting a callback value to Function callback type
     13PASS customElements.define must get "observedAttributes" property on the constructor prototype when "attributeChangedCallback" is present
     14PASS customElements.define must rethrow an exception thrown while getting observedAttributes on the constructor prototype
     15PASS customElements.define must rethrow an exception thrown while converting the value of observedAttributes to sequence<DOMString>
     16PASS customElements.define must rethrow an exception thrown while iterating over observedAttributes to sequence<DOMString>
     17PASS customElements.define must rethrow an exception thrown while retrieving Symbol.iterator on observedAttributes
     18PASS customElements.define must not throw even if "observedAttributes" fails to convert if "attributeChangedCallback" is not defined
     19PASS customElements.define must define an instantiatable custom element
    820
  • trunk/LayoutTests/fast/custom-elements/CustomElementsRegistry.html

    r204539 r204540  
    22<html>
    33<head>
    4 <title>Custom Elements: Extensions to Document interface</title>
     4<title>Custom Elements: CustomElementsRegistry interface</title>
    55<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
    6 <meta name="assert" content="customElements.define should define a custom element">
     6<meta name="assert" content="CustomElementsRegistry interface must exist">
    77<script src="../../resources/testharness.js"></script>
    88<script src="../../resources/testharnessreport.js"></script>
     
    1616    assert_true('define' in CustomElementsRegistry.prototype, '"define" exists on CustomElementsRegistry.prototype');
    1717    assert_true('define' in customElements, '"define" exists on window.customElements');
    18 }, 'Check the existence of CustomElementsRegistry.prototype.define on CustomElementsRegistry interface');
     18}, 'CustomElementsRegistry interface must have define as a method');
    1919
    2020test(function () {
     
    4646    }
    4747
    48 }, 'customElements.define should throw with an invalid name');
     48}, 'customElements.define must throw with an invalid name');
    4949
    5050test(function () {
     
    5656        'customElements.define must throw a NotSupportedError if the specified tag name is already used');
    5757
    58 }, 'customElements.define should throw when there is already a custom element of the same name');
     58}, 'customElements.define must throw when there is already a custom element of the same name');
    5959
    6060test(function () {
     
    6565        'customElements.define must throw a NotSupportedError if the specified class already defines an element');
    6666
    67 }, 'customElements.define should throw when there is already a custom element with the same class');
     67}, 'customElements.define must throw when there is already a custom element with the same class');
    6868
    6969test(function () {
     
    7676    assert_throws({'name': 'TypeError'}, function () { customElements.define('invalid-element', []); },
    7777        'customElements.define must throw a TypeError when the element interface is an array');
    78 }, 'customElements.define should throw when the element interface is not a constructor');
     78}, 'customElements.define must throw when the element interface is not a constructor');
     79
     80test(function () {
     81    var calls = [];
     82    var proxy = new Proxy(class extends HTMLElement { }, {
     83        get: function (target, name) {
     84            calls.push(name);
     85            return target[name];
     86        }
     87    });
     88    customElements.define('proxy-element', proxy);
     89    assert_array_equals(calls, ['prototype']);
     90}, 'customElements.define must get "prototype" property of the constructor');
     91
     92test(function () {
     93    var proxy = new Proxy(class extends HTMLElement { }, {
     94        get: function (target, name) {
     95            throw {name: 'expectedError'};
     96        }
     97    });
     98    assert_throws({'name': 'expectedError'}, function () { customElements.define('element-with-string-prototype', proxy); });
     99}, 'customElements.define must rethrow an exception thrown while getting "prototype" property of the constructor');
     100
     101test(function () {
     102    var returnedValue;
     103    var proxy = new Proxy(class extends HTMLElement { }, {
     104        get: function (target, name) { return returnedValue; }
     105    });
     106
     107    returnedValue = null;
     108    assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-string-prototype', proxy); },
     109        'customElements.define must throw when "prototype" property of the constructor is null');
     110    returnedValue = undefined;
     111    assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-string-prototype', proxy); },
     112        'customElements.define must throw when "prototype" property of the constructor is undefined');
     113    returnedValue = 'hello';
     114    assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-string-prototype', proxy); },
     115        'customElements.define must throw when "prototype" property of the constructor is a string');
     116    returnedValue = 1;
     117    assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-string-prototype', proxy); },
     118        'customElements.define must throw when "prototype" property of the constructor is a number');
     119
     120}, 'customElements.define must throw when "prototype" property of the constructor is not an object');
     121
     122test(function () {
     123    var constructor = function () {}
     124    var calls = [];
     125    constructor.prototype = new Proxy(constructor.prototype, {
     126        get: function (target, name) {
     127            calls.push(name);
     128            return target[name];
     129        }
     130    });
     131    customElements.define('element-with-proxy-prototype', constructor);
     132    assert_array_equals(calls, ['connectedCallback', 'disconnectedCallback', 'adoptedCallback', 'attributeChangedCallback']);
     133}, 'customElements.define must get callbacks of the constructor prototype');
     134
     135test(function () {
     136    var constructor = function () {}
     137    var calls = [];
     138    constructor.prototype = new Proxy(constructor.prototype, {
     139        get: function (target, name) {
     140            calls.push(name);
     141            if (name == 'disconnectedCallback')
     142                throw {name: 'expectedError'};
     143            return target[name];
     144        }
     145    });
     146    assert_throws({'name': 'expectedError'}, function () { customElements.define('element-with-throwing-callback', constructor); });
     147    assert_array_equals(calls, ['connectedCallback', 'disconnectedCallback'],
     148        'customElements.define must not get callbacks after one of the get throws');
     149}, 'customElements.define must rethrow an exception thrown while getting callbacks on the constructor prototype');
     150
     151test(function () {
     152    var constructor = function () {}
     153    var calls = [];
     154    constructor.prototype = new Proxy(constructor.prototype, {
     155        get: function (target, name) {
     156            calls.push(name);
     157            if (name == 'adoptedCallback')
     158                return 1;
     159            return target[name];
     160        }
     161    });
     162    assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-throwing-callback', constructor); });
     163    assert_array_equals(calls, ['connectedCallback', 'disconnectedCallback', 'adoptedCallback'],
     164        'customElements.define must not get callbacks after one of the conversion throws');
     165}, 'customElements.define must rethrow an exception thrown while converting a callback value to Function callback type');
     166
     167test(function () {
     168    var constructor = function () {}
     169    constructor.prototype.attributeChangedCallback = function () { };
     170    var prototypeCalls = [];
     171    var callOrder = 0;
     172    constructor.prototype = new Proxy(constructor.prototype, {
     173        get: function (target, name) {
     174            if (name == 'prototype' || name == 'observedAttributes')
     175                throw 'Unexpected access to observedAttributes';
     176            prototypeCalls.push(callOrder++);   
     177            prototypeCalls.push(name);
     178            return target[name];
     179        }
     180    });
     181    var constructorCalls = [];
     182    var proxy = new Proxy(constructor, {
     183        get: function (target, name) {
     184            constructorCalls.push(callOrder++);   
     185            constructorCalls.push(name);
     186            return target[name];
     187        }
     188    });
     189    customElements.define('element-with-attribute-changed-callback', proxy);
     190    assert_array_equals(prototypeCalls, [1, 'connectedCallback', 2, 'disconnectedCallback', 3, 'adoptedCallback', 4, 'attributeChangedCallback']);
     191    assert_array_equals(constructorCalls, [0, 'prototype', 5, 'observedAttributes']);
     192}, 'customElements.define must get "observedAttributes" property on the constructor prototype when "attributeChangedCallback" is present');
     193
     194test(function () {
     195    var constructor = function () {}
     196    constructor.prototype.attributeChangedCallback = function () { };
     197    var calls = [];
     198    var proxy = new Proxy(constructor, {
     199        get: function (target, name) {
     200            calls.push(name);
     201            if (name == 'observedAttributes')
     202                throw {name: 'expectedError'};
     203            return target[name];
     204        }
     205    });
     206    assert_throws({'name': 'expectedError'}, function () { customElements.define('element-with-throwing-observed-attributes', proxy); });
     207    assert_array_equals(calls, ['prototype', 'observedAttributes'],
     208        'customElements.define must get "prototype" and "observedAttributes" on the constructor');
     209}, 'customElements.define must rethrow an exception thrown while getting observedAttributes on the constructor prototype');
     210
     211test(function () {
     212    var constructor = function () {}
     213    constructor.prototype.attributeChangedCallback = function () { };
     214    var calls = [];
     215    var proxy = new Proxy(constructor, {
     216        get: function (target, name) {
     217            calls.push(name);
     218            if (name == 'observedAttributes')
     219                return 1;
     220            return target[name];
     221        }
     222    });
     223    assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-invalid-observed-attributes', proxy); });
     224    assert_array_equals(calls, ['prototype', 'observedAttributes'],
     225        'customElements.define must get "prototype" and "observedAttributes" on the constructor');
     226}, 'customElements.define must rethrow an exception thrown while converting the value of observedAttributes to sequence<DOMString>');
     227
     228test(function () {
     229    var constructor = function () {}
     230    constructor.prototype.attributeChangedCallback = function () { };
     231    constructor.observedAttributes = {[Symbol.iterator]: function *() {
     232        yield 'foo';
     233        throw {name: 'SomeError'};
     234    }};
     235    assert_throws({'name': 'SomeError'}, function () { customElements.define('element-with-generator-observed-attributes', constructor); });
     236}, 'customElements.define must rethrow an exception thrown while iterating over observedAttributes to sequence<DOMString>');
     237
     238test(function () {
     239    var constructor = function () {}
     240    constructor.prototype.attributeChangedCallback = function () { };
     241    constructor.observedAttributes = {[Symbol.iterator]: 1};
     242    assert_throws({'name': 'TypeError'}, function () { customElements.define('element-with-observed-attributes-with-uncallable-iterator', constructor); });
     243}, 'customElements.define must rethrow an exception thrown while retrieving Symbol.iterator on observedAttributes');
     244
     245test(function () {
     246    var constructor = function () {}
     247    constructor.observedAttributes = 1;
     248    customElements.define('element-without-callback-with-invalid-observed-attributes', constructor);
     249}, 'customElements.define must not throw even if "observedAttributes" fails to convert if "attributeChangedCallback" is not defined');
    79250
    80251test(function () {
     
    95266        'A custom element HTML must use HTML namespace');
    96267
    97 }, 'customElements.define should define an instantiatable custom element');
     268}, 'customElements.define must define an instantiatable custom element');
    98269
    99270</script>
  • trunk/LayoutTests/fast/custom-elements/attribute-changed-callback-expected.txt

    r197611 r204540  
    44PASS setAttributeNode and removeAttributeNS must enqueue and invoke attributeChangedCallback
    55PASS setAttributeNode and removeAttributeNS must enqueue and invoke attributeChangedCallback
     6PASS Mutating attributeChangedCallback after calling customElements.define must not affect the callback being invoked
     7PASS attributedChangedCallback must not be invoked when the observed attributes does not contain the attribute.
     8PASS Mutating observedAttributes after calling customElements.define must not affect the set of attributes for which attributedChangedCallback is invoked
     9PASS attributedChangedCallback must be enqueued for attributes specified in a non-Array iterable observedAttributes
    610
  • trunk/LayoutTests/fast/custom-elements/attribute-changed-callback.html

    r204367 r204540  
    2020    }
    2121}
     22MyCustomElement.observedAttributes = ['title', 'id', 'r'];
    2223customElements.define('my-custom-element', MyCustomElement);
    2324
     
    9798    assert_equals(argumentList[1].value, null);
    9899    assert_array_equals(argumentList[1].arguments, ['r', '100', null, 'http://www.w3.org/2000/svg']);
     100}, 'setAttributeNode and removeAttributeNS must enqueue and invoke attributeChangedCallback');
    99101
    100 }, 'setAttributeNode and removeAttributeNS must enqueue and invoke attributeChangedCallback');
     102test(function () {
     103    var callsToOld = [];
     104    var callsToNew = [];
     105    class CustomElement extends HTMLElement { }
     106    CustomElement.prototype.attributeChangedCallback = function () {
     107        callsToOld.push(Array.from(arguments));
     108    }
     109    CustomElement.observedAttributes = ['title'];
     110    customElements.define('element-with-mutated-attribute-changed-callback', CustomElement);
     111    CustomElement.prototype.attributeChangedCallback = function () {
     112        callsToNew.push(Array.from(arguments));
     113    }
     114
     115    var instance = document.createElement('element-with-mutated-attribute-changed-callback');
     116    instance.setAttribute('title', 'hi');
     117    assert_equals(instance.getAttribute('title'), 'hi');
     118    assert_array_equals(callsToNew, []);
     119    assert_equals(callsToOld.length, 1);
     120    assert_array_equals(callsToOld[0], ['title', null, 'hi', null]);
     121}, 'Mutating attributeChangedCallback after calling customElements.define must not affect the callback being invoked');
     122
     123test(function () {
     124    var calls = [];
     125    class CustomElement extends HTMLElement {
     126        attributeChangedCallback() {
     127            calls.push(Array.from(arguments));
     128        }
     129    }
     130    CustomElement.observedAttributes = ['title'];
     131    customElements.define('element-not-observing-id-attribute', CustomElement);
     132
     133    var instance = document.createElement('element-not-observing-id-attribute');
     134    instance.setAttribute('title', 'hi');
     135    assert_equals(calls.length, 1);
     136    assert_array_equals(calls[0], ['title', null, 'hi', null]);
     137    instance.setAttribute('id', 'some');
     138    assert_equals(calls.length, 1);
     139}, 'attributedChangedCallback must not be invoked when the observed attributes does not contain the attribute.');
     140
     141test(function () {
     142    var calls = [];
     143    class CustomElement extends HTMLElement { }
     144    CustomElement.prototype.attributeChangedCallback = function () {
     145        calls.push(Array.from(arguments));
     146    }
     147    CustomElement.observedAttributes = ['title', 'lang'];
     148    customElements.define('element-with-mutated-observed-attributes', CustomElement);
     149    CustomElement.observedAttributes = ['title', 'id'];
     150
     151    var instance = document.createElement('element-with-mutated-observed-attributes');
     152    instance.setAttribute('title', 'hi');
     153    assert_equals(calls.length, 1);
     154    assert_array_equals(calls[0], ['title', null, 'hi', null]);
     155
     156    instance.setAttribute('id', 'some');
     157    assert_equals(calls.length, 1);
     158
     159    instance.setAttribute('lang', 'en');
     160    assert_equals(calls.length, 2);
     161    assert_array_equals(calls[0], ['title', null, 'hi', null]);
     162    assert_array_equals(calls[1], ['lang', null, 'en', null]);
     163}, 'Mutating observedAttributes after calling customElements.define must not affect the set of attributes for which attributedChangedCallback is invoked');
     164
     165test(function () {
     166    var calls = [];
     167    class CustomElement extends HTMLElement { }
     168    CustomElement.prototype.attributeChangedCallback = function () {
     169        calls.push(Array.from(arguments));
     170    }
     171    CustomElement.observedAttributes = { [Symbol.iterator]: function *() { yield 'lang'; yield 'style'; } };
     172    customElements.define('element-with-generator-observed-attributes', CustomElement);
     173
     174    var instance = document.createElement('element-with-generator-observed-attributes');
     175    instance.setAttribute('lang', 'en');
     176    assert_equals(calls.length, 1);
     177    assert_array_equals(calls[0], ['lang', null, 'en', null]);
     178
     179    instance.setAttribute('lang', 'ja');
     180    assert_equals(calls.length, 2);
     181    assert_array_equals(calls[1], ['lang', 'en', 'ja', null]);
     182
     183    instance.setAttribute('title', 'hello');
     184    assert_equals(calls.length, 2);
     185
     186    instance.setAttribute('style', 'font-size: 2rem');
     187    assert_equals(calls.length, 3);
     188    assert_array_equals(calls[2], ['style', null, 'font-size: 2rem', null]);
     189}, 'attributedChangedCallback must be enqueued for attributes specified in a non-Array iterable observedAttributes');
    101190
    102191</script>
  • trunk/LayoutTests/fast/custom-elements/lifecycle-callback-timing.html

    r204367 r204540  
    2121    handler() { }
    2222}
     23MyCustomElement.observedAttributes = ['data-title', 'title'];
    2324customElements.define('my-custom-element', MyCustomElement);
    2425
  • trunk/Source/WebCore/ChangeLog

    r204537 r204540  
     12016-08-16  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        customElements.define should retrieve lifecycle callbacks
     4        https://bugs.webkit.org/show_bug.cgi?id=160797
     5
     6        Reviewed by Chris Dumez.
     7
     8        Updated JSCustomElementInterface to invoke Get(constructor, "prototype") and Get(prototype, callbackName)
     9        for each lifecycle callback as required by the latest specification:
     10        https://html.spec.whatwg.org/#dom-customelementsregistry-define
     11
     12        Also added the support for "observedAttributes" property on the custom elements constructor which defines
     13        the list of attributes for which attributeChangedCallback is invoked.
     14
     15        Test: fast/custom-elements/CustomElementsRegistry.html
     16
     17        * bindings/js/JSCustomElementInterface.cpp:
     18        (WebCore::JSCustomElementInterface::setAttributeChangedCallback): Added.
     19        (WebCore::JSCustomElementInterface::attributeChanged): Invoke m_attributeChangedCallback instead of on the
     20        result of Get(element, "attributeChangedCallback").
     21        * bindings/js/JSCustomElementInterface.h:
     22        (WebCore::JSCustomElementInterface::observesAttribute): Added.
     23
     24        * bindings/js/JSCustomElementsRegistryCustom.cpp:
     25        (WebCore::getLifecycleCallback): Added.
     26        (WebCore::JSCustomElementsRegistry::define): Invoke Get(prototype, callbackName) for each callback. Also
     27        store attributedChangedCallback and observedAttributes to JSCustomElementInterface. Other callbacks will
     28        be stored in the future when the support for those callbacks are added.
     29
     30        * dom/Element.cpp:
     31        (WebCore::Element::attributeChanged): Moved more code into enqueueAttributeChangedCallbackIfNeeded.
     32
     33        * dom/LifecycleCallbackQueue.cpp:
     34        (WebCore::LifecycleCallbackQueue::enqueueAttributeChangedCallbackIfNeeded): Added an early exit for when
     35        the given attribute is not observed by the custom element. Also moved the logic to retrieve
     36        JSCustomElementInterface from Element::attributeChanged and renamed it from enqueueAttributeChangedCallback.
     37
     38        * bindings/js/JSDOMBinding.h:
     39        (WebCore::toNativeArray): Throw a TypeError when the argument is not an object.
     40        * bindings/js/JSDOMConvert.h:
     41        (WebCore::Converter<Vector<T>>::convert): Removed a FIXME comment.
     42
    1432016-08-16  Anders Carlsson  <andersca@apple.com>
    244
  • trunk/Source/WebCore/bindings/js/JSCustomElementInterface.cpp

    r200895 r204540  
    152152}
    153153
     154void JSCustomElementInterface::setAttributeChangedCallback(JSC::JSObject* callback, const Vector<String>& observedAttributes)
     155{
     156    m_attributeChangedCallback = callback;
     157    m_observedAttributes.clear();
     158    for (auto& name : observedAttributes)
     159        m_observedAttributes.add(name);
     160}
     161
    154162void JSCustomElementInterface::attributeChanged(Element& element, const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue)
    155163{
     
    171179    JSObject* jsElement = asObject(toJS(state, globalObject, element));
    172180
    173     PropertyName attributeChanged(Identifier::fromString(state, "attributeChangedCallback"));
    174     JSValue callback = jsElement->get(state, attributeChanged);
    175181    CallData callData;
    176     CallType callType = getCallData(callback, callData);
    177     if (callType == CallType::None)
    178         return;
     182    CallType callType = m_attributeChangedCallback->methodTable()->getCallData(m_attributeChangedCallback.get(), callData);
     183    ASSERT(callType != CallType::None);
    179184
    180185    const AtomicString& namespaceURI = attributeName.namespaceURI();
     
    188193
    189194    NakedPtr<Exception> exception;
    190     JSMainThreadExecState::call(state, callback, callType, callData, jsElement, args, exception);
     195    JSMainThreadExecState::call(state, m_attributeChangedCallback.get(), callType, callData, jsElement, args, exception);
    191196
    192197    InspectorInstrumentation::didCallFunction(cookie, context);
  • trunk/Source/WebCore/bindings/js/JSCustomElementInterface.h

    r197634 r204540  
    3838#include <wtf/RefCounted.h>
    3939#include <wtf/RefPtr.h>
     40#include <wtf/text/AtomicStringHash.h>
    4041
    4142namespace JSC {
     
    6667    void upgradeElement(Element&);
    6768
     69    void setAttributeChangedCallback(JSC::JSObject* callback, const Vector<String>& observedAttributes);
     70    bool observesAttribute(const AtomicString& name) const { return m_observedAttributes.contains(name); }
    6871    void attributeChanged(Element&, const QualifiedName&, const AtomicString& oldValue, const AtomicString& newValue);
    6972
     
    8487    QualifiedName m_name;
    8588    mutable JSC::Weak<JSC::JSObject> m_constructor;
     89    mutable JSC::Weak<JSC::JSObject> m_attributeChangedCallback;
    8690    RefPtr<DOMWrapperWorld> m_isolatedWorld;
    8791    Vector<RefPtr<Element>, 1> m_constructionStack;
     92    HashSet<AtomicString> m_observedAttributes;
    8893};
    8994
  • trunk/Source/WebCore/bindings/js/JSCustomElementsRegistryCustom.cpp

    r204367 r204540  
    3232#include "JSCustomElementInterface.h"
    3333#include "JSDOMBinding.h"
     34#include "JSDOMConvert.h"
    3435
    3536using namespace JSC;
     
    3738namespace WebCore {
    3839
    39    
    4040#if ENABLE(CUSTOM_ELEMENTS)
     41
     42static JSObject* getLifecycleCallback(ExecState& state, JSObject& prototype, const Identifier& id)
     43{
     44    JSValue callback = prototype.get(&state, id);
     45    if (state.hadException())
     46        return nullptr;
     47    if (callback.isUndefined())
     48        return nullptr;
     49    if (!callback.isFunction()) {
     50        throwTypeError(&state, ASCIILiteral("A lifecycle callback must be a function"));
     51        return nullptr;
     52    }
     53    return callback.getObject();
     54}
     55
     56// https://html.spec.whatwg.org/#dom-customelementsregistry-define
    4157JSValue JSCustomElementsRegistry::define(ExecState& state)
    4258{
     
    5470
    5571    // FIXME: Throw a TypeError if constructor doesn't inherit from HTMLElement.
     72    // https://github.com/w3c/webcomponents/issues/541
    5673
    5774    switch (Document::validateCustomElementName(localName)) {
     
    6683    }
    6784
     85    // FIXME: Check re-entrancy here.
     86    // https://github.com/w3c/webcomponents/issues/545
     87
    6888    CustomElementsRegistry& registry = wrapped();
    6989    if (registry.findInterface(localName)) {
     
    7797    }
    7898
    79     // FIXME: 10. Let prototype be Get(constructor, "prototype"). Rethrow any exceptions.
    80     // FIXME: 11. If Type(prototype) is not Object, throw a TypeError exception.
    81     // FIXME: 12. Let attachedCallback be Get(prototype, "attachedCallback"). Rethrow any exceptions.
    82     // FIXME: 13. Let detachedCallback be Get(prototype, "detachedCallback"). Rethrow any exceptions.
    83     // FIXME: 14. Let attributeChangedCallback be Get(prototype, "attributeChangedCallback"). Rethrow any exceptions.
     99    auto& vm = globalObject()->vm();
     100    JSValue prototypeValue = constructor->get(&state, vm.propertyNames->prototype);
     101    if (state.hadException())
     102        return jsUndefined();
     103    if (!prototypeValue.isObject())
     104        return throwTypeError(&state, ASCIILiteral("Custom element constructor's prototype must be an object"));
     105    JSObject& prototypeObject = *asObject(prototypeValue);
     106
     107    // FIXME: Add the support for connectedCallback.
     108    getLifecycleCallback(state, prototypeObject, Identifier::fromString(&vm, "connectedCallback"));
     109    if (state.hadException())
     110        return jsUndefined();
     111
     112    // FIXME: Add the support for disconnectedCallback.
     113    getLifecycleCallback(state, prototypeObject, Identifier::fromString(&vm, "disconnectedCallback"));
     114    if (state.hadException())
     115        return jsUndefined();
     116
     117    // FIXME: Add the support for adoptedCallback.
     118    getLifecycleCallback(state, prototypeObject, Identifier::fromString(&vm, "adoptedCallback"));
     119    if (state.hadException())
     120        return jsUndefined();
     121
     122    QualifiedName name(nullAtom, localName, HTMLNames::xhtmlNamespaceURI);
     123    auto interface = JSCustomElementInterface::create(name, constructor, globalObject());
     124
     125    auto* attributeChangedCallback = getLifecycleCallback(state, prototypeObject, Identifier::fromString(&vm, "attributeChangedCallback"));
     126    if (state.hadException())
     127        return jsUndefined();
     128    if (attributeChangedCallback) {
     129        auto value = convertOptional<Vector<String>>(state, constructor->get(&state, Identifier::fromString(&state, "observedAttributes")));
     130        if (state.hadException())
     131            return jsUndefined();
     132        if (value)
     133            interface->setAttributeChangedCallback(attributeChangedCallback, *value);
     134    }
    84135
    85136    PrivateName uniquePrivateName;
    86     globalObject()->putDirect(globalObject()->vm(), uniquePrivateName, constructor);
     137    globalObject()->putDirect(vm, uniquePrivateName, constructor);
    87138
    88     QualifiedName name(nullAtom, localName, HTMLNames::xhtmlNamespaceURI);
    89     registry.addElementDefinition(JSCustomElementInterface::create(name, constructor, globalObject()));
     139    registry.addElementDefinition(WTFMove(interface));
    90140
    91141    // FIXME: 17. Let map be registry's upgrade candidates map.
    92142    // FIXME: 18. Upgrade a newly-defined element given map and definition.
     143    // FIXME: 19. Resolve whenDefined promise.
    93144
    94145    return jsUndefined();
  • trunk/Source/WebCore/dom/Element.cpp

    r204466 r204540  
    12911291
    12921292#if ENABLE(CUSTOM_ELEMENTS)
    1293     if (UNLIKELY(isCustomElement())) {
    1294         if (auto* window = document().domWindow()) {
    1295             if (auto* registry = window->customElementsRegistry()) {
    1296                 auto* elementInterface = registry->findInterface(tagQName());
    1297                 RELEASE_ASSERT(elementInterface);
    1298                 LifecycleCallbackQueue::enqueueAttributeChangedCallback(*this, *elementInterface, name, oldValue, newValue);
    1299             }
    1300         }
    1301     }
     1293    if (UNLIKELY(isCustomElement()))
     1294        LifecycleCallbackQueue::enqueueAttributeChangedCallbackIfNeeded(*this, name, oldValue, newValue);
    13021295#endif
    13031296
  • trunk/Source/WebCore/dom/LifecycleCallbackQueue.cpp

    r202559 r204540  
    2929#if ENABLE(CUSTOM_ELEMENTS)
    3030
     31#include "CustomElementsRegistry.h"
     32#include "DOMWindow.h"
    3133#include "Document.h"
    3234#include "Element.h"
     
    9799}
    98100
    99 void LifecycleCallbackQueue::enqueueAttributeChangedCallback(Element& element, JSCustomElementInterface& elementInterface,
    100     const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue)
     101void LifecycleCallbackQueue::enqueueAttributeChangedCallbackIfNeeded(Element& element, const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue)
    101102{
     103    ASSERT(element.isCustomElement());
     104    auto* window = element.document().domWindow();
     105    if (!window)
     106        return;
     107
     108    auto* registry = window->customElementsRegistry();
     109    if (!registry)
     110        return;
     111
     112    auto* elementInterface = registry->findInterface(element.tagQName());
     113    if (!elementInterface->observesAttribute(attributeName.localName()))
     114        return;
     115
    102116    if (auto* queue = CustomElementLifecycleProcessingStack::ensureCurrentQueue())
    103         queue->m_items.append(LifecycleQueueItem(element, elementInterface, attributeName, oldValue, newValue));
     117        queue->m_items.append(LifecycleQueueItem(element, *elementInterface, attributeName, oldValue, newValue));
    104118}
    105119
  • trunk/Source/WebCore/dom/LifecycleCallbackQueue.h

    r197634 r204540  
    2424 */
    2525
    26 #ifndef LifecycleCallbackQueue_h
    27 #define LifecycleCallbackQueue_h
     26#pragma once
    2827
    2928#if ENABLE(CUSTOM_ELEMENTS)
     
    4847
    4948    static void enqueueElementUpgrade(Element&, JSCustomElementInterface&);
    50 
    51     static void enqueueAttributeChangedCallback(Element&, JSCustomElementInterface&,
    52         const QualifiedName&, const AtomicString& oldValue, const AtomicString& newValue);
     49    static void enqueueAttributeChangedCallbackIfNeeded(Element&, const QualifiedName&, const AtomicString& oldValue, const AtomicString& newValue);
    5350
    5451    void invokeAll();
     
    9087
    9188#endif
    92 
    93 #endif // LifecycleCallbackQueue_h
Note: See TracChangeset for help on using the changeset viewer.