diff options
-rw-r--r-- | dom/base/CustomElementRegistry.cpp | 182 | ||||
-rw-r--r-- | dom/base/CustomElementRegistry.h | 30 |
2 files changed, 177 insertions, 35 deletions
diff --git a/dom/base/CustomElementRegistry.cpp b/dom/base/CustomElementRegistry.cpp index ce2e4b04f..e619ad599 100644 --- a/dom/base/CustomElementRegistry.cpp +++ b/dom/base/CustomElementRegistry.cpp @@ -19,7 +19,7 @@ namespace dom { void CustomElementCallback::Call() { - ErrorResult rv; + IgnoredErrorResult rv; switch (mType) { case nsIDocument::eCreated: { @@ -55,10 +55,6 @@ CustomElementCallback::Call() mArgs.name, mArgs.oldValue, mArgs.newValue, rv); break; } - - // If callbacks throw exceptions, it'll be handled and reported in - // Lifecycle*Callback::Call function. - rv.SuppressException(); } void @@ -83,6 +79,38 @@ CustomElementCallback::CustomElementCallback(Element* aThisObject, } //----------------------------------------------------- +// CustomElementConstructor + +already_AddRefed<Element> +CustomElementConstructor::Construct(const char* aExecutionReason, + ErrorResult& aRv) +{ + CallSetup s(this, aRv, aExecutionReason, + CallbackFunction::eRethrowExceptions); + + JSContext* cx = s.GetContext(); + if (!cx) { + MOZ_ASSERT(aRv.Failed()); + return nullptr; + } + + JS::Rooted<JSObject*> result(cx); + JS::Rooted<JS::Value> constructor(cx, JS::ObjectValue(*mCallback)); + if (!JS::Construct(cx, constructor, JS::HandleValueArray::empty(), &result)) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + RefPtr<Element> element; + if (NS_FAILED(UNWRAP_OBJECT(Element, &result, element))) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + return element.forget(); +} + +//----------------------------------------------------- // CustomElementData CustomElementData::CustomElementData(nsIAtom* aType) @@ -101,6 +129,33 @@ CustomElementData::CustomElementData(nsIAtom* aType, State aState) //----------------------------------------------------- // CustomElementRegistry +namespace { + +class MOZ_RAII AutoConstructionStackEntry final +{ +public: + AutoConstructionStackEntry(nsTArray<RefPtr<nsGenericHTMLElement>>& aStack, + nsGenericHTMLElement* aElement) + : mStack(aStack) + { + mIndex = mStack.Length(); + mStack.AppendElement(aElement); + } + + ~AutoConstructionStackEntry() + { + MOZ_ASSERT(mIndex == mStack.Length() - 1, + "Removed element should be the last element"); + mStack.RemoveElementAt(mIndex); + } + +private: + nsTArray<RefPtr<nsGenericHTMLElement>>& mStack; + uint32_t mIndex; +}; + +} // namespace anonymous + // Only needed for refcounted objects. NS_IMPL_CYCLE_COLLECTION_CLASS(CustomElementRegistry) @@ -139,6 +194,10 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CustomElementRegistry) "mCustomDefinitions->mCallbacks->mDetachedCallback"); cb.NoteXPCOMChild(callbacks->mDetachedCallback.Value()); } + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, + "mCustomDefinitions->mConstructor"); + cb.NoteXPCOMChild(iter.UserData()->mConstructor); } NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWhenDefinedPromiseMap) @@ -147,9 +206,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CustomElementRegistry) for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) { - aCallbacks.Trace(&iter.UserData()->mConstructor, - "mCustomDefinitions constructor", - aClosure); aCallbacks.Trace(&iter.UserData()->mPrototype, "mCustomDefinitions prototype", aClosure); @@ -418,6 +474,7 @@ CustomElementRegistry::UpgradeCandidates(nsIAtom* aKey, return; } + // TODO: Bug 1326028 - Upgrade custom element in shadow-including tree order nsAutoPtr<nsTArray<nsWeakPtr>> candidates; mCandidatesMap.RemoveAndForget(aKey, candidates); if (candidates) { @@ -696,7 +753,7 @@ CustomElementRegistry::Define(const nsAString& aName, CustomElementDefinition* definition = new CustomElementDefinition(nameAtom, localNameAtom, - constructor, + &aFunctionConstructor, constructorPrototype, callbacks, 0 /* TODO dependent on HTML imports. Bug 877072 */); @@ -717,7 +774,6 @@ CustomElementRegistry::Define(const nsAString& aName, /** * 13. 14. 15. Upgrade candidates */ - // TODO: Bug 1299363 - Implement custom element v1 upgrade algorithm UpgradeCandidates(nameAtom, definition, aRv); /** @@ -748,7 +804,7 @@ CustomElementRegistry::Get(JSContext* aCx, const nsAString& aName, return; } - aRetVal.setObject(*data->mConstructor); + aRetVal.setObjectOrNull(data->mConstructor->Callable()); return; } @@ -782,23 +838,67 @@ CustomElementRegistry::WhenDefined(const nsAString& aName, ErrorResult& aRv) return promise.forget(); } +namespace { + +static void +DoUpgrade(Element* aElement, + CustomElementConstructor* aConstructor, + ErrorResult& aRv) +{ + // Rethrow the exception since it might actually throw the exception from the + // upgrade steps back out to the caller of document.createElement. + RefPtr<Element> constructResult = + aConstructor->Construct("Custom Element Upgrade", aRv); + if (aRv.Failed()) { + return; + } + + if (constructResult.get() != aElement) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } +} + +} // anonymous namespace + +// https://html.spec.whatwg.org/multipage/scripting.html#upgrades void CustomElementRegistry::Upgrade(Element* aElement, - CustomElementDefinition* aDefinition) + CustomElementDefinition* aDefinition, + ErrorResult& aRv) { - // 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. + RefPtr<CustomElementData> data = aElement->GetCustomElementData(); + MOZ_ASSERT(data, "CustomElementData should exist"); + + // Step 1 and step 2. + if (data->mState == CustomElementData::State::eCustom || + data->mState == CustomElementData::State::eFailed) { + return; + } + + // Step 3 and Step 4. + // TODO: Bug 1334051 - Implement list of observed attributes for custom elements' attributeChanged callbacks + // TODO: Bug 1334043 - Implement connected lifecycle callbacks for custom elements + + // Step 5. + AutoConstructionStackEntry acs(aDefinition->mConstructionStack, + nsGenericHTMLElement::FromContent(aElement)); + + // Step 6 and step 7. + DoUpgrade(aElement, aDefinition->mConstructor, aRv); + if (aRv.Failed()) { + data->mState = CustomElementData::State::eFailed; + // Empty element's custom element reaction queue. + data->mReactionQueue.Clear(); return; } - MOZ_ASSERT(aElement->IsHTMLElement(aDefinition->mLocalName)); + // Step 8. + data->mState = CustomElementData::State::eCustom; + // This is for old spec. EnqueueLifecycleCallback(nsIDocument::eCreated, aElement, nullptr, aDefinition); } @@ -824,7 +924,13 @@ CustomElementReactionsStack::PopAndInvokeElementQueue() ElementQueue* elementQueue = mReactionsStack.ElementAt(lastIndex).get(); // Check element queue size in order to reduce function call overhead. if (!elementQueue->IsEmpty()) { - InvokeReactions(elementQueue); + // It is still not clear what error reporting will look like in custom + // element, see https://github.com/w3c/webcomponents/issues/635. + // We usually report the error to entry global in gecko, so just follow the + // same behavior here. + nsIGlobalObject* global = GetEntryGlobal(); + MOZ_ASSERT(global, "Should always have a entry global here!"); + InvokeReactions(elementQueue, global); } // InvokeReactions() might create other custom element reactions, but those @@ -887,13 +993,24 @@ CustomElementReactionsStack::InvokeBackupQueue() { // Check backup queue size in order to reduce function call overhead. if (!mBackupQueue.IsEmpty()) { - InvokeReactions(&mBackupQueue); + // Upgrade reactions won't be scheduled in backup queue and the exception of + // callback reactions will be automatically reported in CallSetup. + // If the reactions are invoked from backup queue (in microtask check point), + // we don't need to pass global object for error reporting. + InvokeReactions(&mBackupQueue, nullptr); } } void -CustomElementReactionsStack::InvokeReactions(ElementQueue* aElementQueue) +CustomElementReactionsStack::InvokeReactions(ElementQueue* aElementQueue, + nsIGlobalObject* aGlobal) { + // This is used for error reporting. + Maybe<AutoEntryScript> aes; + if (aGlobal) { + aes.emplace(aGlobal, "custom elements reaction invocation"); + } + // Note: It's possible to re-enter this method. for (uint32_t i = 0; i < aElementQueue->Length(); ++i) { nsCOMPtr<Element> element = do_QueryReferent(aElementQueue->ElementAt(i)); @@ -911,7 +1028,16 @@ CustomElementReactionsStack::InvokeReactions(ElementQueue* aElementQueue) // this funciton. The entry will be removed when bug 1379573 is landed. auto reaction(Move(reactions.ElementAt(j))); if (reaction) { - reaction->Invoke(element); + ErrorResult rv; + reaction->Invoke(element, rv); + if (aes) { + JSContext* cx = aes->cx(); + if (rv.MaybeSetPendingException(cx)) { + aes->ReportException(); + } + MOZ_ASSERT(!JS_IsExceptionPending(cx)); + } + MOZ_ASSERT(!rv.Failed()); } } reactions.Clear(); @@ -924,13 +1050,13 @@ CustomElementReactionsStack::InvokeReactions(ElementQueue* aElementQueue) CustomElementDefinition::CustomElementDefinition(nsIAtom* aType, nsIAtom* aLocalName, - JSObject* aConstructor, + Function* aConstructor, JSObject* aPrototype, LifecycleCallbacks* aCallbacks, uint32_t aDocOrder) : mType(aType), mLocalName(aLocalName), - mConstructor(aConstructor), + mConstructor(new CustomElementConstructor(aConstructor)), mPrototype(aPrototype), mCallbacks(aCallbacks), mDocOrder(aDocOrder) @@ -941,16 +1067,16 @@ CustomElementDefinition::CustomElementDefinition(nsIAtom* aType, // CustomElementUpgradeReaction /* virtual */ void -CustomElementUpgradeReaction::Invoke(Element* aElement) +CustomElementUpgradeReaction::Invoke(Element* aElement, ErrorResult& aRv) { - mRegistry->Upgrade(aElement, mDefinition); + mRegistry->Upgrade(aElement, mDefinition, aRv); } //----------------------------------------------------- // CustomElementCallbackReaction /* virtual */ void -CustomElementCallbackReaction::Invoke(Element* aElement) +CustomElementCallbackReaction::Invoke(Element* aElement, ErrorResult& aRv) { mCustomElementCallback->Call(); } diff --git a/dom/base/CustomElementRegistry.h b/dom/base/CustomElementRegistry.h index 620492cbb..c45f3f70c 100644 --- a/dom/base/CustomElementRegistry.h +++ b/dom/base/CustomElementRegistry.h @@ -66,6 +66,18 @@ private: CustomElementData* mOwnerData; }; +class CustomElementConstructor final : public CallbackFunction +{ +public: + explicit CustomElementConstructor(CallbackFunction* aOther) + : CallbackFunction(aOther) + { + MOZ_ASSERT(JS::IsConstructor(mCallback)); + } + + already_AddRefed<Element> Construct(const char* aExecutionReason, ErrorResult& aRv); +}; + // Each custom element has an associated callback queue and an element is // being created flag. struct CustomElementData @@ -114,7 +126,7 @@ struct CustomElementDefinition { CustomElementDefinition(nsIAtom* aType, nsIAtom* aLocalName, - JSObject* aConstructor, + Function* aConstructor, JSObject* aPrototype, mozilla::dom::LifecycleCallbacks* aCallbacks, uint32_t aDocOrder); @@ -126,7 +138,7 @@ struct CustomElementDefinition nsCOMPtr<nsIAtom> mLocalName; // The custom element constructor. - JS::Heap<JSObject *> mConstructor; + RefPtr<CustomElementConstructor> mConstructor; // The prototype to use for new custom elements of this type. JS::Heap<JSObject *> mPrototype; @@ -156,7 +168,7 @@ public: } virtual ~CustomElementReaction() = default; - virtual void Invoke(Element* aElement) = 0; + virtual void Invoke(Element* aElement, ErrorResult& aRv) = 0; virtual void Traverse(nsCycleCollectionTraversalCallback& aCb) const { } @@ -176,7 +188,7 @@ public: } private: - virtual void Invoke(Element* aElement) override; + virtual void Invoke(Element* aElement, ErrorResult& aRv) override; }; class CustomElementCallbackReaction final : public CustomElementReaction @@ -196,7 +208,7 @@ class CustomElementCallbackReaction final : public CustomElementReaction } private: - virtual void Invoke(Element* aElement) override; + virtual void Invoke(Element* aElement, ErrorResult& aRv) override; UniquePtr<CustomElementCallback> mCustomElementCallback; }; @@ -258,7 +270,7 @@ private: * Invoke custom element reactions * https://html.spec.whatwg.org/multipage/scripting.html#invoke-custom-element-reactions */ - void InvokeReactions(ElementQueue* aElementQueue); + void InvokeReactions(ElementQueue* aElementQueue, nsIGlobalObject* aGlobal); void Enqueue(Element* aElement, CustomElementReaction* aReaction); @@ -326,7 +338,11 @@ public: void GetCustomPrototype(nsIAtom* aAtom, JS::MutableHandle<JSObject*> aPrototype); - void Upgrade(Element* aElement, CustomElementDefinition* aDefinition); + /** + * Upgrade an element. + * https://html.spec.whatwg.org/multipage/scripting.html#upgrades + */ + void Upgrade(Element* aElement, CustomElementDefinition* aDefinition, ErrorResult& aRv); private: ~CustomElementRegistry(); |