Bug 1462703 - Upgrade the created element after callback runs
nsContentUtils::NS_NewXULOrHTMLElement will call into
CustomElementRegisty::RegisterCallbackUpgradeElement, which keeps
the newly created element, allowing RunCustomElementCreationCallback
to upgrade them after the callback runs.
It is unclear if this changes the order of constructor executions,
but even so it should not affact our use case.
MozReview-Commit-ID: LWTn7B35aBv
--- a/dom/base/CustomElementRegistry.cpp
+++ b/dom/base/CustomElementRegistry.cpp
@@ -307,35 +307,52 @@ NS_IMETHODIMP
CustomElementRegistry::RunCustomElementCreationCallback::Run()
{
ErrorResult er;
nsDependentAtomString value(mAtom);
mCallback->Call(value, er);
MOZ_ASSERT(NS_SUCCEEDED(er.StealNSResult()),
"chrome JavaScript error in the callback.");
- MOZ_ASSERT(mRegistry->mCustomDefinitions.GetWeak(mAtom),
- "Callback should define the definition of type.");
+ CustomElementDefinition* definition =
+ mRegistry->mCustomDefinitions.GetWeak(mAtom);
+ MOZ_ASSERT(definition, "Callback should define the definition of type.");
MOZ_ASSERT(!mRegistry->mElementCreationCallbacks.GetWeak(mAtom),
"Callback should be removed.");
+ nsAutoPtr<nsTHashtable<nsRefPtrHashKey<nsIWeakReference>>> elements;
+ mRegistry->mElementCreationCallbacksUpgradeCandidatesMap.Remove(mAtom, &elements);
+ MOZ_ASSERT(elements, "There should be a list");
+
+ for (auto iter = elements->Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<Element> elem = do_QueryReferent(iter.Get()->GetKey());
+ if (!elem) {
+ continue;
+ }
+
+ CustomElementRegistry::Upgrade(elem, definition, er);
+ MOZ_ASSERT(NS_SUCCEEDED(er.StealNSResult()),
+ "chrome JavaScript error in custom element construction.");
+ }
+
return NS_OK;
}
CustomElementDefinition*
CustomElementRegistry::LookupCustomElementDefinition(nsAtom* aNameAtom,
nsAtom* aTypeAtom)
{
CustomElementDefinition* data = mCustomDefinitions.GetWeak(aTypeAtom);
if (!data) {
RefPtr<CustomElementCreationCallback> callback;
mElementCreationCallbacks.Get(aTypeAtom, getter_AddRefs(callback));
if (callback) {
mElementCreationCallbacks.Remove(aTypeAtom);
+ mElementCreationCallbacksUpgradeCandidatesMap.LookupOrAdd(aTypeAtom);
RefPtr<Runnable> runnable =
new RunCustomElementCreationCallback(this, aTypeAtom, callback);
nsContentUtils::AddScriptRunner(runnable);
data = mCustomDefinitions.GetWeak(aTypeAtom);
}
}
if (data && data->mLocalName == aNameAtom) {
--- a/dom/base/CustomElementRegistry.h
+++ b/dom/base/CustomElementRegistry.h
@@ -371,27 +371,23 @@ private:
class RunCustomElementCreationCallback : public mozilla::Runnable
{
public:
NS_DECL_NSIRUNNABLE
explicit RunCustomElementCreationCallback(CustomElementRegistry* aRegistry,
nsAtom* aAtom,
CustomElementCreationCallback* aCallback)
: mozilla::Runnable("CustomElementRegistry::RunCustomElementCreationCallback")
-#ifdef DEBUG
, mRegistry(aRegistry)
-#endif
, mAtom(aAtom)
, mCallback(aCallback)
{
}
private:
-#ifdef DEBUG
RefPtr<CustomElementRegistry> mRegistry;
-#endif
RefPtr<nsAtom> mAtom;
RefPtr<CustomElementCreationCallback> mCallback;
};
public:
/**
* Looking up a custom element definition.
* https://html.spec.whatwg.org/#look-up-a-custom-element-definition
@@ -425,16 +421,48 @@ public:
nsAtom* aTypeName = nullptr);
/**
* Unregister an unresolved custom element that is a candidate for
* upgrade when a custom element is removed from tree.
*/
void UnregisterUnresolvedElement(Element* aElement,
nsAtom* aTypeName = nullptr);
+
+ /**
+ * Register an element to be upgraded when the custom element creation
+ * callback is executed.
+ *
+ * To be used when LookupCustomElementDefinition() didn't return a definition,
+ * but with the callback scheduled to be run.
+ */
+ inline void RegisterCallbackUpgradeElement(Element* aElement,
+ nsAtom* aTypeName = nullptr)
+ {
+ if (mElementCreationCallbacksUpgradeCandidatesMap.IsEmpty()) {
+ return;
+ }
+
+ RefPtr<nsAtom> typeName = aTypeName;
+ if (!typeName) {
+ typeName = aElement->NodeInfo()->NameAtom();
+ }
+
+ nsTHashtable<nsRefPtrHashKey<nsIWeakReference>>* elements =
+ mElementCreationCallbacksUpgradeCandidatesMap.Get(typeName);
+
+ // If there isn't a table, there won't be a definition added by the callback.
+ if (!elements) {
+ return;
+ }
+
+ nsWeakPtr elem = do_GetWeakReference(aElement);
+ elements->PutEntry(elem);
+ }
+
private:
~CustomElementRegistry();
static UniquePtr<CustomElementCallback> CreateCustomElementCallback(
nsIDocument::ElementCallbackType aType, Element* aCustomElement,
LifecycleCallbackArgs* aArgs,
LifecycleAdoptedCallbackArgs* aAdoptedCallbackArgs,
CustomElementDefinition* aDefinition);
@@ -474,16 +502,20 @@ private:
WhenDefinedPromiseMap;
WhenDefinedPromiseMap mWhenDefinedPromiseMap;
// The "upgrade candidates map" from the web components spec. Maps from a
// namespace id and local name to a list of elements to upgrade if that
// element is registered as a custom element.
CandidateMap mCandidatesMap;
+ // If an element creation callback is found, the nsTHashtable for the
+ // type is created here, and elements will later be upgraded.
+ CandidateMap mElementCreationCallbacksUpgradeCandidatesMap;
+
nsCOMPtr<nsPIDOMWindowInner> mWindow;
// It is used to prevent reentrant invocations of element definition.
bool mIsCustomDefinitionRunning;
private:
class MOZ_RAII AutoSetRunningFlag final {
public:
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -10034,86 +10034,95 @@ nsContentUtils::NewXULOrHTMLElement(Elem
}
if (!*aResult) {
return NS_ERROR_OUT_OF_MEMORY;
}
if (customElementEnabled && isCustomElement) {
(*aResult)->SetCustomElementData(new CustomElementData(typeAtom));
+ nsContentUtils::RegisterCallbackUpgradeElement(*aResult, typeAtom);
}
return NS_OK;
}
+CustomElementRegistry*
+GetCustomElementRegistry(nsIDocument* aDoc)
+{
+ MOZ_ASSERT(aDoc);
+
+ if (!aDoc->GetDocShell()) {
+ return nullptr;
+ }
+
+ nsPIDOMWindowInner* window = aDoc->GetInnerWindow();
+ if (!window) {
+ return nullptr;
+ }
+
+ return window->CustomElements();
+}
+
/* static */ CustomElementDefinition*
nsContentUtils::LookupCustomElementDefinition(nsIDocument* aDoc,
nsAtom* aNameAtom,
uint32_t aNameSpaceID,
nsAtom* aTypeAtom)
{
- MOZ_ASSERT(aDoc);
-
- if ((aNameSpaceID != kNameSpaceID_XUL &&
- aNameSpaceID != kNameSpaceID_XHTML) ||
- !aDoc->GetDocShell()) {
+ if (aNameSpaceID != kNameSpaceID_XUL &&
+ aNameSpaceID != kNameSpaceID_XHTML) {
return nullptr;
}
- nsPIDOMWindowInner* window = aDoc->GetInnerWindow();
- if (!window) {
- return nullptr;
- }
-
- RefPtr<CustomElementRegistry> registry(window->CustomElements());
+ RefPtr<CustomElementRegistry> registry(GetCustomElementRegistry(aDoc));
if (!registry) {
return nullptr;
}
return registry->LookupCustomElementDefinition(aNameAtom, aTypeAtom);
}
/* static */ void
+nsContentUtils::RegisterCallbackUpgradeElement(Element* aElement,
+ nsAtom* aTypeName)
+{
+ MOZ_ASSERT(aElement);
+
+ nsIDocument* doc = aElement->OwnerDoc();
+ CustomElementRegistry* registry = GetCustomElementRegistry(doc);
+ if (registry) {
+ registry->RegisterCallbackUpgradeElement(aElement, aTypeName);
+ }
+}
+
+/* static */ void
nsContentUtils::RegisterUnresolvedElement(Element* aElement, nsAtom* aTypeName)
{
MOZ_ASSERT(aElement);
nsIDocument* doc = aElement->OwnerDoc();
- nsPIDOMWindowInner* window(doc->GetInnerWindow());
- if (!window) {
- return;
- }
-
- RefPtr<CustomElementRegistry> registry(window->CustomElements());
- if (!registry) {
- return;
- }
-
- registry->RegisterUnresolvedElement(aElement, aTypeName);
+ CustomElementRegistry* registry = GetCustomElementRegistry(doc);
+ if (registry) {
+ registry->RegisterUnresolvedElement(aElement, aTypeName);
+ }
}
/* static */ void
nsContentUtils::UnregisterUnresolvedElement(Element* aElement)
{
MOZ_ASSERT(aElement);
nsAtom* typeAtom =
aElement->GetCustomElementData()->GetCustomElementType();
nsIDocument* doc = aElement->OwnerDoc();
- nsPIDOMWindowInner* window(doc->GetInnerWindow());
- if (!window) {
- return;
- }
-
- CustomElementRegistry* registry = window->CustomElements();
- if (!registry) {
- return;
- }
-
- registry->UnregisterUnresolvedElement(aElement, typeAtom);
+ CustomElementRegistry* registry = GetCustomElementRegistry(doc);
+ if (registry) {
+ registry->UnregisterUnresolvedElement(aElement, typeAtom);
+ }
}
/* static */ void
nsContentUtils::EnqueueUpgradeReaction(Element* aElement,
CustomElementDefinition* aDefinition)
{
MOZ_ASSERT(aElement);
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -3036,16 +3036,19 @@ public:
* https://html.spec.whatwg.org/#look-up-a-custom-element-definition
*/
static mozilla::dom::CustomElementDefinition*
LookupCustomElementDefinition(nsIDocument* aDoc,
nsAtom* aNameAtom,
uint32_t aNameSpaceID,
nsAtom* aTypeAtom);
+ static void RegisterCallbackUpgradeElement(Element* aElement,
+ nsAtom* aTypeName);
+
static void RegisterUnresolvedElement(Element* aElement, nsAtom* aTypeName);
static void UnregisterUnresolvedElement(Element* aElement);
static void EnqueueUpgradeReaction(Element* aElement,
mozilla::dom::CustomElementDefinition* aDefinition);
static void EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
Element* aCustomElement,
--- a/dom/tests/mochitest/webcomponents/test_custom_element_set_element_creation_callback.html
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_set_element_creation_callback.html
@@ -112,18 +112,16 @@ function simpleInnerHTMLTest() {
is(type, "x-html-obj-elem5", "Type is passed to the callback.");
customElements.define("x-html-obj-elem5", XObjElement4);
});
ok(!callbackCalled, "Callback should not be called.");
let p = document.createElement("p");
p.innerHTML = "<x-html-obj-elem5></x-html-obj-elem5>";
let el = p.firstChild;
ok(callbackCalled, "Callback should be called.");
- isnot(Object.getPrototypeOf(el), XObjElement4.prototype, "Created element is not upgraded.");
- document.body.appendChild(p);
is(Object.getPrototypeOf(el), XObjElement4.prototype, "Created element should have the prototype of the custom type.");
}
function twoElementInnerHTMLTest() {
let callbackCalled = false;
class XObjElement5 extends HTMLElement {};
registry.setElementCreationCallback("x-html-obj-elem6", (type) => {
if (callbackCalled) {
@@ -135,19 +133,16 @@ function twoElementInnerHTMLTest() {
});
ok(!callbackCalled, "Callback should not be called.");
let p = document.createElement("p");
p.innerHTML =
"<x-html-obj-elem6></x-html-obj-elem6><x-html-obj-elem6></x-html-obj-elem6>";
let el1 = p.firstChild;
let el2 = p.lastChild;
ok(callbackCalled, "Callback should be called.");
- isnot(Object.getPrototypeOf(el1), XObjElement5.prototype, "Created element is not upgraded.");
- isnot(Object.getPrototypeOf(el2), XObjElement5.prototype, "Created element is not upgraded.");
- document.body.appendChild(p);
is(Object.getPrototypeOf(el1), XObjElement5.prototype, "Created element should have the prototype of the custom type.");
is(Object.getPrototypeOf(el2), XObjElement5.prototype, "Created element should have the prototype of the custom type.");
}
function startTest() {
simpleTest();
multipleDefinitionTest();
throwIfDefined();