Bug 1087460 - Part 2: Call attached and detached callback when attached and detached to/from the composed document. r=smaug
authorWilliam Chen <wchen@mozilla.com>
Mon, 09 Feb 2015 10:01:24 -0800
changeset 255690 7a7e30dbc96cf5e3384f72a69e93e5a705ae4440
parent 255689 09b002eaa381c992fa255074d482286a7a935681
child 255691 72823d7a525031819b81a3ce012bbdad8eb75e2d
push id4610
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:32:55 +0000
treeherdermozilla-beta@4df54044d9ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1087460
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1087460 - Part 2: Call attached and detached callback when attached and detached to/from the composed document. r=smaug
dom/base/Element.cpp
dom/base/nsDocument.cpp
dom/tests/mochitest/webcomponents/mochitest.ini
dom/tests/mochitest/webcomponents/test_custom_element_in_shadow.html
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -1468,23 +1468,16 @@ Element::BindToTree(nsIDocument* aDocume
 
     // We no longer need to track the subtree pointer (and in fact we'll assert
     // if we do this any later).
     ClearSubtreeRootPointer();
 
     // Being added to a document.
     SetInDocument();
 
-    // Attached callback must be enqueued whenever custom element is inserted into a
-    // document and this document has a browsing context.
-    if (GetCustomElementData() && aDocument->GetDocShell()) {
-      // Enqueue an attached callback for the custom element.
-      aDocument->EnqueueLifecycleCallback(nsIDocument::eAttached, this);
-    }
-
     // Unset this flag since we now really are in a document.
     UnsetFlags(NODE_FORCE_XBL_BINDINGS |
                // And clear the lazy frame construction bits.
                NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES |
                // And the restyle bits
                ELEMENT_ALL_RESTYLE_FLAGS);
   } else if (IsInShadowTree()) {
     // We're not in a document, but we did get inserted into a shadow tree.
@@ -1497,16 +1490,26 @@ Element::BindToTree(nsIDocument* aDocume
                NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES |
                ELEMENT_ALL_RESTYLE_FLAGS);
   } else {
     // If we're not in the doc and not in a shadow tree,
     // update our subtree pointer.
     SetSubtreeRootPointer(aParent->SubtreeRoot());
   }
 
+  nsIDocument* composedDoc = GetComposedDoc();
+  if (composedDoc) {
+    // Attached callback must be enqueued whenever custom element is inserted into a
+    // document and this document has a browsing context.
+    if (GetCustomElementData() && composedDoc->GetDocShell()) {
+      // Enqueue an attached callback for the custom element.
+      composedDoc->EnqueueLifecycleCallback(nsIDocument::eAttached, this);
+    }
+  }
+
   // Propagate scoped style sheet tracking bit.
   if (mParent->IsContent()) {
     nsIContent* parent;
     ShadowRoot* shadowRootParent = ShadowRoot::FromNode(mParent);
     if (shadowRootParent) {
       parent = shadowRootParent->GetHost();
     } else {
       parent = mParent->AsContent();
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -453,17 +453,17 @@ CustomElementCallback::Call()
 
       // The callback hasn't actually been invoked yet, but we need to flip
       // this now in order to enqueue the attached callback. This is a spec
       // bug (w3c bug 27437).
       mOwnerData->mCreatedCallbackInvoked = true;
 
       // If ELEMENT is in a document and this document has a browsing context,
       // enqueue attached callback for ELEMENT.
-      nsIDocument* document = mThisObject->GetUncomposedDoc();
+      nsIDocument* document = mThisObject->GetComposedDoc();
       if (document && document->GetDocShell()) {
         document->EnqueueLifecycleCallback(nsIDocument::eAttached, mThisObject);
       }
 
       static_cast<LifecycleCreatedCallback *>(mCallback.get())->Call(mThisObject, rv);
       mOwnerData->mElementIsBeingCreated = false;
       break;
     }
--- a/dom/tests/mochitest/webcomponents/mochitest.ini
+++ b/dom/tests/mochitest/webcomponents/mochitest.ini
@@ -5,16 +5,17 @@ support-files =
 [test_bug900724.html]
 [test_bug1017896.html]
 [test_content_element.html]
 [test_custom_element_adopt_callbacks.html]
 [test_custom_element_callback_innerhtml.html]
 [test_custom_element_clone_callbacks.html]
 [test_custom_element_clone_callbacks_extended.html]
 [test_custom_element_import_node_created_callback.html]
+[test_custom_element_in_shadow.html]
 [test_nested_content_element.html]
 [test_dest_insertion_points.html]
 [test_dest_insertion_points_shadow.html]
 [test_fallback_dest_insertion_points.html]
 [test_detached_style.html]
 [test_dynamic_content_element_matching.html]
 [test_document_register.html]
 [test_document_register_base_queue.html]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_in_shadow.html
@@ -0,0 +1,132 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1087460
+-->
+<head>
+  <title>Test for custom element callbacks in shadow DOM.</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1087460">Bug 1087460</a>
+<div id="container"></div>
+
+<script>
+
+// Test callback for custom element when used after registration.
+
+var createdCallbackCount = 0;
+var attachedCallbackCount = 0;
+var detachedCallbackCount = 0;
+var attributeChangedCallbackCount = 0;
+
+var p1 = Object.create(HTMLElement.prototype);
+
+p1.createdCallback = function() {
+  createdCallbackCount++;
+};
+
+p1.attachedCallback = function() {
+  attachedCallbackCount++;
+};
+
+p1.detachedCallback = function() {
+  detachedCallbackCount++;
+};
+
+p1.attributeChangedCallback = function(name, oldValue, newValue) {
+  attributeChangedCallbackCount++;
+};
+
+document.registerElement("x-foo", { prototype: p1 });
+
+var container = document.getElementById("container");
+var shadow = container.createShadowRoot();
+
+is(createdCallbackCount, 0, "createdCallback should not be called more than once in this test.");
+var customElem = document.createElement("x-foo");
+is(createdCallbackCount, 1, "createdCallback should be called after creating custom element.");
+
+is(attributeChangedCallbackCount, 0, "attributeChangedCallback should not be called after just creating an element.");
+customElem.setAttribute("data-foo", "bar");
+is(attributeChangedCallbackCount, 1, "attributeChangedCallback should be called after setting an attribute.");
+
+is(attachedCallbackCount, 0, "attachedCallback should not be called on an element that is not in a document/composed document.");
+shadow.appendChild(customElem);
+is(attachedCallbackCount, 1, "attachedCallback should be called after attaching custom element to the composed document.");
+
+is(detachedCallbackCount, 0, "detachedCallback should not be called without detaching custom element.");
+shadow.removeChild(customElem);
+is(detachedCallbackCount, 1, "detachedCallback should be called after detaching custom element from the composed document.");
+
+// Test callback for custom element already in the composed doc when created.
+
+createdCallbackCount = 0;
+attachedCallbackCount = 0;
+detachedCallbackCount = 0;
+attributeChangedCallbackCount = 0;
+
+shadow.innerHTML = "<x-foo></x-foo>";
+is(createdCallbackCount, 1, "createdCallback should be called after creating a custom element.");
+is(attachedCallbackCount, 1, "attachedCallback should be called after creating an element in the composed document.");
+
+shadow.innerHTML = "";
+is(detachedCallbackCount, 1, "detachedCallback should be called after detaching custom element from the composed document.");
+
+// Test callback for custom element in shadow DOM when host attached/detached to/from document.
+
+createdCallbackCount = 0;
+attachedCallbackCount = 0;
+detachedCallbackCount = 0;
+attributeChangedCallbackCount = 0;
+
+var host = document.createElement("div");
+shadow = host.createShadowRoot();
+customElem = document.createElement("x-foo");
+
+is(attachedCallbackCount, 0, "attachedCallback should not be called on newly created element.");
+shadow.appendChild(customElem);
+is(attachedCallbackCount, 0, "attachedCallback should not be called on attaching to a tree that is not in the composed document.");
+
+is(attachedCallbackCount, 0, "detachedCallback should not be called.");
+shadow.removeChild(customElem);
+is(detachedCallbackCount, 0, "detachedCallback should not be called when detaching from a tree that is not in the composed document.");
+
+shadow.appendChild(customElem);
+is(attachedCallbackCount, 0, "attachedCallback should still not be called after reattaching to a shadow tree that is not in the composed document.");
+
+container.appendChild(host);
+is(attachedCallbackCount, 1, "attachedCallback should be called after host is inserted into document.");
+
+container.removeChild(host);
+is(detachedCallbackCount, 1, "detachedCallback should be called after host is removed from document.");
+
+// Test callback for custom element for upgraded element.
+
+createdCallbackCount = 0;
+attachedCallbackCount = 0;
+detachedCallbackCount = 0;
+attributeChangedCallbackCount = 0;
+
+shadow = container.shadowRoot;
+shadow.innerHTML = "<x-bar></x-bar>";
+
+var p2 = Object.create(HTMLElement.prototype);
+
+p2.createdCallback = function() {
+  createdCallbackCount++;
+};
+
+p2.attachedCallback = function() {
+  attachedCallbackCount++;
+};
+
+document.registerElement("x-bar", { prototype: p2 });
+is(createdCallbackCount, 1, "createdCallback should be called after registering element.");
+is(attachedCallbackCount, 1, "attachedCallback should be called after upgrading element in composed document.");
+
+</script>
+
+</body>
+</html>