Bug 1415761 - Catch the exception and rethrow it after invoking custom elements reactions; r=bz
The spec was unclear on how CEReactions interact with thrown exceptions; see https://github.com/whatwg/html/issues/3217.
The spec is now being clarified in https://github.com/whatwg/html/pull/3235.
MozReview-Commit-ID: 521HprTRS7k
--- a/dom/base/CustomElementRegistry.h
+++ b/dom/base/CustomElementRegistry.h
@@ -475,24 +475,33 @@ public:
void Get(JSContext* cx, const nsAString& name,
JS::MutableHandle<JS::Value> aRetVal);
already_AddRefed<Promise> WhenDefined(const nsAString& aName, ErrorResult& aRv);
};
class MOZ_RAII AutoCEReaction final {
public:
- explicit AutoCEReaction(CustomElementReactionsStack* aReactionsStack)
- : mReactionsStack(aReactionsStack) {
+ // JSContext is allowed to be a nullptr if we are guaranteeing that we're
+ // not doing something that might throw but not finish reporting a JS
+ // exception during the lifetime of the AutoCEReaction.
+ AutoCEReaction(CustomElementReactionsStack* aReactionsStack, JSContext* aCx)
+ : mReactionsStack(aReactionsStack)
+ , mCx(aCx) {
mReactionsStack->CreateAndPushElementQueue();
}
~AutoCEReaction() {
+ Maybe<JS::AutoSaveExceptionState> ases;
+ if (mCx) {
+ ases.emplace(mCx);
+ }
mReactionsStack->PopAndInvokeElementQueue();
}
private:
RefPtr<CustomElementReactionsStack> mReactionsStack;
+ JSContext* mCx;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_CustomElementRegistry_h
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -6487,17 +6487,18 @@ nsDocument::RegisterElement(JSContext* a
ErrorResult& rv)
{
RefPtr<CustomElementRegistry> registry(GetCustomElementRegistry());
if (!registry) {
rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return;
}
- AutoCEReaction ceReaction(this->GetDocGroup()->CustomElementReactionsStack());
+ AutoCEReaction ceReaction(this->GetDocGroup()->CustomElementReactionsStack(),
+ aCx);
// Unconditionally convert TYPE to lowercase.
nsAutoString lcType;
nsContentUtils::ASCIIToLower(aType, lcType);
nsIGlobalObject* sgo = GetScopeObject();
if (!sgo) {
rv.Throw(NS_ERROR_UNEXPECTED);
return;
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -7871,17 +7871,17 @@ class CGPerSignatureCall(CGThing):
if (idlNode.getExtendedAttribute('CEReactions') is not None and
not getter):
cgThings.append(CGGeneric(fill(
"""
Maybe<AutoCEReaction> ceReaction;
if (CustomElementRegistry::IsCustomElementEnabled()) {
CustomElementReactionsStack* reactionsStack = GetCustomElementReactionsStack(${obj});
if (reactionsStack) {
- ceReaction.emplace(reactionsStack);
+ ceReaction.emplace(reactionsStack, cx);
}
}
""", obj=objectName)))
# If this is a method that was generated by a maplike/setlike
# interface, use the maplike/setlike generator to fill in the body.
# Otherwise, use CGCallGenerator to call the native method.
if idlNode.isMethod() and idlNode.isMaplikeOrSetlikeOrIterableMethod():
--- a/parser/html/nsHtml5TreeOperation.cpp
+++ b/parser/html/nsHtml5TreeOperation.cpp
@@ -423,17 +423,18 @@ nsHtml5TreeOperation::CreateHTMLElement(
if (willExecuteScript) { // This will cause custom element constructors to run
AutoSetThrowOnDynamicMarkupInsertionCounter
throwOnDynamicMarkupInsertionCounter(document);
nsHtml5AutoPauseUpdate autoPauseContentUpdate(aBuilder);
{
nsAutoMicroTask mt;
}
dom::AutoCEReaction
- autoCEReaction(document->GetDocGroup()->CustomElementReactionsStack());
+ autoCEReaction(document->GetDocGroup()->CustomElementReactionsStack(),
+ nullptr);
nsCOMPtr<dom::Element> newElement;
NS_NewHTMLElement(getter_AddRefs(newElement), nodeInfo.forget(),
aFromParser, (isValue.IsEmpty() ? nullptr : &isValue),
definition);
MOZ_ASSERT(newElement, "Element creation created null pointer.");
newContent = newElement;
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -312863,16 +312863,22 @@
]
],
"custom-elements/reactions/ShadowRoot.html": [
[
"/custom-elements/reactions/ShadowRoot.html",
{}
]
],
+ "custom-elements/reactions/with-exceptions.html": [
+ [
+ "/custom-elements/reactions/with-exceptions.html",
+ {}
+ ]
+ ],
"custom-elements/upgrading.html": [
[
"/custom-elements/upgrading.html",
{}
]
],
"custom-elements/upgrading/Node-cloneNode.html": [
[
@@ -533818,16 +533824,20 @@
"custom-elements/reactions/ShadowRoot.html": [
"4db42614132b8510d8ac41745bcd51da5c35ef6a",
"testharness"
],
"custom-elements/reactions/resources/reactions.js": [
"e966b6c608ee9b4183e040b8be7adb2b73722c7b",
"support"
],
+ "custom-elements/reactions/with-exceptions.html": [
+ "b72ee82a3d98c61683e62c4834f54269898e12d5",
+ "testharness"
+ ],
"custom-elements/resources/custom-elements-helpers.js": [
"accaee861d319e0caf00356521479dc5951ac162",
"support"
],
"custom-elements/resources/empty-html-document.html": [
"c436e35464c8c32f3afb191c46ef599b4986a395",
"support"
],
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/custom-elements/reactions/with-exceptions.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Custom Elements: CEReactions interaction with exceptions</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<meta name="help" content="https://html.spec.whatwg.org/multipage/#cereactions">
+<meta name="help" content="https://github.com/whatwg/html/pull/3235">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+
+<div id="log"></div>
+
+<script>
+"use strict";
+// Basically from https://github.com/whatwg/html/issues/3217#issuecomment-343633273
+test_with_window((contentWindow, contentDocument) => {
+ let reactionRan = false;
+ contentWindow.customElements.define("custom-element", class extends contentWindow.HTMLElement {
+ disconnectedCallback() {
+ reactionRan = true;
+ }
+ });
+ const text = contentDocument.createTextNode("");
+ contentDocument.documentElement.appendChild(text);
+ const element = contentDocument.createElement("custom-element");
+ contentDocument.documentElement.appendChild(element);
+ assert_throws("HierarchyRequestError", () => text.before("", contentDocument.documentElement));
+ assert_true(reactionRan);
+}, "Reaction must run even after the exception is thrown");
+</script>