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 59767dfccc
commit ad8005e0f4
11 changed files with 202 additions and 56 deletions

View File

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

View File

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

View File

@ -2237,10 +2237,9 @@ public:
Element* aCustomElement,
mozilla::dom::LifecycleCallbackArgs* aArgs = nullptr,
mozilla::dom::CustomElementDefinition* aDefinition = nullptr) = 0;
virtual void SwizzleCustomElement(Element* aElement,
const nsAString& aTypeExtension,
uint32_t aNamespaceID,
mozilla::ErrorResult& rv) = 0;
virtual void SetupCustomElement(Element* aElement,
uint32_t aNamespaceID,
const nsAString* aTypeExtension = nullptr) = 0;
virtual void
RegisterElement(JSContext* aCx, const nsAString& aName,
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));
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 we're cloning we need to insert the cloned children into the cloned
// parent.

View File

@ -266,13 +266,7 @@ NS_NewHTMLElement(Element** aResult, already_AddRefed<mozilla::dom::NodeInfo>&&
return NS_ERROR_OUT_OF_MEMORY;
}
// Element may be unresolved at this point.
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);
doc->SetupCustomElement(*aResult, kNameSpaceID_XHTML);
return NS_OK;
}

View File

@ -5,6 +5,9 @@ support-files =
[test_bug900724.html]
[test_bug1017896.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_dest_insertion_points.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,
false);
// Custom element prototype swizzling may be needed if there is an
// "is" attribute.
// Custom element setup may be needed if there is an "is" attribute.
if (kNameSpaceID_None == nsuri && !prefix && nsGkAtoms::is == localName) {
ErrorResult errorResult;
newContent->OwnerDoc()->SwizzleCustomElement(newContent,
value,
newContent->GetNameSpaceID(),
errorResult);
newContent->OwnerDoc()->SetupCustomElement(newContent,
newContent->GetNameSpaceID(),
&value);
}
}
}

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]
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]
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