Bug 1415761 - Catch the exception and rethrow it after invoking custom elements reactions; r=bz draft
authorEdgar Chen <echen@mozilla.com>
Fri, 17 Nov 2017 17:44:18 +0800
changeset 701069 107d331203d0d16062fa061569e822d3c6d5f2c9
parent 700338 dd08f8b19cc32da161811abb2f7093e0f5392e69
child 701281 a04198d19d61644287de1e7cffc552a832ea6419
push id90062
push userechen@mozilla.com
push dateTue, 21 Nov 2017 07:31:51 +0000
reviewersbz
bugs1415761
milestone59.0a1
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
dom/base/CustomElementRegistry.h
dom/base/nsDocument.cpp
dom/bindings/Codegen.py
parser/html/nsHtml5TreeOperation.cpp
testing/web-platform/meta/MANIFEST.json
testing/web-platform/tests/custom-elements/reactions/with-exceptions.html
--- 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>