Changeset 205261 in webkit


Ignore:
Timestamp:
Aug 31, 2016 12:31:11 PM (8 years ago)
Author:
rniwa@webkit.org
Message:

Add the check for reentrancy to CustomElementRegistry
https://bugs.webkit.org/show_bug.cgi?id=161423

Reviewed by Antti Koivisto.

Source/WebCore:

Added the "element definition is running" flag to JSCustomElementRegistry:
https://html.spec.whatwg.org/multipage/scripting.html#element-definition-is-running

And added an exception for when this flag is set during JSCustomElementRegistry::define:
https://html.spec.whatwg.org/multipage/scripting.html#dom-customelementregistry-define

Tests: fast/custom-elements/CustomElementRegistry.html

  • bindings/js/JSCustomElementRegistryCustom.cpp:

(WebCore::JSCustomElementRegistry::define): Throw NotSupportedError when m_elementDefinitionIsRunning is true.

  • dom/CustomElementRegistry.h:

(WebCore::CustomElementRegistry::elementDefinitionIsRunning): Added.

LayoutTests:

Add test cases for reentrancy during customElements.define.

  • fast/custom-elements/CustomElementRegistry-expected.txt:
  • fast/custom-elements/CustomElementRegistry.html:
Location:
trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r205258 r205261  
     12016-08-31  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        Add the check for reentrancy to CustomElementRegistry
     4        https://bugs.webkit.org/show_bug.cgi?id=161423
     5
     6        Reviewed by Antti Koivisto.
     7
     8        Add test cases for reentrancy during customElements.define.
     9
     10        * fast/custom-elements/CustomElementRegistry-expected.txt:
     11        * fast/custom-elements/CustomElementRegistry.html:
     12
    1132016-08-31  Chris Dumez  <cdumez@apple.com>
    214
  • trunk/LayoutTests/fast/custom-elements/CustomElementRegistry-expected.txt

    r205220 r205261  
    11
    22PASS CustomElementRegistry interface must have define as a method
     3PASS customElements.define must throw when the element interface is not a constructor
    34PASS customElements.define must throw with an invalid name
    45PASS customElements.define must throw when there is already a custom element of the same name
    5 PASS customElements.define must throw when there is already a custom element with the same class
    6 PASS customElements.define must throw when the element interface is not a constructor
     6PASS customElements.define must throw a NotSupportedError when there is already a custom element with the same class
     7PASS customElements.define must throw a NotSupportedError when element definition is running flag is set
     8PASS customElements.define must check IsConstructor on the constructor before checking the element definition is running flag
     9PASS customElements.define must validate the custom element name before checking the element definition is running flag
     10PASS customElements.define must not throw when defining another custom element in a different global object during Get(constructor, "prototype")
     11PASS Custom Elements: CustomElementRegistry interface
    712PASS customElements.define must get "prototype" property of the constructor
    813PASS customElements.define must rethrow an exception thrown while getting "prototype" property of the constructor
  • trunk/LayoutTests/fast/custom-elements/CustomElementRegistry.html

    r205220 r205261  
    1919
    2020test(function () {
     21    assert_throws({'name': 'TypeError'}, function () { customElements.define('badname', 1); },
     22        'customElements.define must throw a TypeError when the element interface is a number');
     23    assert_throws({'name': 'TypeError'}, function () { customElements.define('badname', '123'); },
     24        'customElements.define must throw a TypeError when the element interface is a string');
     25    assert_throws({'name': 'TypeError'}, function () { customElements.define('badname', {}); },
     26        'customElements.define must throw a TypeError when the element interface is an object');
     27    assert_throws({'name': 'TypeError'}, function () { customElements.define('badname', []); },
     28        'customElements.define must throw a TypeError when the element interface is an array');
     29}, 'customElements.define must throw when the element interface is not a constructor');
     30
     31test(function () {
    2132    class MyCustomElement extends HTMLElement {};
    2233
     
    5061test(function () {
    5162    class SomeCustomElement extends HTMLElement {};
    52     class OtherCustomElement extends HTMLElement {};
     63
     64    var calls = [];
     65    var OtherCustomElement = new Proxy(class extends HTMLElement {}, {
     66        get: function (target, name) {
     67            calls.push(name);
     68            return target[name];
     69        }
     70    })
    5371
    5472    customElements.define('some-custom-element', SomeCustomElement);
    5573    assert_throws({'name': 'NotSupportedError'}, function () { customElements.define('some-custom-element', OtherCustomElement); },
    5674        'customElements.define must throw a NotSupportedError if the specified tag name is already used');
     75    assert_array_equals(calls, [], 'customElements.define must validate the custom element name before getting the prototype of the constructor');
    5776
    5877}, 'customElements.define must throw when there is already a custom element of the same name');
     
    6584        'customElements.define must throw a NotSupportedError if the specified class already defines an element');
    6685
    67 }, 'customElements.define must throw when there is already a custom element with the same class');
    68 
    69 test(function () {
    70     assert_throws({'name': 'TypeError'}, function () { customElements.define('invalid-element', 1); },
    71         'customElements.define must throw a TypeError when the element interface is a number');
    72     assert_throws({'name': 'TypeError'}, function () { customElements.define('invalid-element', '123'); },
    73         'customElements.define must throw a TypeError when the element interface is a string');
    74     assert_throws({'name': 'TypeError'}, function () { customElements.define('invalid-element', {}); },
    75         'customElements.define must throw a TypeError when the element interface is an object');
    76     assert_throws({'name': 'TypeError'}, function () { customElements.define('invalid-element', []); },
    77         'customElements.define must throw a TypeError when the element interface is an array');
    78 }, 'customElements.define must throw when the element interface is not a constructor');
     86}, 'customElements.define must throw a NotSupportedError when there is already a custom element with the same class');
     87
     88test(function () {
     89    var outerCalls = [];
     90    var OuterCustomElement = new Proxy(class extends HTMLElement { }, {
     91        get: function (target, name) {
     92            outerCalls.push(name);
     93            customElements.define('inner-custom-element', InnerCustomElement);
     94            return target[name];
     95        }
     96    });
     97    var innerCalls = [];
     98    var InnerCustomElement = new Proxy(class extends HTMLElement { }, {
     99        get: function (target, name) {
     100            outerCalls.push(name);
     101            return target[name];
     102        }
     103    });
     104
     105    assert_throws({'name': 'NotSupportedError'}, function () { customElements.define('outer-custom-element', OuterCustomElement); },
     106        'customElements.define must throw a NotSupportedError if the specified class already defines an element');
     107    assert_array_equals(outerCalls, ['prototype'], 'customElements.define must get "prototype"');
     108    assert_array_equals(innerCalls, [],
     109        'customElements.define must throw a NotSupportedError when element definition is running flag is set'
     110        + ' before getting the prototype of the constructor');
     111
     112}, 'customElements.define must throw a NotSupportedError when element definition is running flag is set');
     113
     114test(function () {
     115    var calls = [];
     116    var ElementWithBadInnerConstructor = new Proxy(class extends HTMLElement { }, {
     117        get: function (target, name) {
     118            calls.push(name);
     119            customElements.define('inner-custom-element', 1);
     120            return target[name];
     121        }
     122    });
     123
     124    assert_throws({'name': 'TypeError'}, function () {
     125        customElements.define('element-with-bad-inner-constructor', ElementWithBadInnerConstructor);
     126    }, 'customElements.define must throw a NotSupportedError if IsConstructor(constructor) is false');
     127
     128    assert_array_equals(calls, ['prototype'], 'customElements.define must get "prototype"');
     129}, 'customElements.define must check IsConstructor on the constructor before checking the element definition is running flag');
     130
     131test(function () {
     132    var calls = [];
     133    var ElementWithBadInnerName = new Proxy(class extends HTMLElement { }, {
     134        get: function (target, name) {
     135            calls.push(name);
     136            customElements.define('badname', class extends HTMLElement {});
     137            return target[name];
     138        }
     139    });
     140
     141    assert_throws({'name': 'SyntaxError'}, function () {
     142        customElements.define('element-with-bad-inner-name', ElementWithBadInnerName);
     143    }, 'customElements.define must throw a SyntaxError if the specified name is not a valid custom element name');
     144
     145    assert_array_equals(calls, ['prototype'], 'customElements.define must get "prototype"');
     146}, 'customElements.define must validate the custom element name before checking the element definition is running flag');
     147
     148(function () {
     149    var testCase = async_test('customElements.define must not throw'
     150        +' when defining another custom element in a different global object during Get(constructor, "prototype")', {timeout: 100});
     151
     152    var iframe = document.createElement('iframe');
     153    iframe.onload = function () {
     154        testCase.step(function () {
     155            var InnerCustomElement = class extends iframe.contentWindow.HTMLElement {};
     156            var calls = [];
     157            var proxy = new Proxy(class extends HTMLElement { }, {
     158                get: function (target, name) {
     159                    calls.push(name);
     160                    iframe.contentWindow.customElements.define('another-custom-element', InnerCustomElement);
     161                    return target[name];
     162                }
     163            })
     164            customElements.define('element-with-inner-element-define', proxy);
     165            assert_array_equals(calls, ['prototype'], 'customElements.define must get "prototype"');
     166            assert_true(iframe.contentDocument.createElement('another-custom-element') instanceof InnerCustomElement);
     167        });
     168        document.body.removeChild(iframe);
     169        testCase.done();
     170    }
     171
     172    document.body.appendChild(iframe);
     173})();
     174
     175test(function () {
     176    var calls = [];
     177    var ElementWithBadInnerName = new Proxy(class extends HTMLElement { }, {
     178        get: function (target, name) {
     179            calls.push(name);
     180            customElements.define('badname', class extends HTMLElement {});
     181            return target[name];
     182        }
     183    });
     184
     185    assert_throws({'name': 'SyntaxError'}, function () {
     186        customElements.define('element-with-bad-inner-name', ElementWithBadInnerName);
     187    }, 'customElements.define must throw a SyntaxError if the specified name is not a valid custom element name');
     188
     189    assert_array_equals(calls, ['prototype'], 'customElements.define must get "prototype"');
     190}, '');
    79191
    80192test(function () {
  • trunk/Source/WebCore/ChangeLog

    r205257 r205261  
     12016-08-31  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        Add the check for reentrancy to CustomElementRegistry
     4        https://bugs.webkit.org/show_bug.cgi?id=161423
     5
     6        Reviewed by Antti Koivisto.
     7
     8        Added the "element definition is running" flag to JSCustomElementRegistry:
     9        https://html.spec.whatwg.org/multipage/scripting.html#element-definition-is-running
     10
     11        And added an exception for when this flag is set during JSCustomElementRegistry::define:
     12        https://html.spec.whatwg.org/multipage/scripting.html#dom-customelementregistry-define
     13
     14        Tests: fast/custom-elements/CustomElementRegistry.html
     15
     16        * bindings/js/JSCustomElementRegistryCustom.cpp:
     17        (WebCore::JSCustomElementRegistry::define): Throw NotSupportedError when m_elementDefinitionIsRunning is true.
     18        * dom/CustomElementRegistry.h:
     19        (WebCore::CustomElementRegistry::elementDefinitionIsRunning): Added.
     20
    1212016-08-30  Ryosuke Niwa  <rniwa@webkit.org>
    222
  • trunk/Source/WebCore/bindings/js/JSCustomElementRegistryCustom.cpp

    r205198 r205261  
    9393
    9494    CustomElementRegistry& registry = wrapped();
     95
     96    if (registry.elementDefinitionIsRunning()) {
     97        throwNotSupportedError(state, scope, ASCIILiteral("Cannot define a custom element while defining another custom element"));
     98        return jsUndefined();
     99    }
     100    TemporaryChange<bool> change(registry.elementDefinitionIsRunning(), true);
     101
    95102    if (registry.findInterface(localName)) {
    96103        throwNotSupportedError(state, scope, ASCIILiteral("Cannot define multiple custom elements with the same tag name"));
  • trunk/Source/WebCore/dom/CustomElementRegistry.h

    r205220 r205261  
    3030#include "QualifiedName.h"
    3131#include <wtf/HashMap.h>
     32#include <wtf/TemporaryChange.h>
    3233#include <wtf/text/AtomicString.h>
    3334#include <wtf/text/AtomicStringHash.h>
     
    4243namespace WebCore {
    4344
     45class CustomElementRegistry;
    4446class Element;
    4547class JSCustomElementInterface;
     
    5355    void addElementDefinition(Ref<JSCustomElementInterface>&&);
    5456    void addUpgradeCandidate(Element&);
     57
     58    bool& elementDefinitionIsRunning() { return m_elementDefinitionIsRunning; }
    5559
    5660    JSCustomElementInterface* findInterface(const QualifiedName&) const;
     
    6771    HashMap<AtomicString, Ref<JSCustomElementInterface>> m_nameMap;
    6872    HashMap<const JSC::JSObject*, JSCustomElementInterface*> m_constructorMap;
     73
     74    bool m_elementDefinitionIsRunning { false };
     75
     76    friend class ElementDefinitionIsRunningTemporaryChange;
    6977};
    7078
Note: See TracChangeset for help on using the changeset viewer.