Skip to content

Commit

Permalink
part 2. Combine HTMLConstructor and CreateXULOrHTMLElement into a sin…
Browse files Browse the repository at this point in the history
…gle function. (#10311)

This fixes an observable bug we had due to doing the steps in a different order
from the spec: the 'prototype' get can have side-effects so needs to happen
after some of the other sanity checks.
bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=1446246
gecko-commit: 9ed0718ede32ea01c67a47b8f143152e860a8be2
gecko-integration-branch: central
gecko-reviewers: peterv
  • Loading branch information
moz-wptsync-bot authored and jgraham committed Apr 4, 2018
1 parent fc53891 commit 9a3fdb4
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 1 deletion.
153 changes: 153 additions & 0 deletions custom-elements/HTMLElement-constructor.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,159 @@

}, 'HTMLElement constructor must allow subclassing an user-defined subclass of HTMLElement');

test(function() {
class SomeCustomElement extends HTMLElement {};
var getCount = 0;
var countingProxy = new Proxy(SomeCustomElement, {
get: function(target, prop, receiver) {
if (prop == "prototype") {
++getCount;
}
return Reflect.get(target, prop, receiver);
}
});
customElements.define("success-counting-element-1", countingProxy);
// define() gets the prototype of the constructor it's passed, so
// reset the counter.
getCount = 0;
var instance = new countingProxy();
assert_equals(getCount, 1, "Should have gotten .prototype once");
assert_true(instance instanceof countingProxy);
assert_true(instance instanceof HTMLElement);
assert_true(instance instanceof SomeCustomElement);
assert_equals(instance.localName, "success-counting-element-1");
assert_equals(instance.nodeName, "SUCCESS-COUNTING-ELEMENT-1");
}, 'HTMLElement constructor must only get .prototype once, calling proxy constructor directly');

test(function() {
class SomeCustomElement extends HTMLElement {};
var getCount = 0;
var countingProxy = new Proxy(SomeCustomElement, {
get: function(target, prop, receiver) {
if (prop == "prototype") {
++getCount;
}
return Reflect.get(target, prop, receiver);
}
});
customElements.define("success-counting-element-2", countingProxy);
// define() gets the prototype of the constructor it's passed, so
// reset the counter.
getCount = 0;
var instance = Reflect.construct(HTMLElement, [], countingProxy);
assert_equals(getCount, 1, "Should have gotten .prototype once");
assert_true(instance instanceof countingProxy);
assert_true(instance instanceof HTMLElement);
assert_true(instance instanceof SomeCustomElement);
assert_equals(instance.localName, "success-counting-element-2");
assert_equals(instance.nodeName, "SUCCESS-COUNTING-ELEMENT-2");
}, 'HTMLElement constructor must only get .prototype once, calling proxy constructor via Reflect');

test(function() {
class SomeCustomElement {};
var getCount = 0;
var countingProxy = new Proxy(SomeCustomElement, {
get: function(target, prop, receiver) {
if (prop == "prototype") {
++getCount;
}
return Reflect.get(target, prop, receiver);
}
});
customElements.define("success-counting-element-3", countingProxy);
// define() gets the prototype of the constructor it's passed, so
// reset the counter.
getCount = 0;
var instance = Reflect.construct(HTMLElement, [], countingProxy);
assert_equals(getCount, 1, "Should have gotten .prototype once");
assert_true(instance instanceof countingProxy);
assert_true(instance instanceof SomeCustomElement);
assert_equals(instance.localName, undefined);
assert_equals(instance.nodeName, undefined);
}, 'HTMLElement constructor must only get .prototype once, calling proxy constructor via Reflect with no inheritance');

test(function() {
class SomeCustomElement extends HTMLElement {};
var getCount = 0;
var countingProxy = new Proxy(SomeCustomElement, {
get: function(target, prop, receiver) {
if (prop == "prototype") {
++getCount;
}
return Reflect.get(target, prop, receiver);
}
});
customElements.define("failure-counting-element-1", countingProxy,
{ extends: "button" });
// define() gets the prototype of the constructor it's passed, so
// reset the counter.
getCount = 0;
assert_throws({'name': 'TypeError'},
function () { new countingProxy() },
"Should not be able to construct an HTMLElement named 'button'");
assert_equals(getCount, 0, "Should never have gotten .prototype");
}, 'HTMLElement constructor must not get .prototype until it finishes its extends sanity checks, calling proxy constructor directly');

test(function() {
class SomeCustomElement extends HTMLElement {};
var getCount = 0;
var countingProxy = new Proxy(SomeCustomElement, {
get: function(target, prop, receiver) {
if (prop == "prototype") {
++getCount;
}
return Reflect.get(target, prop, receiver);
}
});
customElements.define("failure-counting-element-2", countingProxy,
{ extends: "button" });
// define() gets the prototype of the constructor it's passed, so
// reset the counter.
getCount = 0;
assert_throws({'name': 'TypeError'},
function () { Reflect.construct(HTMLElement, [], countingProxy) },
"Should not be able to construct an HTMLElement named 'button'");
assert_equals(getCount, 0, "Should never have gotten .prototype");
}, 'HTMLElement constructor must not get .prototype until it finishes its extends sanity checks, calling via Reflect');

test(function() {
class SomeCustomElement extends HTMLElement {};
var getCount = 0;
var countingProxy = new Proxy(SomeCustomElement, {
get: function(target, prop, receiver) {
if (prop == "prototype") {
++getCount;
}
return Reflect.get(target, prop, receiver);
}
});

// Purposefully don't register it.
assert_throws({'name': 'TypeError'},
function () { new countingProxy() },
"Should not be able to construct an HTMLElement named 'button'");
assert_equals(getCount, 0, "Should never have gotten .prototype");
}, 'HTMLElement constructor must not get .prototype until it finishes its registration sanity checks, calling proxy constructor directly');

test(function() {
class SomeCustomElement extends HTMLElement {};
var getCount = 0;
var countingProxy = new Proxy(SomeCustomElement, {
get: function(target, prop, receiver) {
if (prop == "prototype") {
++getCount;
}
return Reflect.get(target, prop, receiver);
}
});

// Purposefully don't register it.
assert_throws({'name': 'TypeError'},
function () { Reflect.construct(HTMLElement, [], countingProxy) },
"Should not be able to construct an HTMLElement named 'button'");
assert_equals(getCount, 0, "Should never have gotten .prototype");
}, 'HTMLElement constructor must not get .prototype until it finishes its registration sanity checks, calling via Reflect');
</script>
</body>
</html>

43 changes: 42 additions & 1 deletion custom-elements/htmlconstructor/newtarget.html
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,46 @@
}, "If prototype is not object (" + notAnObject + "), derives the fallback from NewTarget's realm (customized built-in elements)");
});

test_with_window(w => {
class SomeCustomElement extends HTMLParagraphElement {};
var getCount = 0;
var countingProxy = new Proxy(SomeCustomElement, {
get: function(target, prop, receiver) {
if (prop == "prototype") {
++getCount;
}
return Reflect.get(target, prop, receiver);
}
});
w.customElements.define("failure-counting-element", countingProxy);
// define() gets the prototype of the constructor it's passed, so
// reset the counter.
getCount = 0;
assert_throws({'name': 'TypeError'},
function () { new countingProxy() },
"Should not be able to construct an HTMLParagraphElement not named 'p'");
assert_equals(getCount, 0, "Should never have gotten .prototype");
}, 'HTMLParagraphElement constructor must not get .prototype until it finishes its extends sanity checks, calling proxy constructor directly');

test_with_window(w => {
class SomeCustomElement extends HTMLParagraphElement {};
var getCount = 0;
var countingProxy = new Proxy(SomeCustomElement, {
get: function(target, prop, receiver) {
if (prop == "prototype") {
++getCount;
}
return Reflect.get(target, prop, receiver);
}
});
w.customElements.define("failure-counting-element", countingProxy);
// define() gets the prototype of the constructor it's passed, so
// reset the counter.
getCount = 0;
assert_throws({'name': 'TypeError'},
function () { Reflect.construct(HTMLParagraphElement, [], countingProxy) },
"Should not be able to construct an HTMLParagraphElement not named 'p'");
assert_equals(getCount, 0, "Should never have gotten .prototype");
}, 'HTMLParagraphElement constructor must not get .prototype until it finishes its extends sanity checks, calling via Reflect');
</script>
</body>
</body>

0 comments on commit 9a3fdb4

Please sign in to comment.