Bug 1081039 - cloneNode on a custom element should call createdCallback if cloned in a document with a custom element definition. r=smaug

This commit is contained in:
William Chen 2014-12-22 18:19:08 -08:00
parent 18d9cfef3c
commit 27d4f06056
11 changed files with 202 additions and 56 deletions

View File

@ -5424,23 +5424,30 @@ nsIDocument::CreateElement(const nsAString& aTagName, ErrorResult& rv)
} }
void void
nsDocument::SwizzleCustomElement(Element* aElement, nsDocument::SetupCustomElement(Element* aElement,
const nsAString& aTypeExtension, uint32_t aNamespaceID,
uint32_t aNamespaceID, const nsAString* aTypeExtension)
ErrorResult& rv)
{ {
nsCOMPtr<nsIAtom> typeAtom(do_GetAtom(aTypeExtension)); if (!mRegistry) {
nsCOMPtr<nsIAtom> tagAtom = aElement->Tag();
if (!mRegistry || tagAtom == typeAtom) {
return; return;
} }
nsCOMPtr<nsIAtom> tagAtom = aElement->Tag();
nsCOMPtr<nsIAtom> typeAtom = aTypeExtension ?
do_GetAtom(*aTypeExtension) : tagAtom;
if (aTypeExtension && !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::is)) {
// Custom element setup in the parser happens after the "is"
// attribute is added.
aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::is, *aTypeExtension, true);
}
CustomElementDefinition* data; CustomElementDefinition* data;
CustomElementHashKey key(aNamespaceID, typeAtom); CustomElementHashKey key(aNamespaceID, typeAtom);
if (!mRegistry->mCustomDefinitions.Get(&key, &data)) { if (!mRegistry->mCustomDefinitions.Get(&key, &data)) {
// The type extension doesn't exist in the registry, // The type extension doesn't exist in the registry,
// thus we don't need to swizzle, but it is possibly // thus we don't need to enqueue callback or adjust
// an upgrade candidate. // the "is" attribute, but it is possibly an upgrade candidate.
RegisterUnresolvedElement(aElement, typeAtom); RegisterUnresolvedElement(aElement, typeAtom);
return; return;
} }
@ -5452,11 +5459,6 @@ nsDocument::SwizzleCustomElement(Element* aElement,
return; return;
} }
if (!aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::is)) {
// Swizzling in the parser happens after the "is" attribute is added.
aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::is, aTypeExtension, true);
}
// Enqueuing the created callback will set the CustomElementData on the // Enqueuing the created callback will set the CustomElementData on the
// element, causing prototype swizzling to occur in Element::WrapObject. // element, causing prototype swizzling to occur in Element::WrapObject.
EnqueueLifecycleCallback(nsIDocument::eCreated, aElement, nullptr, data); EnqueueLifecycleCallback(nsIDocument::eCreated, aElement, nullptr, data);
@ -5472,10 +5474,9 @@ nsDocument::CreateElement(const nsAString& aTagName,
return nullptr; return nullptr;
} }
SwizzleCustomElement(elem, aTypeExtension, if (!aTagName.Equals(aTypeExtension)) {
GetDefaultNamespaceID(), rv); // Custom element type can not extend itself.
if (rv.Failed()) { SetupCustomElement(elem, GetDefaultNamespaceID(), &aTypeExtension);
return nullptr;
} }
return elem.forget(); return elem.forget();
@ -5540,9 +5541,9 @@ nsDocument::CreateElementNS(const nsAString& aNamespaceURI,
} }
} }
SwizzleCustomElement(elem, aTypeExtension, nameSpaceId, rv); if (!aQualifiedName.Equals(aTypeExtension)) {
if (rv.Failed()) { // A custom element type can not extend itself.
return nullptr; SetupCustomElement(elem, nameSpaceId, &aTypeExtension);
} }
return elem.forget(); return elem.forget();
@ -5769,12 +5770,12 @@ nsDocument::CustomElementConstructor(JSContext* aCx, unsigned aArgc, JS::Value*
getter_AddRefs(newElement)); getter_AddRefs(newElement));
NS_ENSURE_SUCCESS(rv, true); NS_ENSURE_SUCCESS(rv, true);
ErrorResult errorResult;
nsCOMPtr<Element> element = do_QueryInterface(newElement); nsCOMPtr<Element> element = do_QueryInterface(newElement);
document->SwizzleCustomElement(element, elemName, definition->mNamespaceID, if (definition->mLocalName != typeAtom) {
errorResult); // This element is a custom element by extension, thus we need to
if (errorResult.Failed()) { // do some special setup. For non-extended custom elements, this happens
return true; // when the element is created.
document->SetupCustomElement(element, definition->mNamespaceID, &elemName);
} }
rv = nsContentUtils::WrapNative(aCx, newElement, newElement, args.rval()); rv = nsContentUtils::WrapNative(aCx, newElement, newElement, args.rval());
@ -6095,7 +6096,7 @@ nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType,
} }
JS::Rooted<JSObject*> global(aCx, sgo->GetGlobalJSObject()); JS::Rooted<JSObject*> global(aCx, sgo->GetGlobalJSObject());
nsCOMPtr<nsIAtom> nameAtom;; nsCOMPtr<nsIAtom> nameAtom;
int32_t namespaceID = kNameSpaceID_XHTML; int32_t namespaceID = kNameSpaceID_XHTML;
JS::Rooted<JSObject*> protoObject(aCx); JS::Rooted<JSObject*> protoObject(aCx);
{ {

View File

@ -1557,11 +1557,12 @@ private:
public: public:
static void ProcessBaseElementQueue(); static void ProcessBaseElementQueue();
// Modify the prototype and "is" attribute of newly created custom elements. // Enqueue created callback or register upgrade candidate for
virtual void SwizzleCustomElement(Element* aElement, // newly created custom elements, possibly extending an existing type.
const nsAString& aTypeExtension, // ex. <x-button>, <button is="x-button> (type extension)
uint32_t aNamespaceID, virtual void SetupCustomElement(Element* aElement,
mozilla::ErrorResult& rv); uint32_t aNamespaceID,
const nsAString* aTypeExtension);
static bool IsWebComponentsEnabled(JSContext* aCx, JSObject* aObject); static bool IsWebComponentsEnabled(JSContext* aCx, JSObject* aObject);

View File

@ -2237,10 +2237,9 @@ public:
Element* aCustomElement, Element* aCustomElement,
mozilla::dom::LifecycleCallbackArgs* aArgs = nullptr, mozilla::dom::LifecycleCallbackArgs* aArgs = nullptr,
mozilla::dom::CustomElementDefinition* aDefinition = nullptr) = 0; mozilla::dom::CustomElementDefinition* aDefinition = nullptr) = 0;
virtual void SwizzleCustomElement(Element* aElement, virtual void SetupCustomElement(Element* aElement,
const nsAString& aTypeExtension, uint32_t aNamespaceID,
uint32_t aNamespaceID, const nsAString* aTypeExtension = nullptr) = 0;
mozilla::ErrorResult& rv) = 0;
virtual void virtual void
RegisterElement(JSContext* aCx, const nsAString& aName, RegisterElement(JSContext* aCx, const nsAString& aName,
const mozilla::dom::ElementRegistrationOptions& aOptions, const mozilla::dom::ElementRegistrationOptions& aOptions,

View File

@ -363,6 +363,24 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep,
rv = aNode->Clone(nodeInfo, getter_AddRefs(clone)); rv = aNode->Clone(nodeInfo, getter_AddRefs(clone));
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
if (clone->IsElement()) {
// The cloned node may be a custom element that may require
// enqueing created callback and prototype swizzling.
Element* elem = clone->AsElement();
if (nsContentUtils::IsCustomElementName(nodeInfo->NameAtom())) {
elem->OwnerDoc()->SetupCustomElement(elem, nodeInfo->NamespaceID());
} else {
// Check if node may be custom element by type extension.
// ex. <button is="x-button">
nsAutoString extension;
if (elem->GetAttr(kNameSpaceID_None, nsGkAtoms::is, extension) &&
!extension.IsEmpty()) {
elem->OwnerDoc()->SetupCustomElement(elem, nodeInfo->NamespaceID(),
&extension);
}
}
}
if (aParent) { if (aParent) {
// If we're cloning we need to insert the cloned children into the cloned // If we're cloning we need to insert the cloned children into the cloned
// parent. // parent.

View File

@ -266,13 +266,7 @@ NS_NewHTMLElement(Element** aResult, already_AddRefed<mozilla::dom::NodeInfo>&&
return NS_ERROR_OUT_OF_MEMORY; return NS_ERROR_OUT_OF_MEMORY;
} }
// Element may be unresolved at this point. doc->SetupCustomElement(*aResult, kNameSpaceID_XHTML);
doc->RegisterUnresolvedElement(*aResult);
// Try to enqueue a created callback. The custom element data will be set
// and created callback will be enqueued if the custom element type
// has already been registered.
doc->EnqueueLifecycleCallback(nsIDocument::eCreated, *aResult);
return NS_OK; return NS_OK;
} }

View File

@ -5,6 +5,9 @@ support-files =
[test_bug900724.html] [test_bug900724.html]
[test_bug1017896.html] [test_bug1017896.html]
[test_content_element.html] [test_content_element.html]
[test_custom_element_adopt_callbacks.html]
[test_custom_element_clone_callbacks.html]
[test_custom_element_clone_callbacks_extended.html]
[test_nested_content_element.html] [test_nested_content_element.html]
[test_dest_insertion_points.html] [test_dest_insertion_points.html]
[test_dest_insertion_points_shadow.html] [test_dest_insertion_points_shadow.html]

View File

@ -0,0 +1,29 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1081039
-->
<head>
<title>Test callbacks for adopted custom elements.</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<template id="template"><x-foo></x-foo></template>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1081039">Bug 1081039</a>
<script>
var p = Object.create(HTMLElement.prototype);
p.createdCallback = function() {
ok(false, "Created callback should not be called for adopted node.");
};
document.registerElement("x-foo", { prototype: p });
var template = document.getElementById("template");
var adoptedFoo = document.adoptNode(template.content.firstChild);
is(adoptedFoo.nodeName, "X-FOO");
</script>
</body>
</html>

View File

@ -0,0 +1,54 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1081039
-->
<head>
<title>Test callbacks for cloned custom elements.</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=1081039">Bug 1081039</a>
<script>
SimpleTest.waitForExplicitFinish();
// Test to make sure created callback is called on clones that are upgraded and clones
// created after registering the custom element.
var callbackCalledOnUpgrade = false;
var callbackCalledOnClone = false;
var foo = document.createElement("x-foo");
var fooClone = foo.cloneNode(true);
var p = Object.create(HTMLElement.prototype);
p.createdCallback = function() {
is(this.__proto__, p, "Correct prototype should be set on custom elements.");
if (this == fooClone) {
// Callback called for the element created before registering the custom element.
// Should be called on element upgrade.
is(callbackCalledOnUpgrade, false, "Upgrade should only be called once per clone.");
callbackCalledOnUpgrade = true;
} else if (this != foo) {
// Callback called for the element created after registering the custom element.
is(callbackCalledOnClone, false, "Upgrade should only be called once per clone.");
callbackCalledOnClone = true;
}
if (callbackCalledOnUpgrade && callbackCalledOnClone) {
SimpleTest.finish();
}
};
document.registerElement("x-foo", { prototype: p });
var anotherFooClone = foo.cloneNode(true);
SimpleTest.waitForExplicitFinish();
</script>
</body>
</html>

View File

@ -0,0 +1,56 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1081039
-->
<head>
<title>Test callbacks for cloned extended custom elements.</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=1081039">Bug 1081039</a>
<script>
SimpleTest.waitForExplicitFinish();
// Test to make sure created callback is called on clones that are upgraded and clones
// created after registering the custom element.
var callbackCalledOnUpgrade = false;
var callbackCalledOnClone = false;
var foo = document.createElement("button", "x-foo");
is(foo.getAttribute("is"), "x-foo");
var fooClone = foo.cloneNode(true);
var p = Object.create(HTMLButtonElement.prototype);
p.createdCallback = function() {
is(this.__proto__, p, "Correct prototype should be set on custom elements.");
if (this == fooClone) {
// Callback called for the element created before registering the custom element.
// Should be called on element upgrade.
is(callbackCalledOnUpgrade, false, "Upgrade should only be called once per clone.");
callbackCalledOnUpgrade = true;
} else if (this != foo) {
// Callback called for the element created after registering the custom element.
is(callbackCalledOnClone, false, "Upgrade should only be called once per clone.");
callbackCalledOnClone = true;
}
if (callbackCalledOnUpgrade && callbackCalledOnClone) {
SimpleTest.finish();
}
};
document.registerElement("x-foo", { prototype: p, extends: "button" });
var anotherFooClone = foo.cloneNode(true);
SimpleTest.waitForExplicitFinish();
</script>
</body>
</html>

View File

@ -439,14 +439,11 @@ nsHtml5TreeOperation::CreateElement(int32_t aNs,
value, value,
false); false);
// Custom element prototype swizzling may be needed if there is an // Custom element setup may be needed if there is an "is" attribute.
// "is" attribute.
if (kNameSpaceID_None == nsuri && !prefix && nsGkAtoms::is == localName) { if (kNameSpaceID_None == nsuri && !prefix && nsGkAtoms::is == localName) {
ErrorResult errorResult; newContent->OwnerDoc()->SetupCustomElement(newContent,
newContent->OwnerDoc()->SwizzleCustomElement(newContent, newContent->GetNameSpaceID(),
value, &value);
newContent->GetNameSpaceID(),
errorResult);
} }
} }
} }

View File

@ -3,12 +3,6 @@
[Test Document.createElement() sets the element\'s IS attribute value to type, if type is not the same as localName] [Test Document.createElement() sets the element\'s IS attribute value to type, if type is not the same as localName]
expected: FAIL expected: FAIL
[Test Document.createElement() sets the element\'s IS attribute value to type, if type is not the same as localName and an element definition with matching localName, namespace, and type is not registered]
expected: FAIL
[Test Document.createElementNS() sets the element\'s IS attribute value to type, if type is not the same as localName] [Test Document.createElementNS() sets the element\'s IS attribute value to type, if type is not the same as localName]
expected: FAIL expected: FAIL
[Test Document.createElementNS() sets the element\'s IS attribute value to type, if type is not the same as localNameand and an element definition with matching localName, namespace, and type is not registered ]
expected: FAIL