summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dom/base/CustomElementRegistry.cpp186
-rw-r--r--dom/base/CustomElementRegistry.h110
2 files changed, 267 insertions, 29 deletions
diff --git a/dom/base/CustomElementRegistry.cpp b/dom/base/CustomElementRegistry.cpp
index 6cd213210e..20eb59e94a 100644
--- a/dom/base/CustomElementRegistry.cpp
+++ b/dom/base/CustomElementRegistry.cpp
@@ -137,6 +137,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CustomElementRegistry)
cb.NoteXPCOMChild(callbacks->mDetachedCallback.Value());
}
}
+
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWhenDefinedPromiseMap)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
@@ -237,6 +238,7 @@ CustomElementRegistry::sProcessingStack;
CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow)
: mWindow(aWindow)
, mIsCustomDefinitionRunning(false)
+ , mIsBackupQueueProcessing(false)
{
mozilla::HoldJSObjects(this);
@@ -502,35 +504,7 @@ CustomElementRegistry::UpgradeCandidates(JSContext* aCx,
continue;
}
- elem->RemoveStates(NS_EVENT_STATE_UNRESOLVED);
-
- // Make sure that the element name matches the name in the definition.
- // (e.g. a definition for x-button extending button should match
- // <button is="x-button"> but not <x-button>.
- if (elem->NodeInfo()->NameAtom() != aDefinition->mLocalName) {
- //Skip over this element because definition does not apply.
- continue;
- }
-
- MOZ_ASSERT(elem->IsHTMLElement(aDefinition->mLocalName));
- nsWrapperCache* cache;
- CallQueryInterface(elem, &cache);
- MOZ_ASSERT(cache, "Element doesn't support wrapper cache?");
-
- // We want to set the custom prototype in the caller's comparment.
- // In the case that element is in a different compartment,
- // this will set the prototype on the element's wrapper and
- // thus only visible in the wrapper's compartment.
- JS::RootedObject wrapper(aCx);
- JS::Rooted<JSObject*> prototype(aCx, aDefinition->mPrototype);
- if ((wrapper = cache->GetWrapper()) && JS_WrapObject(aCx, &wrapper)) {
- if (!JS_SetPrototype(aCx, wrapper, prototype)) {
- continue;
- }
- }
-
- nsContentUtils::EnqueueLifecycleCallback(
- elem->OwnerDoc(), nsIDocument::eCreated, elem, nullptr, aDefinition);
+ EnqueueUpgradeReaction(elem, aDefinition);
}
}
}
@@ -590,6 +564,9 @@ CustomElementRegistry::Define(const nsAString& aName,
const ElementDefinitionOptions& aOptions,
ErrorResult& aRv)
{
+ // We do this for [CEReaction] temporarily and it will be removed
+ // after webidl supports [CEReaction] annotation in bug 1309147.
+ AutoCEReaction ceReaction(this);
aRv.MightThrowJSException();
AutoJSAPI jsapi;
@@ -883,6 +860,148 @@ CustomElementRegistry::WhenDefined(const nsAString& aName, ErrorResult& aRv)
return promise.forget();
}
+void
+CustomElementRegistry::EnqueueUpgradeReaction(Element* aElement,
+ CustomElementDefinition* aDefinition)
+{
+ Enqueue(aElement, new CustomElementUpgradeReaction(this, aDefinition));
+}
+
+void
+CustomElementRegistry::Upgrade(Element* aElement,
+ CustomElementDefinition* aDefinition)
+{
+ // TODO: This function will be replaced to v1 upgrade in bug 1299363
+ aElement->RemoveStates(NS_EVENT_STATE_UNRESOLVED);
+
+ // Make sure that the element name matches the name in the definition.
+ // (e.g. a definition for x-button extending button should match
+ // <button is="x-button"> but not <x-button>.
+ if (aElement->NodeInfo()->NameAtom() != aDefinition->mLocalName) {
+ //Skip over this element because definition does not apply.
+ return;
+ }
+
+ MOZ_ASSERT(aElement->IsHTMLElement(aDefinition->mLocalName));
+ nsWrapperCache* cache;
+ CallQueryInterface(aElement, &cache);
+ MOZ_ASSERT(cache, "Element doesn't support wrapper cache?");
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(mWindow))) {
+ return;
+ }
+
+ JSContext *cx = jsapi.cx();
+ // We want to set the custom prototype in the the compartment of define()'s caller.
+ // We store the prototype from define() directly,
+ // hence the prototype's compartment is the caller's compartment.
+ JS::RootedObject wrapper(cx);
+ JS::Rooted<JSObject*> prototype(cx, aDefinition->mPrototype);
+ { // Enter prototype's compartment.
+ JSAutoCompartment ac(cx, prototype);
+
+ if ((wrapper = cache->GetWrapper()) && JS_WrapObject(cx, &wrapper)) {
+ if (!JS_SetPrototype(cx, wrapper, prototype)) {
+ return;
+ }
+ }
+ } // Leave prototype's compartment.
+
+ // Enqueuing the created callback will set the CustomElementData on the
+ // element, causing prototype swizzling to occur in Element::WrapObject.
+ EnqueueLifecycleCallback(nsIDocument::eCreated, aElement, nullptr, aDefinition);
+}
+
+
+void
+CustomElementRegistry::CreateAndPushElementQueue()
+{
+ // Push a new element queue onto the custom element reactions stack.
+ mReactionsStack.AppendElement();
+}
+
+void
+CustomElementRegistry::PopAndInvokeElementQueue()
+{
+ // Pop the element queue from the custom element reactions stack,
+ // and invoke custom element reactions in that queue.
+ MOZ_ASSERT(!mReactionsStack.IsEmpty(),
+ "Reaction stack shouldn't be empty");
+
+ ElementQueue& elementQueue = mReactionsStack.LastElement();
+ CustomElementRegistry::InvokeReactions(elementQueue);
+ DebugOnly<bool> isRemovedElement = mReactionsStack.RemoveElement(elementQueue);
+ MOZ_ASSERT(isRemovedElement,
+ "Reaction stack should have an element queue to remove");
+}
+
+void
+CustomElementRegistry::Enqueue(Element* aElement,
+ CustomElementReaction* aReaction)
+{
+ // Add element to the current element queue.
+ if (!mReactionsStack.IsEmpty()) {
+ mReactionsStack.LastElement().AppendElement(do_GetWeakReference(aElement));
+ ReactionQueue* reactionQueue =
+ mElementReactionQueueMap.LookupOrAdd(aElement);
+ reactionQueue->AppendElement(aReaction);
+
+ return;
+ }
+
+ // If the custom element reactions stack is empty, then:
+ // Add element to the backup element queue.
+ mBackupQueue.AppendElement(do_GetWeakReference(aElement));
+
+ ReactionQueue* reactionQueue =
+ mElementReactionQueueMap.LookupOrAdd(aElement);
+ reactionQueue->AppendElement(aReaction);
+
+ if (mIsBackupQueueProcessing) {
+ return;
+ }
+
+ CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
+ RefPtr<ProcessBackupQueueRunnable> processBackupQueueRunnable =
+ new ProcessBackupQueueRunnable(this);
+ context->DispatchToMicroTask(processBackupQueueRunnable.forget());
+}
+
+void
+CustomElementRegistry::InvokeBackupQueue()
+{
+ CustomElementRegistry::InvokeReactions(mBackupQueue);
+}
+
+void
+CustomElementRegistry::InvokeReactions(ElementQueue& aElementQueue)
+{
+ for (uint32_t i = 0; i < aElementQueue.Length(); ++i) {
+ nsCOMPtr<Element> element = do_QueryReferent(aElementQueue[i]);
+
+ if (!element) {
+ continue;
+ }
+
+ nsAutoPtr<ReactionQueue> reactions;
+ mElementReactionQueueMap.RemoveAndForget(element, reactions);
+
+ MOZ_ASSERT(reactions,
+ "Associated ReactionQueue must be found in mElementReactionQueueMap");
+
+ for (uint32_t j = 0; j < reactions->Length(); ++j) {
+ nsAutoPtr<CustomElementReaction>& reaction = reactions->ElementAt(j);
+ reaction->Invoke(element);
+ }
+ }
+ aElementQueue.Clear();
+}
+
+//-----------------------------------------------------
+// CustomElementDefinition
+
+
CustomElementDefinition::CustomElementDefinition(nsIAtom* aType,
nsIAtom* aLocalName,
JSObject* aConstructor,
@@ -898,5 +1017,14 @@ CustomElementDefinition::CustomElementDefinition(nsIAtom* aType,
{
}
+//-----------------------------------------------------
+// CustomElementUpgradeReaction
+
+/* virtual */ void
+CustomElementUpgradeReaction::Invoke(Element* aElement)
+{
+ mRegistry->Upgrade(aElement, mDefinition);
+}
+
} // namespace dom
} // namespace mozilla
diff --git a/dom/base/CustomElementRegistry.h b/dom/base/CustomElementRegistry.h
index 8ef0785af3..b3b4d7c0f0 100644
--- a/dom/base/CustomElementRegistry.h
+++ b/dom/base/CustomElementRegistry.h
@@ -134,6 +134,37 @@ struct CustomElementDefinition
}
};
+class CustomElementReaction
+{
+public:
+ explicit CustomElementReaction(CustomElementRegistry* aRegistry,
+ CustomElementDefinition* aDefinition)
+ : mRegistry(aRegistry)
+ , mDefinition(aDefinition)
+ {
+ };
+
+ virtual ~CustomElementReaction() = default;
+ virtual void Invoke(Element* aElement) = 0;
+
+protected:
+ CustomElementRegistry* mRegistry;
+ CustomElementDefinition* mDefinition;
+};
+
+class CustomElementUpgradeReaction final : public CustomElementReaction
+{
+public:
+ explicit CustomElementUpgradeReaction(CustomElementRegistry* aRegistry,
+ CustomElementDefinition* aDefinition)
+ : CustomElementReaction(aRegistry, aDefinition)
+ {
+ }
+
+private:
+ virtual void Invoke(Element* aElement) override;
+};
+
class CustomElementRegistry final : public nsISupports,
public nsWrapperCache
{
@@ -177,6 +208,24 @@ public:
void GetCustomPrototype(nsIAtom* aAtom,
JS::MutableHandle<JSObject*> aPrototype);
+ /**
+ * Enqueue a custom element upgrade reaction
+ * https://html.spec.whatwg.org/multipage/scripting.html#enqueue-a-custom-element-upgrade-reaction
+ */
+ void EnqueueUpgradeReaction(Element* aElement,
+ CustomElementDefinition* aDefinition);
+
+ void Upgrade(Element* aElement, CustomElementDefinition* aDefinition);
+
+ // [CEReactions] Before executing the algorithm's steps
+ // Push a new element queue onto the custom element reactions stack.
+ void CreateAndPushElementQueue();
+
+ // [CEReactions] After executing the algorithm's steps
+ // Pop the element queue from the custom element reactions stack,
+ // and invoke custom element reactions in that queue.
+ void PopAndInvokeElementQueue();
+
private:
explicit CustomElementRegistry(nsPIDOMWindowInner* aWindow);
~CustomElementRegistry();
@@ -198,6 +247,21 @@ private:
nsIAtom* aKey,
CustomElementDefinition* aDefinition);
+ void InvokeBackupQueue();
+
+ void Enqueue(Element* aElement, CustomElementReaction* aReaction);
+
+ // nsWeakPtr is a weak pointer of Element
+ // The element reaction queues are stored in ElementReactionQueueMap.
+ // We need to lookup ElementReactionQueueMap again to get relevant reaction queue.
+ typedef nsTArray<nsWeakPtr> ElementQueue;
+
+ /**
+ * Invoke custom element reactions
+ * https://html.spec.whatwg.org/multipage/scripting.html#invoke-custom-element-reactions
+ */
+ void InvokeReactions(ElementQueue& aElementQueue);
+
typedef nsClassHashtable<nsISupportsHashKey, CustomElementDefinition>
DefinitionMap;
typedef nsClassHashtable<nsISupportsHashKey, nsTArray<nsWeakPtr>>
@@ -238,6 +302,17 @@ private:
// It is used to prevent reentrant invocations of element definition.
bool mIsCustomDefinitionRunning;
+ // https://html.spec.whatwg.org/#enqueue-an-element-on-the-appropriate-element-queue
+ bool mIsBackupQueueProcessing;
+
+ typedef nsTArray<nsAutoPtr<CustomElementReaction>> ReactionQueue;
+ typedef nsClassHashtable<nsISupportsHashKey, ReactionQueue>
+ ElementReactionQueueMap;
+
+ ElementReactionQueueMap mElementReactionQueueMap;
+
+ nsTArray<ElementQueue> mReactionsStack;
+ ElementQueue mBackupQueue;
private:
class MOZ_RAII AutoSetRunningFlag final {
@@ -258,6 +333,28 @@ private:
CustomElementRegistry* mRegistry;
};
+private:
+ class ProcessBackupQueueRunnable : public mozilla::Runnable {
+ public:
+ explicit ProcessBackupQueueRunnable(CustomElementRegistry* aRegistry)
+ : mRegistry(aRegistry)
+ {
+ MOZ_ASSERT(!mRegistry->mIsBackupQueueProcessing,
+ "mIsBackupQueueProcessing should be initially false");
+ mRegistry->mIsBackupQueueProcessing = true;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ mRegistry->InvokeBackupQueue();
+ mRegistry->mIsBackupQueueProcessing = false;
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<CustomElementRegistry> mRegistry;
+ };
+
public:
nsISupports* GetParentObject() const;
@@ -272,6 +369,19 @@ public:
already_AddRefed<Promise> WhenDefined(const nsAString& aName, ErrorResult& aRv);
};
+class MOZ_RAII AutoCEReaction final {
+ public:
+ explicit AutoCEReaction(CustomElementRegistry* aRegistry)
+ : mRegistry(aRegistry) {
+ mRegistry->CreateAndPushElementQueue();
+ }
+ ~AutoCEReaction() {
+ mRegistry->PopAndInvokeElementQueue();
+ }
+ private:
+ RefPtr<CustomElementRegistry> mRegistry;
+};
+
} // namespace dom
} // namespace mozilla